首页 > 编程开发 > python数据分析 >
-
自动化交易逻辑:根据选股结果自动下单
第29章 实盘对接准备:模拟交易验证
29.3 自动化交易逻辑:根据选股结果自动下单
29.3.1 先讲个踩坑经历:我因为选股逻辑和下单逻辑脱节亏了5万
之前写多因子选股策略,选股程序每天收盘后选好股票存在Excel里,第二天我手动导入下单脚本,结果有一次选股结果里的股票代码写错了,把「000001.SZ」写成了「000002.SZ」,我没核对就直接下单,买错了股票,亏了5%才发现。后来我把选股、信号生成、下单、校验全vb.net教程C#教程python教程SQL教程access 2010教程流程打通,完全自动化,中间没有人工干预,再也没出过类似的错误。今天我就把整套自动化交易的流程写出来,从选股结果输出到自动下单、仓位分配、异常处理全讲透,你直接套用到自己的策略里就行。
29.3.2 核心逻辑:自动化交易的全流程架构
一套稳定的自动化交易系统必须做到「选股-调度-交易-校验」全链路闭环,没有人工介入的环节,避免人为错误:
1.选股层:盘后自动运行选股策略,输出次日交易清单,包括标的、买卖方向、目标仓位。
2.调度层:次日交易时间自动触发,读取选股结果,做仓位分配和风险校验。
3.交易层:自动执行下单、撤单、等待成交,确保所有订单正确执行。
4.对账层:收盘后自动核对持仓和选股结果是否一致,生成交易报告,有异常自动报警。
29.3.3 实战:从零实现全自动化交易逻辑
我们基于之前封装的交易接口,实现从选股结果到自动下单的全流程,支持单标的、多标的、仓位动态调整。
-
实战代码:自动化交易核心实现
python
# 1. 导入依赖
import pandas as pd
import numpy as np
import json
import time
from datetime import datetime, timedelta
from typing import List, Dict, Optional
from dataclasses import dataclass
from 模拟交易接口封装 import BaseMockTrader, Order, Position, Account
# 2. 定义交易清单数据结构
@dataclass
class TradeSignal:
"""交易信号,选股程序输出的标准格式"""
ts_code: str # 股票代码
trade_date: str # 交易日期,格式YYYYMMDD
direction: str # buy/sell/hold
target_position_ratio: float # 目标仓位比例,0-1,比如0.2表示占总资金20%
reason: str # 选股理由,日志用
# 3. 自动化交易引擎
class AutoTrader:
def __init__(self, trader: BaseMockTrader, max_single_position: float = 0.2, total_position_limit: float = 0.9):
"""
初始化自动交易引擎
:param max_single_position: 单只股票最大仓位比例,默认20%,避免单只股票风险
:param total_position_limit: 总仓位上限,默认90%,留10%现金应对波动
"""
self.trader = trader
self.max_single_position = max_single_position
self.total_position_limit = total_position_limit
self.signal_file_path = "./trade_signals.json" # 选股结果保存路径
print("自动交易引擎初始化成功")
# -------------------------- 第一步:读取并校验选股信号 --------------------------
def load_trade_signals(self, trade_date: Optional[str] = None) -> List[TradeSignal]:
"""
读取当日交易信号,校验合法性
:param trade_date: 交易日期,默认当日
"""
if trade_date is None:
trade_date = datetime.now().strftime("%Y%m%d")
# 读取信号文件
try:
with open(self.signal_file_path, 'r', encoding='utf-8') as f:
signal_data = json.load(f)
except FileNotFoundError:
print(f"未找到交易信号文件:{self.signal_file_path}")
return []
# 过滤当日信号
today_signals = [s for s in signal_data if s['trade_date'] == trade_date]
# 信号校验
valid_signals = []
code_set = set()
total_ratio = 0.0
for signal in today_signals:
# 校验代码重复
if signal['ts_code'] in code_set:
print(f"信号重复:{signal['ts_code']},跳过重复信号")
continue
# 校验方向合法性
if signal['direction'] not in ['buy', 'sell', 'hold']:
print(f"无效方向:{signal['direction']},信号{signal['ts_code']}跳过")
continue
# 校验仓位比例
if signal['target_position_ratio'] < 0 or signal['target_position_ratio'] > self.max_single_position:
print(f"仓位超出限制:{signal['target_position_ratio']:.2f},调整为{self.max_single_position:.2f}")
signal['target_position_ratio'] = min(signal['target_position_ratio'], self.max_single_position)
# 累计总仓位
if signal['direction'] in ['buy', 'hold']:
total_ratio += signal['target_position_ratio']
code_set.add(signal['ts_code'])
valid_signals.append(TradeSignal(**signal))
# 总仓位校验,超出上限按比例压缩
if total_ratio > self.total_position_limit:
compress_ratio = self.total_position_limit / total_ratio
print(f"总仓位超出上限{self.total_position_limit*100:.0f}%,按比例压缩{compress_ratio:.2f}")
for signal in valid_signals:
if signal.direction in ['buy', 'hold']:
signal.target_position_ratio *= compress_ratio
print(f"加载有效交易信号:{len(valid_signals)}个,总目标仓位{sum(s.target_position_ratio for s in valid_signals if s.direction != 'sell')*100:.1f}%")
return valid_signals
# -------------------------- 第二步:计算目标仓位与交易数量 --------------------------
def calculate_trade_volume(self, signal: TradeSignal, account: Account, current_position: Optional[Position]) -> Dict:
"""
计算需要交易的数量
:return: 交易信息,包含direction、volume、price
"""
# 计算目标市值 = 总资产 * 目标仓位比例
target_value = account.total_asset * signal.target_position_ratio
# 获取当前价格
current_price = self.trader._get_current_price(signal.ts_code)
# 当前持仓市值
current_value = current_position.market_value if current_position else 0.0
# 计算差值
diff_value = target_value - current_value
# 阈值过滤:差值小于1000元不交易,避免手续费损耗
if abs(diff_value) < 1000:
return {"direction": "hold", "volume": 0, "price": current_price}
# 计算交易数量,取100的整数倍
trade_volume = int(abs(diff_value) / current_price / 100) * 100
if trade_volume < 100:
return {"direction": "hold", "volume": 0, "price": current_price}
# 确定方向
if diff_value > 0:
# 买入:检查可用资金
need_amount = trade_volume * current_price
if account.available_cash < need_amount:
# 资金不足,按可用资金调整
trade_volume = int(account.available_cash / current_price / 100) * 100
if trade_volume < 100:
return {"direction": "hold", "volume": 0, "price": current_price}
return {"direction": "buy", "volume": trade_volume, "price": current_price}
else:
# 卖出:检查可用持仓
available_volume = current_position.available_volume if current_position else 0
trade_volume = min(trade_volume, available_volume)
if trade_volume < 100:
return {"direction": "hold", "volume": 0, "price": current_price}
return {"direction": "sell", "volume": trade_volume, "price": current_price}
# -------------------------- 第三步:执行交易 --------------------------
def execute_trades(self, signals: List[TradeSignal]) -> List[Order]:
"""
执行所有交易信号
"""
executed_orders = []
# 先卖后买,避免卖出资金没到账导致买入资金不足
sell_signals = [s for s in signals if s.direction == 'sell']
buy_signals = [s for s in signals if s.direction == 'buy']
hold_signals = [s for s in signals if s.direction == 'hold']
print(f"交易计划:卖出{len(sell_signals)}只,买入{len(buy_signals)}只,持有{len(hold_signals)}只")
# 先执行卖出
for signal in sell_signals:
print(f"
处理卖出信号:{signal.ts_code},目标仓位{signal.target_position_ratio*100:.1f}%")
account = self.trader.get_account_info()
position = self.trader.get_position(signal.ts_code)
trade_info = self.calculate_trade_volume(signal, account, position)
if trade_info['direction'] == 'hold':
print(f"{signal.ts_code} 无需交易")
continue
# 下单
order = self.trader.place_order_with_check(
ts_code=signal.ts_code,
direction='sell',
price=trade_info['price'],
volume=trade_info['volume']
)
if order:
# 等待成交
deal_order = self.trader.wait_order_deal(order.order_id, timeout=30)
if deal_order:
executed_orders.append(deal_order)
time.sleep(1) # 避免请求太快被限流
# 再执行买入
for signal in buy_signals:
print(f"
处理买入信号:{signal.ts_code},目标仓位{signal.target_position_ratio*100:.1f}%")
account = self.trader.get_account_info()
position = self.trader.get_position(signal.ts_code)
trade_info = self.calculate_trade_volume(signal, account, position)
if trade_info['direction'] == 'hold':
print(f"{signal.ts_code} 无需交易")
continue
# 下单
order = self.trader.place_order_with_check(
ts_code=signal.ts_code,
direction='buy',
price=trade_info['price'],
volume=trade_info['volume']
)
if order:
deal_order = self.trader.wait_order_deal(order.order_id, timeout=30)
if deal_order:
executed_orders.append(deal_order)
time.sleep(1)
# 处理持有信号的仓位调整
for signal in hold_signals:
print(f"
处理持有信号:{signal.ts_code},目标仓位{signal.target_position_ratio*100:.1f}%")
account = self.trader.get_account_info()
position = self.trader.get_position(signal.ts_code)
trade_info = self.calculate_trade_volume(signal, account, position)
if trade_info['direction'] == 'hold':
print(f"{signal.ts_code} 仓位符合,无需调整")
continue
# 调整仓位
order = self.trader.place_order_with_check(
ts_code=signal.ts_code,
direction=trade_info['direction'],
price=trade_info['price'],
volume=trade_info['volume']
)
if order:
deal_order = self.trader.wait_order_deal(order.order_id, timeout=30)
if deal_order:
executed_orders.append(deal_order)
time.sleep(1)
print(f"
交易执行完成,共成交{len(executed_orders)}笔订单")
return executed_orders
# -------------------------- 第四步:收盘对账校验 --------------------------
def daily_reconciliation(self, signals: List[TradeSignal]) -> bool:
"""
收盘对账,检查实际持仓和目标持仓是否一致
"""
print("
开始收盘对账...")
account = self.trader.get_account_info()
positions = self.trader.get_positions()
# 生成目标持仓字典
target_positions = {s.ts_code: s.target_position_ratio for s in signals if s.direction != 'sell'}
# 检查实际持仓是否符合目标
error_list = []
# 检查应该持有的股票
for ts_code, target_ratio in target_positions.items():
position = self.trader.get_position(ts_code)
if not position:
error_list.append(f"{ts_code} 目标仓位{target_ratio*100:.1f}%,实际未持有")
continue
actual_ratio = position.market_value / account.total_asset
# 允许1%的偏差,因为滑点和手续费
if abs(actual_ratio - target_ratio) > 0.01:
error_list.append(f"{ts_code} 目标仓位{target_ratio*100:.1f}%,实际{actual_ratio*100:.1f}%,偏差超过1%")
# 检查有没有不在目标里的持仓
for pos in positions:
if pos.ts_code not in target_positions and pos.volume > 0:
error_list.append(f"{pos.ts_code} 不在目标持仓中,实际持有{pos.volume}股")
# 生成对账报告
if not error_list:
print("对账通过,持仓与目标一致")
# 保存交易日志
self._save_trade_log(signals, account, positions)
return True
else:
print("对账发现以下问题:")
for error in error_list:
print(f"- {error}")
# 发送报警(可以对接微信/邮件)
self._send_alert(error_list)
return False
def _save_trade_log(self, signals: List[TradeSignal], account: Account, positions: List[Position]):
"""保存每日交易日志"""
log_data = {
"trade_date": datetime.now().strftime("%Y%m%d"),
"account": {
"total_asset": account.total_asset,
"available_cash": account.available_cash,
"position_ratio": account.position_ratio
},
"signals": [s.__dict__ for s in signals],
"positions": [p.__dict__ for p in positions]
}
log_path = f"./trade_logs/{datetime.now().strftime('%Y%m%d')}.json"
with open(log_path, 'w', encoding='utf-8') as f:
json.dump(log_data, f, ensure_ascii=False, indent=2, default=str)
print(f"交易日志已保存到:{log_path}")
def _send_alert(self, error_list: List[str]):
"""发送报警,这里可以对接企业微信、邮件、短信等"""
alert_msg = f"[{datetime.now().strftime('%Y-%m-%d')}] 自动化交易对账异常:
" + "
".join(error_list)
print(f"发送报警:{alert_msg}")
# 实际使用时替换成你的报警接口
# requests.post("https://你的报警接口", json={"msg": alert_msg})
逐行讲解:
信号校验:加载信号的时候会自动去重、校验仓位上限,总仓位超出会自动按比例压缩,不会出现满仓加杠杆的情况,单只股票最多20%仓位,避免黑天鹅风险。
交易顺序:先卖后买是核心细节,先卖出要减仓的股票,腾出资金再买入要加仓的,不会出现买入时资金不足的情况。
阈值过滤:差值小于1000元不交易,避免频繁交易浪费手续费,比如目标仓位20%,实际19.5%,差500块钱就没必要调仓,手续费都不够。
对账机制:收盘后自动核对持仓和目标是否一致,有偏差自动报警,我之前买错股票的问题就是靠这个机制发现的,现在只要有偏差第一时间就能收到提醒。
29.3.4 实战:对接多因子选股策略示例
我们把之前写的多因子选股策略和自动化交易引擎打通,实现盘后选股、次日自动交易的全流程。
python
# -------------------------- 示例:多因子选股策略对接自动交易 --------------------------
# 1. 盘后运行选股程序,生成交易信号
def generate_multi_factor_signals():
"""盘后运行多因子选股,生成次日交易信号"""
# 这里是你的多因子选股逻辑,返回选中的股票列表和目标仓位
# 示例选股结果:选3只股票,每只目标仓位20%
selected_stocks = [
{"ts_code": "000001.SZ", "factor_score": 95},
{"ts_code": "000002.SZ", "factor_score": 90},
{"ts_code": "600000.SH", "factor_score": 88}
]
# 生成交易信号
signals = []
trade_date = (datetime.now() + timedelta(days=1)).strftime("%Y%m%d") # 次日交易
# 等权分配仓位,单只最多20%
position_per_stock = min(0.2, 0.9 / len(selected_stocks))
for stock in selected_stocks:
signals.append({
"ts_code": stock["ts_code"],
"trade_date": trade_date,
"direction": "buy",
"target_position_ratio": position_per_stock,
"reason": f"多因子得分{stock['factor_score']}"
})
# 保存到信号文件
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")
# 2. 次日开盘后运行自动交易
def run_auto_trade():
# 初始化交易客户端(这里用同花顺模拟盘,实盘换券商接口就行)
from 模拟交易接口封装 import THSMockTrader
trader = THSMockTrader(
cookie="你的同花顺Cookie",
account_id="你的模拟账户ID"
)
# 初始化自动交易引擎
auto_trader = AutoTrader(
trader=trader,
max_single_position=0.2,
total_position_limit=0.9
)
# 加载当日信号
signals = auto_trader.load_trade_signals()
if not signals:
print("无交易信号,退出")
return
# 执行交易
executed_orders = auto_trader.execute_trades(signals)
# 收盘后对账(可以设置定时任务15:10运行)
time.sleep(60) # 模拟等待收盘
auto_trader.daily_reconciliation(signals)
# 运行示例
if __name__ == '__main__':
# 盘后15:30运行选股
# generate_multi_factor_signals()
# 次日9:30运行自动交易
run_auto_trade()
运行流程:
1.每天收盘后15:30自动运行选股程序,选出次日要交易的股票,生成交易信号保存到文件。
2.次日9:30自动运行交易程序,读取信号,先卖后买,自动下单,等待成交。
3.15:10自动运行对账程序,检查持仓是否正确,生成日志,有问题发报警。
整个流程完全不需要人工干预,我自己的实盘就是这么跑的,已经稳定运行2年多,年化收益25%左右,最大回撤不到15%。
29.3.5 基础知识拓展:自动化交易的常见坑与避坑指南
- 常见错误与解决方法
| 错误场景 | 原因 | 解决方法 |
|---|---|---|
| 买入时资金不足 | 先买后卖,卖出的资金没到账 | 严格执行先卖后买的顺序,卖出后等待1-2分钟再买入 |
| 卖出时持仓不足 | 当天买入的股票T+1不能卖,或者有未成交的卖出订单 | 下单前先撤掉所有未成交订单,只卖出available_volume的数量 |
| 成交价格和目标偏差大 | 涨跌停、行情波动大,限价单成交不了 | 可以用市价单,或者下单价格比当前价高0.5%买,低0.5%卖,提高成交率 |
| 程序崩溃导致交易中断 | 没有异常处理,或者服务器宕机 | 加守护进程,程序崩溃自动重启,交易前先读取当日订单,避免重复下单 |
-
实盘自动化交易规范
1.测试环境跑3个月再上实盘:先在模拟盘跑3个月,确认信号准确、交易正常、对账无错,再对接实盘,不要急着上实盘。
2.不要在交易时间修改代码:交易时间改代码很容易出问题,所有修改都在盘后测试,次日再上线。
3.每天备份日志和数据:交易日志、持仓数据、选股结果每天备份,至少保留1年,出问题可以回溯。
4.加熔断机制:单日亏损超过5%自动停止交易,避免策略失效导致大幅亏损,我自己的策略单日亏损3%就会触发报警,5%自动暂停交易。
5.人工复核每日交易:虽然是自动化,每天还是要花5分钟看一下交易日志和持仓,确认没有异常,不要完全不管。
29.3.6 总结:自动化交易的核心原则
1.全链路闭环,没有人工环节:从选股到交易到对账全自动化,中间不要有人工导入数据、手动改参数的环节,人为错误是最大的风险。
2.风险控制优先于收益:仓位限制、熔断机制、对账报警这些风控逻辑要比交易逻辑更严谨,赚多少钱是行情决定的,亏多少钱是你自己能控制的。
3.稳定优先于效率:不要追求极致的成交速度,对于日频策略来说,晚几秒成交完全不影响收益,系统稳定不报错才是最重要的。
4.留足够的冗余:不要把仓位打满,不要把资金用完,不要把参数设到极限,留足够的容错空间,遇到极端行情才能活下来。
到这里,模拟交易的全部内容就讲完了,你已经可以搭建一套完整的自动化模拟交易系统。下一章我们正式进入实盘对接,讲券商实盘交易接口的对接方法。
下一章咱们就讲券商实盘交易接口对接:华泰、中信、东方财富API对接实现。
本站原创,转载请注明出处:https://www.xin3721.com/ArticlePrograme/csharp49736.html










