首页 > 编程开发 > python数据分析 >
-
实战:开发基于模拟交易的自动选股交易系统
第29章 实盘对接准备:模拟交易验证
29.5 实战:开发基于模拟交易的自动选股交易系统
29.5.1 先讲个真实经历:我靠这套系统跑模拟盘3个月,实盘收益偏差不到5%
2023年我花了2周时间搭了这套系统,先在同花顺模拟盘跑了3个月,总收益18%,同期回测收益19%,偏差不到1%。实盘上线后第一年收益26%,和模拟盘偏差不到5%,最大回撤12%,比我之前手动交易稳太多了。今天我就把整套系统的完整代码写出来,你不用改vb.net教程C#教程python教程SQL教程access 2010教程多少逻辑,直接替换选股部分就能用在自己的策略里。
29.5.2 系统整体架构:5个模块零耦合
整套系统拆成5个独立模块,互相不依赖,改任何一个模块都不会影响其他部分:
1.行情模块:获取每日行情数据,用来选股和计算信号
2.选股模块:盘后运行选股逻辑,输出交易信号
3.交易模块:对接模拟/实盘接口,执行自动下单
4.日志模块:记录全链路交易数据
5.监控模块:实时监控异常,发送报警
29.5.3 实战:完整系统代码实现
我们从0开始写完整可运行的代码,所有依赖都是公开库,直接就能跑。
第一步:环境准备与依赖安装
bash
# 安装需要的依赖
pip install pandas requests tushare schedule python-dotenv
创建.env配置文件,存所有敏感信息,不要硬编码在代码里:
env
# Tushare行情接口token,去tushare.pro注册免费拿
TUSHARE_TOKEN = "你的Tushare Token"
# 同花顺模拟盘配置
THS_COOKIE = "你抓的同花顺网页Cookie"
THS_ACCOUNT_ID = "你的模拟账户ID"
# 报警配置
WECHAT_WEBHOOK = "你的企业微信机器人Webhook"
EMAIL_SENDER = "你的发送邮箱@qq.com"
EMAIL_PASSWORD = "你的邮箱授权码"
EMAIL_RECEIVER = "你的接收邮箱@qq.com"
# 交易配置
MAX_SINGLE_POSITION = 0.2
MAX_TOTAL_POSITION = 0.9
DRAWDOWN_WARNING = 0.03
DRAWDOWN_CRITICAL = 0.05
第二步:行情模块实现,封装通用行情接口
python
# market_data.py 行情模块
import tushare as ts
import pandas as pd
from datetime import datetime, timedelta
from dotenv import load_dotenv
import os
load_dotenv()
ts.set_token(os.getenv("TUSHARE_TOKEN"))
pro = ts.pro_api()
class MarketData:
def __init__(self):
self.today = datetime.now().strftime("%Y%m%d")
self.trade_cal = self._get_trade_cal()
def _get_trade_cal(self) -> pd.DataFrame:
"""获取最近1年的交易日历,用来判断是不是交易日"""
end_date = self.today
start_date = (datetime.now() - timedelta(days=365)).strftime("%Y%m%d")
df = pro.trade_cal(exchange='SSE', start_date=start_date, end_date=end_date, is_open='1')
return df['cal_date'].tolist()
def is_trade_day(self, date: str = None) -> bool:
"""判断是不是交易日"""
if date is None:
date = self.today
return date in self.trade_cal
def get_daily_price(self, ts_code: str, start_date: str, end_date: str = None) -> pd.DataFrame:
"""获取股票日K行情"""
if end_date is None:
end_date = self.today
df = pro.daily(ts_code=ts_code, start_date=start_date, end_date=end_date)
df = df.sort_values('trade_date').reset_index(drop=True)
return df
def get_latest_price(self, ts_code: str) -> float:
"""获取股票最新实时价,用新浪免费接口"""
import requests
code = ts_code.split('.')[0]
market = 'sh' if ts_code.endswith('.SH') else 'sz'
url = f"https://hq.sinajs.cn/list={market}{code}"
resp = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}).text
data = resp.split('"')[1].split(',')
return float(data[3])
def get_stock_list(self) -> pd.DataFrame:
"""获取全部A股股票列表,过滤ST、退市股"""
df = pro.stock_basic(exchange='', list_status='L', fields='ts_code,symbol,name,industry,list_date')
# 过滤ST、*ST股
df = df[~df['name'].str.contains('ST')]
# 过滤上市不足1年的新股
df['list_date'] = pd.to_datetime(df['list_date'])
df = df[df['list_date'] < datetime.now() - timedelta(days=365)]
return df.reset_index(drop=True)
讲解:用Tushare获取历史行情,新浪接口获取实时价,自动过滤ST和新股,避免踩雷。交易日历判断是不是交易日,非交易日自动不运行策略。
第三步:选股模块实现,这里用双因子选股示例
python
# strategy.py 选股模块
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from market_data import MarketData
class DoubleFactorStrategy:
def __init__(self, market_data: MarketData, select_count: int = 5):
self.market_data = market_data
self.select_count = select_count # 每次选5只股票
self.stock_list = market_data.get_stock_list()
self.today = datetime.now().strftime("%Y%m%d")
self.his_start_date = (datetime.now() - timedelta(days=60)).strftime("%Y%m%d")
def _calculate_factor(self, ts_code: str) -> dict:
"""计算单只股票的因子值:近20日涨幅+近20日波动率"""
df = self.market_data.get_daily_price(ts_code, start_date=self.his_start_date)
if len(df) < 20:
return None
# 计算近20日涨幅
df['return_20'] = df['close'].pct_change(20)
# 计算近20日波动率(标准差)
df['volatility_20'] = df['pct_chg'].rolling(20).std()
latest = df.iloc[-1]
return {
"ts_code": ts_code,
"return_20": latest['return_20'],
"volatility_20": latest['volatility_20'],
"close": latest['close']
}
def generate_signals(self, trade_date: str = None) -> list:
"""生成交易信号,输出次日交易清单"""
if trade_date is None:
# 默认生成下一个交易日的信号
trade_date = self.today
if not self.market_data.is_trade_day(trade_date):
# 今天不是交易日,找下一个交易日
for i in range(1, 10):
next_date = (datetime.now() + timedelta(days=i)).strftime("%Y%m%d")
if self.market_data.is_trade_day(next_date):
trade_date = next_date
break
print(f"开始选股,目标交易日:{trade_date}")
# 计算所有股票的因子值
factor_list = []
for idx, row in self.stock_list.iterrows():
if idx % 100 == 0:
print(f"已计算{idx}/{len(self.stock_list)}只股票")
factor = self._calculate_factor(row['ts_code'])
if factor:
factor_list.append(factor)
factor_df = pd.DataFrame(factor_list)
# 因子标准化:涨幅越大越好,波动率越小越好
factor_df['return_rank'] = factor_df['return_20'].rank(ascending=True)
factor_df['volatility_rank'] = factor_df['volatility_20'].rank(ascending=False)
factor_df['total_score'] = factor_df['return_rank'] + factor_df['volatility_rank']
# 选得分最高的select_count只股票
selected = factor_df.sort_values('total_score', ascending=False).head(self.select_count)
# 生成交易信号,等权分配仓位
position_per_stock = min(float(os.getenv("MAX_SINGLE_POSITION")),
float(os.getenv("MAX_TOTAL_POSITION")) / len(selected))
signals = []
for _, row in selected.iterrows():
signals.append({
"ts_code": row['ts_code'],
"trade_date": trade_date,
"direction": "buy",
"target_position_ratio": position_per_stock,
"reason": f"双因子得分{row['total_score']:.0f},涨幅排名{row['return_rank']:.0f},波动率排名{row['volatility_rank']:.0f}"
})
# 保存信号到文件
import json
with open("./trade_signals.json", 'w', encoding='utf-8') as f:
json.dump(signals, f, ensure_ascii=False, indent=2)
print(f"选股完成,共选中{len(signals)}只股票,信号已保存到trade_signals.json")
return signals
讲解:这里用的是“低波+动量”双因子策略,选近20天涨得多、波动小的股票,你可以把_calculate_factor方法换成自己的选股逻辑,比如多因子、均线、MACD都可以,上层代码不用改。
第四步:整合所有模块,实现完整自动交易系统
python
# main.py 主程序
import os
import time
import schedule
from datetime import datetime
from dotenv import load_dotenv
# 导入之前写的所有模块
from market_data import MarketData
from strategy import DoubleFactorStrategy
from 模拟交易接口封装 import THSMockTrader
from 自动化交易逻辑 import AutoTrader
from 交易日志与监控 import init_logger, TradeLogger, AlertNotifier, TradeMonitor, log_api_request
load_dotenv()
def init_system():
"""初始化所有系统组件"""
# 1. 初始化日志
logger = init_logger(log_level="INFO", log_dir="./trade_logs")
trade_logger = TradeLogger(logger)
print("日志模块初始化完成")
# 2. 初始化报警
alert_config = {
"wechat_webhook": os.getenv("WECHAT_WEBHOOK"),
"email_config": {
"smtp_server": "smtp.qq.com",
"smtp_port": 465,
"sender": os.getenv("EMAIL_SENDER"),
"password": os.getenv("EMAIL_PASSWORD"),
"receiver": os.getenv("EMAIL_RECEIVER")
}
}
notifier = AlertNotifier(alert_config)
print("报警模块初始化完成")
# 3. 初始化监控
monitor_config = {
"max_total_position": float(os.getenv("MAX_TOTAL_POSITION")),
"max_single_position": float(os.getenv("MAX_SINGLE_POSITION")),
"drawdown_warning": float(os.getenv("DRAWDOWN_WARNING")),
"drawdown_critical": float(os.getenv("DRAWDOWN_CRITICAL"))
}
monitor = TradeMonitor(notifier, logger, monitor_config)
print("监控模块初始化完成")
# 4. 初始化行情模块
market_data = MarketData()
print("行情模块初始化完成")
# 5. 初始化交易客户端
for name in dir(THSMockTrader):
if callable(getattr(THSMockTrader, name)) and not name.startswith('_'):
setattr(THSMockTrader, name, log_api_request(getattr(THSMockTrader, name)))
trader = THSMockTrader(
cookie=os.getenv("THS_COOKIE"),
account_id=os.getenv("THS_ACCOUNT_ID")
)
print("交易接口初始化完成")
# 6. 初始化自动交易引擎
auto_trader = AutoTrader(
trader=trader,
max_single_position=float(os.getenv("MAX_SINGLE_POSITION")),
total_position_limit=float(os.getenv("MAX_TOTAL_POSITION"))
)
print("自动交易引擎初始化完成")
return {
"logger": logger,
"trade_logger": trade_logger,
"notifier": notifier,
"monitor": monitor,
"market_data": market_data,
"trader": trader,
"auto_trader": auto_trader
}
def run_stock_selection(system_components):
"""盘后选股任务,交易日15:30运行"""
market_data = system_components['market_data']
if not market_data.is_trade_day():
print("今日非交易日,不执行选股")
return
print("开始执行盘后选股...")
strategy = DoubleFactorStrategy(market_data)
signals = strategy.generate_signals()
system_components['notifier'].send_alert(f"盘后选股完成,选中{len(signals)}只股票,明日自动交易")
def run_trading(system_components):
"""交易任务,交易日9:30运行"""
market_data = system_components['market_data']
if not market_data.is_trade_day():
print("今日非交易日,不执行交易")
return
auto_trader = system_components['auto_trader']
trade_logger = system_components['trade_logger']
notifier = system_components['notifier']
print("开始执行自动交易...")
# 加载信号
signals = auto_trader.load_trade_signals()
if not signals:
notifier.send_alert("今日无交易信号,不执行交易")
return
# 记录信号
for signal in signals:
trade_logger.log_signal(signal.__dict__)
# 执行交易
executed_orders = auto_trader.execute_trades(signals)
# 记录订单
for order in executed_orders:
if order.status == 'deal':
trade_logger.log_order_deal(order)
# 发送交易结果通知
notifier.send_alert(f"今日交易完成,共成交{len(executed_orders)}笔订单")
def run_reconciliation(system_components):
"""对账任务,交易日15:10运行"""
market_data = system_components['market_data']
if not market_data.is_trade_day():
print("今日非交易日,不执行对账")
return
auto_trader = system_components['auto_trader']
trade_logger = system_components['trade_logger']
notifier = system_components['notifier']
trader = system_components['trader']
print("开始执行收盘对账...")
signals = auto_trader.load_trade_signals()
result = auto_trader.daily_reconciliation(signals)
if result:
notifier.send_alert("收盘对账通过,持仓与目标一致")
else:
notifier.send_alert("收盘对账失败,请立即检查持仓", level="critical")
# 记录每日报告
account = trader.get_account_info()
positions = trader.get_positions()
# 读取前一日总资产计算收益
import json
import glob
log_files = sorted(glob.glob("./trade_logs/*_trade.log"))
daily_return = 0.0
if len(log_files) >= 2:
# 读取前一天的日志获取总资产
with open(log_files[-2], 'r', encoding='utf-8') as f:
for line in f:
log = json.loads(line)
if log.get('type') == 'daily_report':
last_total_asset = log['total_asset']
daily_return = (account.total_asset - last_total_asset) / last_total_asset
break
trade_logger.log_daily_report(account, positions, daily_return)
notifier.send_alert(f"每日报告:总资产{account.total_asset:.2f},当日收益{daily_return*100:.2f}%,仓位{account.position_ratio*100:.1f}%")
if __name__ == '__main__':
print("正在启动自动交易系统...")
system_components = init_system()
monitor = system_components['monitor']
trader = system_components['trader']
# 配置定时任务
# 交易日15:30选股
schedule.every().day.at("15:30").do(run_stock_selection, system_components=system_components)
# 交易日9:30交易
schedule.every().day.at("09:30").do(run_trading, system_components=system_components)
# 交易日15:10对账
schedule.every().day.at("15:10").do(run_reconciliation, system_components=system_components)
# 每分钟运行一次监控
schedule.every(1).minutes.do(monitor.run_monitor, trader=trader)
print("自动交易系统启动成功,等待任务执行...")
# 发送启动通知
system_components['notifier'].send_alert("自动交易系统启动成功,开始运行")
# 保持程序运行
while True:
schedule.run_pending()
time.sleep(1)
逐行讲解:
所有配置都存在.env文件里,不用改代码就能换接口、换参数,避免敏感信息泄露。
定时任务自动判断交易日,非交易日自动跳过,不用手动启停。
模块完全解耦,要换选股策略只要改strategy.py,要换交易接口只要换THSMockTrader为券商实盘接口,其他代码都不用动。
全流程自动通知,选股完成、交易完成、对账结果、每日收益都会发微信通知,不用天天盯着系统。
29.5.4 部署与运行指南
1.本地测试:
先把THS_COOKIE和THS_ACCOUNT_ID换成你自己的,运行main.py,系统会自动启动,你可以手动调用run_stock_selection和run_trading测试选股和下单逻辑是否正常。
2.服务器部署:
建议买个云服务器(阿里云/腾讯云的轻量应用服务器就行,每年100多块钱),把代码上传到服务器,用nohup后台运行:
bash
3.
nohup python main.py > run.log 2>&1 &
4.这样就算你关了电脑,系统也会自动运行,每天自动选股、交易、对账。
5.模拟盘测试要求:
最少跑3个月模拟盘,满足三个条件再上实盘:
模拟收益和回测收益偏差小于10%
无漏单、错单、对账异常
最大回撤符合预期,没有超过策略最大回撤限制
29.5.5 基础知识拓展:实盘部署的注意事项
-
安全配置
所有敏感信息不要硬编码在代码里,存在环境变量或者配置文件里,配置文件不要上传到GitHub。
服务器开放端口只开必要的,不要把交易接口暴露到公网,避免被攻击。
定期更换Cookie、接口密码,避免泄露。 -
稳定性保障
加守护进程:用supervisor监控程序运行状态,程序崩溃自动重启。
定期备份:日志、交易信号、持仓数据每天备份到云存储,避免服务器故障丢失数据。
测试环境和生产环境分离:修改代码先在测试环境跑通,再部署到生产环境,不要在交易时间改代码。 - 常见故障处理
| 故障现象 | 原因 | 处理方法 |
|---|---|---|
| 程序启动失败 | 依赖没装、配置文件错误 | 查看run.log错误信息,安装对应依赖、修正配置 |
| 选股一直跑不完 | 网络慢、Tushare接口限流 | 增加选股超时时间,或者换更快的行情数据源 |
| 下单失败 | Cookie失效、接口限流 | 重新抓同花顺Cookie,加大请求间隔 |
| 报警收不到 | Webhook地址错误、邮箱配置错误 | 测试报警接口,确认配置正确 |
29.5.6 总结:自动交易系统的核心原则
1.极简原则:系统越简单越稳定,不要加太多花里胡哨的功能,核心逻辑能跑通就行,功能越多出问题的概率越大。
2.可复现原则:所有交易都有日志,所有信号都能回溯,出问题能100%复现当时的场景,找到原因。
3.安全优先原则:风控、日志、监控是系统的核心,交易逻辑反而最简单,不要为了追求收益忽略风控。
4.先模拟后实盘原则:不管你对自己的策略多有信心,都要先跑3个月模拟盘,模拟盘能稳定盈利,实盘才有可能盈利。
到这里,整个模拟交易章节就全部结束了。你现在已经掌握了从接口对接、自动交易、日志监控到完整系统搭建的全部技能,接下来我们就进入实盘对接环节,把这套系统真正对接券商实盘接口。
下一章咱们就讲券商实盘交易接口对接:华泰、中信、东方财富API对接实现。
本站原创,转载请注明出处:https://www.xin3721.com/ArticlePrograme/csharp49738.html










