Files
sanguo_vnpy/archive/2026-04-29-cleanup/test/backtest/backtest_container.py
T
2026-04-29 20:15:25 +08:00

199 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
在Docker容器内执行回测 - 完整版
"""
import types
import sys
# vnpy.app 兼容性
vnpy_app = types.ModuleType('vnpy.app')
sys.modules['vnpy.app'] = vnpy_app
for name in ['cta_strategy', 'cta_backtester', 'data_manager']:
mod = types.ModuleType(f'vnpy.app.{name}')
sys.modules[f'vnpy.app.{name}'] = mod
setattr(vnpy_app, name, mod)
from vnpy_ctastrategy import (
CtaTemplate, StopOrder, TickData, BarData, TradeData, OrderData, BarGenerator, ArrayManager
)
sys.modules['vnpy.app.cta_strategy'].CtaTemplate = CtaTemplate
from vnpy_ctabacktester import BacktesterEngine
sys.modules['vnpy.app.cta_backtester'].BacktesterEngine = BacktesterEngine
from vnpy.event import EventEngine
from vnpy.trader.engine import MainEngine
from vnpy.trader.constant import Exchange, Interval, Direction, Offset
from vnpy.trader.database import get_database
from datetime import datetime
import traceback
# ============================================
# 策略代码
# ============================================
STRATEGY_CODE = '''
"""
单票固定比例止损策略 - vnpy CTA回测
"""
from vnpy_ctastrategy import (
CtaTemplate, StopOrder, TickData, BarData, TradeData, OrderData, BarGenerator, ArrayManager
)
from vnpy.trader.constant import Direction, Offset
class SingleStockStopLossStrategy(CtaTemplate):
"""单票固定比例止损策略 - 均线趋势跟踪+固定比例止损"""
author = "关羽 (云长)"
parameters = ["fast_window", "slow_window", "stop_loss_pct"]
variables = ["fast_ma", "slow_ma", "cost_price", "in_position"]
def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
super().__init__(cta_engine, strategy_name, vt_symbol, setting)
self.bg = BarGenerator(self.on_bar)
self.am = ArrayManager(max(30, 100))
self.fast_ma = 0.0
self.slow_ma = 0.0
self.cost_price = 0.0
self.in_position = False
def on_init(self):
self.write_log(f"策略初始化,fast={self.fast_window}, slow={self.slow_window}, stop_loss={self.stop_loss_pct:.1%}")
self.put_event()
def on_bar(self, bar):
self.am.update_bar(bar)
if not self.am.inited:
return
self.fast_ma = self.am.sma(self.fast_window)
self.slow_ma = self.am.sma(self.slow_window)
have_signal = True
if self.in_position and self.cost_price > 0:
current_drawdown = (bar.close_price - self.cost_price) / self.cost_price
if current_drawdown <= -self.stop_loss_pct:
if self.pos > 0:
self.sell(bar.close_price, self.pos)
self.in_position = False
self.write_log(f"触发止损:成本{self.cost_price:.2f},当前{bar.close_price:.2f},回撤{current_drawdown:.1%}")
have_signal = False
if have_signal:
if not self.in_position:
if self.fast_ma > self.slow_ma:
self.buy(bar.close_price, 10000)
self.cost_price = bar.close_price
self.in_position = True
self.write_log(f"金叉开多:价格{bar.close_price:.2f}")
else:
if self.fast_ma < self.slow_ma:
if self.pos > 0:
self.sell(bar.close_price, self.pos)
self.in_position = False
self.write_log(f"死叉平仓:价格{bar.close_price:.2f}")
self.put_event()
'''
# 执行策略代码
local_vars = {
'CtaTemplate': CtaTemplate,
'StopOrder': StopOrder,
'TickData': TickData,
'BarData': BarData,
'TradeData': TradeData,
'OrderData': OrderData,
'BarGenerator': BarGenerator,
'ArrayManager': ArrayManager,
'Direction': Direction,
'Offset': Offset,
}
exec(STRATEGY_CODE, globals(), local_vars)
# 查找策略类
strategy_classes = [
v for k, v in local_vars.items()
if isinstance(v, type) and issubclass(v, CtaTemplate) and v != CtaTemplate
]
if not strategy_classes:
print("❌ 未找到CtaTemplate子类")
sys.exit(1)
StrategyClass = strategy_classes[0]
class_name = StrategyClass.__name__
print("=" * 80)
print("🚀 回测执行 - 510300.SSE + 关羽15%止损")
print("=" * 80)
print(f"✅ 策略类: {class_name}")
# 初始化引擎
event_engine = EventEngine()
main_engine = MainEngine(event_engine)
backtester_engine = BacktesterEngine(main_engine, event_engine)
backtester_engine.classes[class_name] = StrategyClass
print("✅ 引擎初始化完成")
# 运行回测
print("\n运行回测...")
try:
backtester_engine.run_backtesting(
class_name=class_name,
vt_symbol="510300.SSE",
interval="1d",
start=datetime(2021, 1, 1),
end=datetime(2026, 3, 1),
rate=3e-5,
slippage=0.002,
size=10000,
pricetick=0.001,
capital=1000000,
setting={"stop_loss_pct": 0.15}
)
print("✅ 回测完成")
result = backtester_engine.get_result_statistics()
print("\n" + "=" * 80)
print("回测结果:")
print("=" * 80)
print(f"\n📊 绩效指标:")
print(f" 总收益率: {result.get('total_return', 0):.2%}")
print(f" 年化收益率: {result.get('annual_return', 0):.2%}")
print(f" 最大回撤: {result.get('max_drawdown', 0):.2%}")
print(f" 夏普比率: {result.get('sharpe_ratio', 0):.2f}")
print(f" 卡玛比率: {result.get('calmar_ratio', 0):.2f}")
print(f" 总交易次数: {result.get('total_trades', 0)}")
print(f" 胜率: {result.get('win_rate', 0):.2%}")
print(f" 盈亏比: {result.get('profit_loss_ratio', 0):.2f}")
trades = backtester_engine.get_all_trades()
print(f"\n📝 交易记录: 共 {len(trades)}")
for idx, trade in enumerate(trades, 1):
direction_str = "买入" if trade.direction == Direction.LONG else "卖出"
offset_str = "开仓" if trade.offset == Offset.OPEN else "平仓"
print(f" {idx}. {trade.datetime} {direction_str}{offset_str} {trade.symbol} @ {trade.price:.2f} × {trade.volume}")
print("\n" + "=" * 80)
print("✅ 回测执行完成!")
print("=" * 80)
except Exception as e:
print(f"❌ 回测失败: {e}")
traceback.print_exc()
sys.exit(1)