下载地址【已上传】:https://www.pan38.com/share.php?code=JCnzE 提取码:6666
声明:所下载的文件以及如下所示代码仅供学习参考用途,作者并不提供软件的相关服务。
完整的股票模拟交易系统的JavaScript实现。这个系统包含实时行情模拟、交易引擎、账户管理、K线图表等核心功能模块。
/**
- 股票虚拟仿真交易系统
- 功能包含:
-
- 实时行情模拟
-
- 交易引擎
-
- 账户管理
-
- K线图表
-
- 历史数据回放
*/
- 历史数据回放
// 系统配置
const CONFIG = {
INITIAL_BALANCE: 100000, // 初始资金
COMMISSION_RATE: 0.0003, // 佣金费率
STAMP_TAX: 0.001, // 印花税
TRANSFER_FEE: 0.00002, // 过户费
UPDATE_INTERVAL: 1000 // 行情更新间隔(ms)
};
// 股票数据生成器
class StockDataGenerator {
constructor() {
this.stocks = {
'SH600000': { name: '浦发银行', price: 10.25, open: 10.20, high: 10.30, low: 10.15, vol: 500000 },
'SH601318': { name: '中国平安', price: 45.60, open: 45.50, high: 46.20, low: 45.30, vol: 800000 },
'SZ000001': { name: '平安银行', price: 12.75, open: 12.70, high: 12.85, low: 12.65, vol: 600000 },
'SZ000002': { name: '万科A', price: 22.40, open: 22.30, high: 22.50, low: 22.20, vol: 400000 }
};
this.historyData = {};
this.initHistoryData();
}
initHistoryData() {
for (const code in this.stocks) {
this.historyData[code] = this.generateHistoryData(this.stocks[code].price);
}
}
generateHistoryData(basePrice) {
const data = [];
const now = new Date();
const days = 30;
for (let i = days; i >= 0; i--) {
const date = new Date(now);
date.setDate(date.getDate() - i);
const open = basePrice * (0.95 + Math.random() * 0.1);
const close = open * (0.98 + Math.random() * 0.04);
const high = Math.max(open, close) * (1 + Math.random() * 0.02);
const low = Math.min(open, close) * (0.98 + Math.random() * 0.02);
const vol = Math.floor(100000 + Math.random() * 900000);
data.push({
date: date.toISOString().split('T')[0],
open: parseFloat(open.toFixed(2)),
close: parseFloat(close.toFixed(2)),
high: parseFloat(high.toFixed(2)),
low: parseFloat(low.toFixed(2)),
vol: vol
});
}
return data;
}
updatePrices() {
for (const code in this.stocks) {
const stock = this.stocks[code];
const change = (Math.random() - 0.5) 0.1;
const newPrice = stock.price (1 + change);
stock.price = parseFloat(newPrice.toFixed(2));
stock.high = Math.max(stock.high, stock.price);
stock.low = Math.min(stock.low, stock.price);
stock.vol = Math.floor(stock.vol * (0.9 + Math.random() * 0.2));
// 添加到历史数据
const now = new Date();
const today = now.toISOString().split('T')[0];
const lastDay = this.historyData[code][this.historyData[code].length - 1];
if (lastDay.date === today) {
lastDay.close = stock.price;
lastDay.high = stock.high;
lastDay.low = stock.low;
lastDay.vol = stock.vol;
} else {
this.historyData[code].push({
date: today,
open: stock.open,
close: stock.price,
high: stock.high,
low: stock.low,
vol: stock.vol
});
if (this.historyData[code].length > 60) {
this.historyData[code].shift();
}
}
}
return this.stocks;
}
getStock(code) {
return this.stocks[code];
}
getHistoryData(code, days = 30) {
if (!this.historyData[code]) return null;
return this.historyData[code].slice(-days);
}
}
// 交易引擎
class TradingEngine {
constructor(dataGenerator) {
this.dataGenerator = dataGenerator;
this.orders = [];
this.transactions = [];
this.account = {
balance: CONFIG.INITIAL_BALANCE,
positions: {},
totalValue: CONFIG.INITIAL_BALANCE,
history: []
};
}
// 计算交易费用
calculateFees(code, price, quantity, isBuy) {
const stock = this.dataGenerator.getStock(code);
if (!stock) return 0;
const amount = price * quantity;
let fees = 0;
// 佣金
fees += amount * CONFIG.COMMISSION_RATE;
if (fees < 5) fees = 5; // 最低5元
// 印花税 (卖出时收取)
if (!isBuy) {
fees += amount * CONFIG.STAMP_TAX;
}
// 过户费
fees += amount * CONFIG.TRANSFER_FEE;
return parseFloat(fees.toFixed(2));
}
// 下单
placeOrder(code, price, quantity, type) {
const stock = this.dataGenerator.getStock(code);
if (!stock) return { success: false, message: '股票不存在' };
const isBuy = type === 'buy';
const amount = price * quantity;
const fees = this.calculateFees(code, price, quantity, isBuy);
if (isBuy) {
const totalCost = amount + fees;
if (totalCost > this.account.balance) {
return { success: false, message: '资金不足' };
}
} else {
const position = this.account.positions[code] || { quantity: 0 };
if (position.quantity < quantity) {
return { success: false, message: '持仓不足' };
}
}
const order = {
id: Date.now().toString(),
code,
name: stock.name,
price,
quantity,
type,
status: 'pending',
time: new Date().toISOString(),
fees
};
this.orders.push(order);
this.matchOrder(order);
return { success: true, order };
}
// 撮合订单
matchOrder(order) {
const stock = this.dataGenerator.getStock(order.code);
if (!stock) return;
// 简单撮合逻辑 - 按当前价格成交
if ((order.type === 'buy' && order.price >= stock.price) ||
(order.type === 'sell' && order.price <= stock.price)) {
this.executeOrder(order, stock.price);
}
}
// 执行订单
executeOrder(order, executePrice) {
order.status = 'filled';
order.executePrice = executePrice;
order.executeTime = new Date().toISOString();
const isBuy = order.type === 'buy';
const amount = executePrice * order.quantity;
const fees = order.fees;
if (isBuy) {
this.account.balance -= (amount + fees);
if (!this.account.positions[order.code]) {
this.account.positions[order.code] = {
quantity: 0,
cost: 0,
profit: 0
};
}
const position = this.account.positions[order.code];
const totalCost = position.cost * position.quantity + amount;
position.quantity += order.quantity;
position.cost = totalCost / position.quantity;
} else {
this.account.balance += (amount - fees);
const position = this.account.positions[order.code];
position.quantity -= order.quantity;
position.profit += (executePrice - position.cost) * order.quantity;
if (position.quantity === 0) {
delete this.account.positions[order.code];
}
}
const transaction = {
id: Date.now().toString(),
code: order.code,
name: order.name,
price: executePrice,
quantity: order.quantity,
type: order.type,
amount,
fees,
time: order.executeTime
};
this.transactions.push(transaction);
this.updateAccountValue();
return transaction;
}
// 更新账户总价值
updateAccountValue() {
let positionsValue = 0;
for (const code in this.account.positions) {
const stock = this.dataGenerator.getStock(code);
if (stock) {
const position = this.account.positions[code];
positionsValue += stock.price * position.quantity;
}
}
this.account.totalValue = this.account.balance + positionsValue;
// 记录账户历史
this.account.history.push({
date: new Date().toISOString(),
totalValue: this.account.totalValue,
balance: this.account.balance,
positionsValue
});
if (this.account.history.length > 100) {
this.account.history.shift();
}
}
// 获取账户信息
getAccountInfo() {
this.updateAccountValue();
return {
balance: this.account.balance,
totalValue: this.account.totalValue,
positions: this.account.positions,
history: this.account.history
};
}
// 获取持仓详情
getPositionDetails() {
const details = [];
for (const code in this.account.positions) {
const stock = this.dataGenerator.getStock(code);
if (stock) {
const position = this.account.positions[code];
const marketValue = stock.price * position.quantity;
const profit = (stock.price - position.cost) * position.quantity;
const profitRate = (stock.price - position.cost) / position.cost;
details.push({
code,
name: stock.name,
quantity: position.quantity,
cost: position.cost,
currentPrice: stock.price,
marketValue: parseFloat(marketValue.toFixed(2)),
profit: parseFloat(profit.toFixed(2)),
profitRate: parseFloat(profitRate.toFixed(4)),
weight: parseFloat((marketValue / this.account.totalValue).toFixed(4))
});
}
}
return details;
}
}
// K线图表渲染器
class KLineRenderer {
constructor(canvasId, dataGenerator) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
this.dataGenerator = dataGenerator;
this.config = {
padding: { top: 20, right: 20, bottom: 30, left: 50 },
colors: {
up: '#f6465d',
down: '#0ecb81',
grid: '#e6e6e6',
text: '#333',
background: '#fff'
},
candleWidth: 8,
gap: 2
};
}
render(code, days = 30) {
const data = this.dataGenerator.getHistoryData(code, days);
if (!data || data.length === 0) return;
const width = this.canvas.width;
const height = this.canvas.height;
const padding = this.config.padding;
const chartWidth = width - padding.left - padding.right;
const chartHeight = height - padding.top - padding.bottom;
// 清空画布
this.ctx.fillStyle = this.config.colors.background;
this.ctx.fillRect(0, 0, width, height);
// 计算价格范围
let minPrice = Infinity;
let maxPrice = -Infinity;
let maxVol = 0;
data.forEach(item => {
minPrice = Math.min(minPrice, item.low);
maxPrice = Math.max(maxPrice, item.high);
maxVol = Math.max(maxVol, item.vol);
});
const priceRange = maxPrice - minPrice;
const priceStep = priceRange / 5;
// 绘制网格和价格刻度
this.ctx.strokeStyle = this.config.colors.grid;
this.ctx.fillStyle = this.config.colors.text;
this.ctx.font = '10px Arial';
this.ctx.textAlign = 'right';
for (let i = 0; i <= 5; i++) {
const price = maxPrice - i * priceStep;
const y = padding.top + (i * chartHeight / 5);
this.ctx.beginPath();
this.ctx.moveTo(padding.left, y);
this.ctx.lineTo(width - padding.right, y);
this.ctx.stroke();
this.ctx.fillText(price.toFixed(2), padding.left - 5, y + 4);
}
// 绘制日期刻度
this.ctx.textAlign = 'center';
const dateStep = Math.max(1, Math.floor(data.length / 5));
for (let i = 0; i < data.length; i += dateStep) {
const x = padding.left + (i * (this.config.candleWidth + this.config.gap)) +
(this.config.candleWidth / 2);
const date = data[i].date.split('-').slice(1).join('/');
this.ctx.fillText(date, x, height - padding.bottom + 15);
}
// 绘制K线
const candleWidth = this.config.candleWidth;
const gap = this.config.gap;
for (let i = 0; i < data.length; i++) {
const item = data[i];
const isUp = item.close >= item.open;
const color = isUp ? this.config.colors.up : this.config.colors.down;
const x = padding.left + i * (candleWidth + gap);
const openY = padding.top + chartHeight * (maxPrice - item.open) / priceRange;
const closeY = padding.top + chartHeight * (maxPrice - item.close) / priceRange;
const highY = padding.top + chartHeight * (maxPrice - item.high) / priceRange;
const lowY = padding.top + chartHeight * (maxPrice - item.low) / priceRange;
// 绘制上下影线
this.ctx.strokeStyle = color;
this.ctx.lineWidth = 1;
this.ctx.beginPath();
this.ctx.moveTo(x + candleWidth / 2, highY);
this.ctx.lineTo(x + candleWidth / 2, lowY);
this.ctx.stroke();
// 绘制实体
this.ctx.fillStyle = color;
const bodyTop = Math.min(openY, closeY);
const bodyHeight = Math.abs(openY - closeY);
this.ctx.fillRect(x, bodyTop, candleWidth, bodyHeight || 1);
// 绘制成交量
const volHeight = (item.vol / maxVol) * (chartHeight * 0.2);
this.ctx.fillStyle = isUp ? this.config.colors.up : this.config.colors.down;
this.ctx.fillRect(x, height - padding.bottom - volHeight, candleWidth, volHeight);
}
}
}
// UI控制器
class UIController {
constructor(dataGenerator, tradingEngine) {
this.dataGenerator = dataGenerator;
this.tradingEngine = tradingEngine;
this.currentStock = 'SH600000';
this.initUI();
this.startDataUpdate();
}
initUI() {
// 初始化股票选择器
const stockSelect = document.getElementById('stock-select');
for (const code in this.dataGenerator.stocks) {
const stock = this.dataGenerator.stocks[code];
const option = document.createElement('option');
option.value = code;
option.textContent = ${code} ${stock.name}
;
stockSelect.appendChild(option);
}
stockSelect.addEventListener('change', (e) => {
this.currentStock = e.target.value;
this.updateStockInfo();
this.updateChart();
});
// 初始化交易表单
const tradeForm = document.getElementById('trade-form');
tradeForm.addEventListener('submit', (e) => {
e.preventDefault();
const code = this.currentStock;
const type = document.getElementById('trade-type').value;
const price = parseFloat(document.getElementById('trade-price').value);
const quantity = parseInt(document.getElementById('trade-quantity').value);
const result = this.tradingEngine.placeOrder(code, price, quantity, type);
if (result.success) {
alert(`订单提交成功! 订单号: ${result.order.id}`);
this.updateAccountInfo();
} else {
alert(`订单提交失败: ${result.message}`);
}
});
// 初始化K线图
this.klineRenderer = new KLineRenderer('kline-canvas', this.dataGenerator);
// 初始更新
this.updateStockInfo();
this.updateAccountInfo();
this.updateChart();
}
startDataUpdate() {
setInterval(() => {
this.dataGenerator.updatePrices();
this.updateStockInfo();
this.updateChart();
}, CONFIG.UPDATE_INTERVAL);
}
updateStockInfo() {
const stock = this.dataGenerator.getStock(this.currentStock);
if (!stock) return;
const change = stock.price - stock.open;
const changePercent = (change / stock.open) * 100;
const is