下载地址:https://pan38.com/xiazai/index.php?id=15 提取码:0fa4
声明一下,这个模拟器仅供学习和参考和,或者是教程演示用
哈喽各位小伙伴大家好呀!今天给大家带来一个超实用的前端项目——股票交割单生成器!完全纯前端实现,零后端依赖,打开即用!不仅可以生成逼真的交割单,还能绘制收益曲线、标记买卖点,简直是学习交易、制作教学演示的神器!
先上最终效果图(代码模拟):
📊 完全浏览器端运行,保护隐私
🎨 可视化收益曲线与买卖点标记
📝 生成逼真的股票交割单
🧩 模块化设计,易于二次开发
💾 支持数据导出为图片
🛠️ 技术栈
HTML5 + CSS3 + JavaScript(ES6+)
Canvas API(图表绘制)
FileSaver.js(文件导出)
纯前端,无需任何框架!
💻 代码实战开始!
项目结构
html
<!DOCTYPE html>
📈 股票交易模拟器 - 教学演示版
纯前端实现 · 隐私安全 · 零依赖
css
- {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Microsoft YaHei', sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a3a 0%, #0d1520 100%);
color: #e0e0e0;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
background: rgba(255, 255, 255, 0.05);
border-radius: 15px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.header h1 {
font-size: 2.5rem;
background: linear-gradient(90deg, #00d2ff, #3a7bd5);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
margin-bottom: 10px;
}
.control-panel {
background: rgba(255, 255, 255, 0.05);
border-radius: 12px;
padding: 25px;
margin-bottom: 30px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.control-item {
display: flex;
flex-direction: column;
gap: 8px;
}
.control-item label {
font-size: 0.9rem;
color: #8ba0b5;
}
.control-item input, .control-item select {
padding: 12px 15px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
color: white;
font-size: 1rem;
transition: all 0.3s;
}
.control-item input:focus, .control-item select:focus {
outline: none;
border-color: #3a7bd5;
box-shadow: 0 0 0 2px rgba(58, 123, 213, 0.3);
}
.btn-group {
display: flex;
gap: 15px;
margin-top: 10px;
}
.btn {
padding: 12px 25px;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
flex: 1;
}
.btn-primary {
background: linear-gradient(90deg, #3a7bd5, #00d2ff);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(58, 123, 213, 0.4);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.1);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.15);
}
JavaScript核心逻辑
javascript
// 股票数据生成器
class StockDataGenerator {
constructor() {this.stocks = [ { code: '000001', name: '平安银行', price: 12.5 }, { code: '000002', name: '万科A', price: 18.3 }, { code: '000858', name: '五粮液', price: 156.8 }, { code: '300750', name: '宁德时代', price: 210.5 }, { code: '00700', name: '腾讯控股', price: 320.8 } ];}
// 生成随机交易记录
generateTradeRecords(count = 20) {const records = []; const startDate = new Date(); startDate.setDate(startDate.getDate() - 30); for (let i = 0; i < count; i++) { const stock = this.stocks[Math.floor(Math.random() * this.stocks.length)]; const date = new Date(startDate); date.setDate(date.getDate() + Math.floor(i * 1.5)); const type = Math.random() > 0.5 ? '买入' : '卖出'; const amount = Math.floor(Math.random() * 1000) + 100; const price = stock.price * (0.9 + Math.random() * 0.2); const fee = amount * price * 0.0003; records.push({ 日期: this.formatDate(date), 股票代码: stock.code, 股票名称: stock.name, 买卖类型: type, 成交数量: amount, 成交价格: price.toFixed(2), 成交金额: (amount * price).toFixed(2), 手续费: fee.toFixed(2), 成交编号: `TR${Date.now()}${i}` }); } return records;}
// 生成收益曲线数据
generateProfitCurve(days = 30) {const data = []; let profit = 0; let balance = 100000; for (let i = 0; i < days; i++) { // 模拟每日收益波动 const dailyChange = (Math.random() - 0.5) * 0.1; profit += balance * dailyChange; balance += balance * dailyChange; data.push({ day: i + 1, profit: parseFloat(profit.toFixed(2)), balance: parseFloat(balance.toFixed(2)) }); } return data;}
formatDate(date) {
return `${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;}
}
// 图表绘制器
class ChartRenderer {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
this.width = this.canvas.width;
this.height = this.canvas.height;
// 初始化Canvas尺寸
this.resizeCanvas();
window.addEventListener('resize', () => this.resizeCanvas());
}
resizeCanvas() {
const container = this.canvas.parentElement;
this.canvas.width = container.clientWidth;
this.canvas.height = container.clientHeight;
this.width = this.canvas.width;
this.height = this.canvas.height;
}
// 绘制收益曲线
drawProfitCurve(data) {
// 清除画布
this.ctx.clearRect(0, 0, this.width, this.height);
// 设置边距
const margin = { top: 40, right: 40, bottom: 60, left: 60 };
const chartWidth = this.width - margin.left - margin.right;
const chartHeight = this.height - margin.top - margin.bottom;
// 查找最大值和最小值
const profits = data.map(d => d.profit);
const maxProfit = Math.max(...profits);
const minProfit = Math.min(...profits);
// 计算比例尺
const xScale = chartWidth / (data.length - 1);
const yScale = chartHeight / (maxProfit - minProfit);
// 绘制网格
this.drawGrid(margin, chartWidth, chartHeight, data.length);
// 绘制曲线
this.ctx.beginPath();
this.ctx.lineWidth = 3;
this.ctx.strokeStyle = '#00d2ff';
data.forEach((point, i) => {
const x = margin.left + i * xScale;
const y = margin.top + chartHeight - (point.profit - minProfit) * yScale;
if (i === 0) {
this.ctx.moveTo(x, y);
} else {
this.ctx.lineTo(x, y);
}
});
this.ctx.stroke();
// 绘制买卖点
this.drawTradePoints(data, margin, xScale, chartHeight, minProfit, yScale);
// 绘制坐标轴标签
this.drawAxisLabels(margin, chartWidth, chartHeight, maxProfit, minProfit, data.length);
}
drawGrid(margin, chartWidth, chartHeight, dataLength) {
this.ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
this.ctx.lineWidth = 1;
// 垂直线
for (let i = 0; i <= 10; i++) {
const x = margin.left + (i / 10) * chartWidth;
this.ctx.beginPath();
this.ctx.moveTo(x, margin.top);
this.ctx.lineTo(x, margin.top + chartHeight);
this.ctx.stroke();
}
// 水平线
for (let i = 0; i <= 10; i++) {
const y = margin.top + (i / 10) * chartHeight;
this.ctx.beginPath();
this.ctx.moveTo(margin.left, y);
this.ctx.lineTo(margin.left + chartWidth, y);
this.ctx.stroke();
}
}
drawTradePoints(data, margin, xScale, chartHeight, minProfit, yScale) {
// 随机选择一些点作为买卖点
const tradeIndices = [];
for (let i = 0; i < data.length; i += Math.floor(data.length / 5)) {
tradeIndices.push(i);
}
tradeIndices.forEach(i => {
const point = data[i];
const x = margin.left + i * xScale;
const y = margin.top + chartHeight - (point.profit - minProfit) * yScale;
// 买卖点颜色
const isBuy = Math.random() > 0.5;
this.ctx.fillStyle = isBuy ? '#00ff88' : '#ff4757';
// 绘制点
this.ctx.beginPath();
this.ctx.arc(x, y, 8, 0, Math.PI * 2);
this.ctx.fill();
// 绘制标签
this.ctx.fillStyle = 'white';
this.ctx.font = 'bold 14px Arial';
this.ctx.textAlign = 'center';
this.ctx.fillText(isBuy ? 'B' : 'S', x, y + 4);
});
}
drawAxisLabels(margin, chartWidth, chartHeight, maxProfit, minProfit, dataLength) {
this.ctx.fillStyle = '#8ba0b5';
this.ctx.font = '12px Arial';
// Y轴标签
this.ctx.textAlign = 'right';
this.ctx.textBaseline = 'middle';
for (let i = 0; i <= 5; i++) {
const value = minProfit + (maxProfit - minProfit) * (i / 5);
const y = margin.top + chartHeight - (i / 5) * chartHeight;
this.ctx.fillText(`¥${value.toFixed(2)}`, margin.left - 10, y);
}
// X轴标签
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'top';
for (let i = 0; i <= 5; i++) {
const day = Math.floor((dataLength - 1) * (i / 5));
const x = margin.left + (i / 5) * chartWidth;
this.ctx.fillText(`第${day + 1}天`, x, margin.top + chartHeight + 20);
}
// 标题
this.ctx.fillStyle = '#ffffff';
this.ctx.font = 'bold 18px Arial';
this.ctx.textAlign = 'center';
this.ctx.fillText('收益曲线与买卖点示意图', this.width / 2, 20);
}
}
// 交割单生成器
class SettlementGenerator {
constructor() {
this.generator = new StockDataGenerator();
}
// 生成交割单HTML
generateSettlementHTML(records) {
if (!records || records.length === 0) {
return '<div class="empty-data">暂无交易记录</div>';
}
let html = `
<div class="settlement-header">
<h3>📋 股票交割单</h3>
<div class="summary">
<div class="summary-item">
<span class="label">交易笔数:</span>
<span class="value">${records.length}笔</span>
</div>
<div class="summary-item">
<span class="label">总金额:</span>
<span class="value">¥${this.calculateTotal(records).toFixed(2)}</span>
</div>
</div>
</div>
<div class="table-container">
<table class="settlement-table">
<thead>
<tr>
${Object.keys(records[0]).map(key => `<th>${key}</th>`).join('')}
</tr>
</thead>
<tbody>
`;
records.forEach(record => {
html += '<tr>';
Object.values(record).forEach(value => {
const isBuy = record['买卖类型'] === '买入';
const colorClass = isBuy ? 'buy' : 'sell';
const displayValue = typeof value === 'string' && value.includes('TR')
? `<span class="trade-id">${value}</span>`
: value;
html += `<td class="${colorClass}">${displayValue}</td>`;
});
html += '</tr>';
});
html += `
</tbody>
</table>
</div>
`;
return html;
}
calculateTotal(records) {
return records.reduce((sum, record) => {
return sum + parseFloat(record.成交金额);
}, 0);
}
// 导出为图片
exportAsImage() {
const element = document.querySelector('.result-container');
html2canvas(element, {
backgroundColor: '#1a2a3a',
scale: 2
}).then(canvas => {
canvas.toBlob(blob => {
saveAs(blob, `交割单_${new Date().toISOString().slice(0,10)}.png`);
});
});
}
}
// 主应用控制器
class StockSimulatorApp {
constructor() {
this.dataGenerator = new StockDataGenerator();
this.chartRenderer = new ChartRenderer('profitChart');
this.settlementGenerator = new SettlementGenerator();
this.initEventListeners();
this.initializeDemoData();
}
initEventListeners() {
document.getElementById('generateBtn').addEventListener('click', () => this.generateData());
document.getElementById('exportBtn').addEventListener('click', () => this.settlementGenerator.exportAsImage());
document.getElementById('resetBtn').addEventListener('click', () => this.resetData());
}
initializeDemoData() {
// 生成初始演示数据
const records = this.dataGenerator.generateTradeRecords(15);
const profitData = this.dataGenerator.generateProfitCurve(30);
this.updateDisplay(records, profitData);
}
generateData() {
const recordCount = parseInt(document.getElementById('recordCount').value) || 15;
const daysCount = parseInt(document.getElementById('daysCount').value) || 30;
const records = this.dataGenerator.generateTradeRecords(recordCount);
const profitData = this.dataGenerator.generateProfitCurve(daysCount);
this.updateDisplay(records, profitData);
}
updateDisplay(records, profitData) {
// 更新图表
this.chartRenderer.drawProfitCurve(profitData);
// 更新交割单
const settlementHTML = this.settlementGenerator.generateSettlementHTML(records);
document.querySelector('.result-container').innerHTML = settlementHTML;
// 更新统计信息
this.updateStats(records, profitData);
}
updateStats(records, profitData) {
const lastProfit = profitData[profitData.length - 1].profit;
const totalAmount = this.settlementGenerator.calculateTotal(records);
document.getElementById('totalProfit').textContent = `¥${lastProfit.toFixed(2)}`;
document.getElementById('totalTrades').textContent = `${records.length}笔`;
document.getElementById('profitRate').textContent = `${((lastProfit / 100000) * 100).toFixed(2)}%`;
}
resetData() {
document.getElementById('recordCount').value = 15;
document.getElementById('daysCount').value = 30;
this.initializeDemoData();
}
}
// 页面加载完成后初始化应用
document.addEventListener('DOMContentLoaded', () => {
// 检查是否引入必要的库
if (typeof saveAs === 'undefined') {
console.error('FileSaver.js未正确加载');
alert('文件导出功能需要FileSaver.js库支持');
}
// 初始化应用
const app = new StockSimulatorApp();
// 添加CSS动画
document.querySelectorAll('.btn').forEach(btn => {
btn.addEventListener('click', function() {
this.style.transform = 'scale(0.95)';
setTimeout(() => {
this.style.transform = '';
}, 150);
});
});
});
🎯 功能扩展建议
这个基础版本已经实现了核心功能,你还可以扩展以下功能:
更多图表类型:添加K线图、成交量图等
数据导入导出:支持Excel/CSV格式
交易策略回测:添加简单策略回测功能
多账户管理:支持多个模拟账户
实时数据:接入免费股票API获取实时数据
📁 完整项目结构
text
stock-simulator/
├── index.html # 主页面
├── style.css # 样式文件
├── script.js # 主逻辑文件
├── README.md # 项目说明
└── assets/ # 静态资源
├── fonts/ # 字体文件
└── icons/ # 图标文件
💡 使用技巧
教学演示:调整买卖点参数,展示不同交易策略
数据模拟:可以修改生成器逻辑,模拟特定行情
样式定制:修改CSS变量,适配不同主题
性能优化:大量数据时使用虚拟滚动
🚀 如何运行?
复制上面的代码到三个文件中:index.html, style.css, script.js
用浏览器打开index.html
点击"生成模拟数据"按钮
探索各种功能!
如果这个项目对你有帮助,别忘了三连支持哦!
代码已上传GitHub,链接在评论区置顶!
有任何问题或建议,欢迎在评论区留言讨论!
下期预告:如何用Python爬取实时股票数据并接入这个模拟器!