feat(strategy): 策略库灌入vnpy内置8策略模板(DoubleMa/AtrRsi/BollChannel/DualThrust/KingKeltner/MultiSignal/MultiTimeframe/TurtleSignal); registry自研目录优先加载(编辑副本即刻生效,pip兜底); 分类器识别vnpy_ctastrategy import风格 [vps]
CI/CD / test (push) Successful in 10s
CI/CD / nas-deploy (push) Failing after 12s
CI/CD / nas-verify (push) Has been skipped

This commit is contained in:
2026-08-13 18:24:40 +08:00
parent 08aec403f7
commit d8c156e6de
11 changed files with 1289 additions and 8 deletions
+40 -7
View File
@@ -59,8 +59,7 @@ def list_strategies() -> list[dict]:
def strategy_params(name: str) -> dict:
"""Return {parameters: [...], defaults: {...}} for a strategy's dynamic form."""
classes = _load_strategy_classes()
cls = classes.get(name)
cls = _load_self_owned_module_classes().get(name) or _load_strategy_classes().get(name)
if cls is None:
return {"parameters": [], "defaults": {}}
params = list(getattr(cls, "parameters", []))
@@ -68,8 +67,37 @@ def strategy_params(name: str) -> dict:
return {"parameters": params, "defaults": defaults}
def _load_self_owned_module_classes() -> dict[str, type]:
"""从自研 CTA 目录(sanguo_trader/strategy)动态 import 策略类。
让「复制进来的 vnpy 内置模板 + 在线编辑的自研策略」真正可被回测加载,
且优先于 pip 包同名类(编辑副本即刻生效)。import 失败(如本机 dev 无
vnpy_ctastrategy)静默降级到 pip 包。
"""
classes: dict[str, type] = {}
pkg = SELF_OWNED_DIRS[0].replace("/", ".")
for f in _scan_self_owned():
if f["type"] != "cta" or f["dir"] != SELF_OWNED_DIRS[0] + "/":
continue
stem = f["name"][:-3]
try:
mod = importlib.import_module(f"{pkg}.{stem}")
cls = getattr(mod, f["class_name"], None)
if isinstance(cls, type):
classes[f["class_name"]] = cls
except Exception as e:
logger.debug("自研策略 %s 导入失败(降级 pip 包): %s", f["name"], e)
return classes
def get_strategy_class(name: str) -> type | None:
"""Return the strategy class by name (None if unavailable)."""
"""Return the strategy class by name (None if unavailable).
自研目录优先(编辑生效),pip 包兜底。
"""
cls = _load_self_owned_module_classes().get(name)
if cls is not None:
return cls
return _load_strategy_classes().get(name)
@@ -88,13 +116,18 @@ def _repo_root() -> str:
def _classify_strategy_type(tree: ast.Module) -> str:
"""AST 判定 portfolio vs ctaimport 了 vnpy StrategyTemplate → cta,否则 portfolio。"""
"""AST 判定 portfolio vs cta
- import 了 bullet_trade 风格 StrategyTemplate → cta
- from vnpy_ctastrategy import ...vnpy 内置/自研 CTA 文件风格)→ cta
否则 portfolio。"""
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom) and (node.module or "").endswith("StrategyTemplate"):
return "cta"
if isinstance(node, ast.ImportFrom):
mod = node.module or ""
if mod.endswith("StrategyTemplate") or mod.endswith("vnpy_ctastrategy"):
return "cta"
if isinstance(node, ast.Import):
for alias in node.names:
if "StrategyTemplate" in alias.name:
if "StrategyTemplate" in alias.name or "vnpy_ctastrategy" in alias.name:
return "cta"
return "portfolio"
View File
+143
View File
@@ -0,0 +1,143 @@
import numpy as np
from vnpy_ctastrategy import (
CtaTemplate,
StopOrder,
TickData,
BarData,
TradeData,
OrderData,
BarGenerator,
ArrayManager,
)
class AtrRsiStrategy(CtaTemplate):
""""""
author = "用Python的交易员"
atr_length: int = 22
atr_ma_length: int = 10
rsi_length: int = 5
rsi_entry: int = 16
trailing_percent: float = 0.8
fixed_size: int = 1
atr_value: float = 0
atr_ma: float = 0
rsi_value: float = 0
rsi_buy: float = 0
rsi_sell: float = 0
intra_trade_high: float = 0
intra_trade_low: float = 0
parameters = [
"atr_length",
"atr_ma_length",
"rsi_length",
"rsi_entry",
"trailing_percent",
"fixed_size"
]
variables = [
"atr_value",
"atr_ma",
"rsi_value",
"rsi_buy",
"rsi_sell",
"intra_trade_high",
"intra_trade_low"
]
def on_init(self) -> None:
"""
Callback when strategy is inited.
"""
self.write_log("策略初始化")
self.bg: BarGenerator = BarGenerator(self.on_bar)
self.am: ArrayManager = ArrayManager()
self.rsi_buy = 50 + self.rsi_entry
self.rsi_sell = 50 - self.rsi_entry
self.load_bar(10)
def on_start(self) -> None:
"""
Callback when strategy is started.
"""
self.write_log("策略启动")
def on_stop(self) -> None:
"""
Callback when strategy is stopped.
"""
self.write_log("策略停止")
def on_tick(self, tick: TickData) -> None:
"""
Callback of new tick data update.
"""
self.bg.update_tick(tick)
def on_bar(self, bar: BarData) -> None:
"""
Callback of new bar data update.
"""
self.cancel_all()
am: ArrayManager = self.am
am.update_bar(bar)
if not am.inited:
return
atr_array: np.ndarray = am.atr(self.atr_length, array=True)
self.atr_value = atr_array[-1]
self.atr_ma = atr_array[-self.atr_ma_length:].mean()
self.rsi_value = am.rsi(self.rsi_length)
if self.pos == 0:
self.intra_trade_high = bar.high_price
self.intra_trade_low = bar.low_price
if self.atr_value > self.atr_ma:
if self.rsi_value > self.rsi_buy:
self.buy(bar.close_price + 5, self.fixed_size)
elif self.rsi_value < self.rsi_sell:
self.short(bar.close_price - 5, self.fixed_size)
elif self.pos > 0:
self.intra_trade_high = max(self.intra_trade_high, bar.high_price)
self.intra_trade_low = bar.low_price
long_stop: float = self.intra_trade_high * (1 - self.trailing_percent / 100)
self.sell(long_stop, abs(self.pos), stop=True)
elif self.pos < 0:
self.intra_trade_low = min(self.intra_trade_low, bar.low_price)
self.intra_trade_high = bar.high_price
short_stop: float = self.intra_trade_low * (1 + self.trailing_percent / 100)
self.cover(short_stop, abs(self.pos), stop=True)
self.put_event()
def on_order(self, order: OrderData) -> None:
"""
Callback of new order data update.
"""
pass
def on_trade(self, trade: TradeData) -> None:
"""
Callback of new trade data update.
"""
self.put_event()
def on_stop_order(self, stop_order: StopOrder) -> None:
"""
Callback of stop order update.
"""
pass
@@ -0,0 +1,142 @@
from vnpy_ctastrategy import (
CtaTemplate,
StopOrder,
TickData,
BarData,
TradeData,
OrderData,
BarGenerator,
ArrayManager,
)
class BollChannelStrategy(CtaTemplate):
""""""
author = "用Python的交易员"
boll_window: int = 18
boll_dev: float = 3.4
cci_window: int = 10
atr_window: int = 30
sl_multiplier: float = 5.2
fixed_size: int = 1
boll_up: float = 0
boll_down: float = 0
cci_value: float = 0
atr_value: float = 0
intra_trade_high: float = 0
intra_trade_low: float = 0
long_stop: float = 0
short_stop: float = 0
parameters = [
"boll_window",
"boll_dev",
"cci_window",
"atr_window",
"sl_multiplier",
"fixed_size"
]
variables = [
"boll_up",
"boll_down",
"cci_value",
"atr_value",
"intra_trade_high",
"intra_trade_low",
"long_stop",
"short_stop"
]
def on_init(self) -> None:
"""
Callback when strategy is inited.
"""
self.write_log("策略初始化")
self.bg = BarGenerator(self.on_bar, 15, self.on_15min_bar)
self.am = ArrayManager()
self.load_bar(10)
def on_start(self) -> None:
"""
Callback when strategy is started.
"""
self.write_log("策略启动")
def on_stop(self) -> None:
"""
Callback when strategy is stopped.
"""
self.write_log("策略停止")
def on_tick(self, tick: TickData) -> None:
"""
Callback of new tick data update.
"""
self.bg.update_tick(tick)
def on_bar(self, bar: BarData) -> None:
"""
Callback of new bar data update.
"""
self.bg.update_bar(bar)
def on_15min_bar(self, bar: BarData) -> None:
""""""
self.cancel_all()
am = self.am
am.update_bar(bar)
if not am.inited:
return
self.boll_up, self.boll_down = am.boll(self.boll_window, self.boll_dev)
self.cci_value = am.cci(self.cci_window)
self.atr_value = am.atr(self.atr_window)
if self.pos == 0:
self.intra_trade_high = bar.high_price
self.intra_trade_low = bar.low_price
if self.cci_value > 0:
self.buy(self.boll_up, self.fixed_size, True)
elif self.cci_value < 0:
self.short(self.boll_down, self.fixed_size, True)
elif self.pos > 0:
self.intra_trade_high = max(self.intra_trade_high, bar.high_price)
self.intra_trade_low = bar.low_price
self.long_stop = self.intra_trade_high - self.atr_value * self.sl_multiplier
self.sell(self.long_stop, abs(self.pos), True)
elif self.pos < 0:
self.intra_trade_high = bar.high_price
self.intra_trade_low = min(self.intra_trade_low, bar.low_price)
self.short_stop = self.intra_trade_low + self.atr_value * self.sl_multiplier
self.cover(self.short_stop, abs(self.pos), True)
self.put_event()
def on_order(self, order: OrderData) -> None:
"""
Callback of new order data update.
"""
pass
def on_trade(self, trade: TradeData) -> None:
"""
Callback of new trade data update.
"""
self.put_event()
def on_stop_order(self, stop_order: StopOrder) -> None:
"""
Callback of stop order update.
"""
pass
@@ -0,0 +1,117 @@
import numpy as np
from vnpy_ctastrategy import (
CtaTemplate,
StopOrder,
TickData,
BarData,
TradeData,
OrderData,
BarGenerator,
ArrayManager,
)
class DoubleMaStrategy(CtaTemplate):
""""""
author = "用Python的交易员"
fast_window: int = 10
slow_window: int = 20
fast_ma0: float = 0.0
fast_ma1: float = 0.0
slow_ma0: float = 0.0
slow_ma1: float = 0.0
parameters = ["fast_window", "slow_window"]
variables = ["fast_ma0", "fast_ma1", "slow_ma0", "slow_ma1"]
def on_init(self) -> None:
"""
Callback when strategy is inited.
"""
self.write_log("策略初始化")
self.bg: BarGenerator = BarGenerator(self.on_bar)
self.am: ArrayManager = ArrayManager()
self.load_bar(10)
def on_start(self) -> None:
"""
Callback when strategy is started.
"""
self.write_log("策略启动")
self.put_event()
def on_stop(self) -> None:
"""
Callback when strategy is stopped.
"""
self.write_log("策略停止")
self.put_event()
def on_tick(self, tick: TickData) -> None:
"""
Callback of new tick data update.
"""
self.bg.update_tick(tick)
def on_bar(self, bar: BarData) -> None:
"""
Callback of new bar data update.
"""
self.cancel_all()
am: ArrayManager = self.am
am.update_bar(bar)
if not am.inited:
return
fast_ma: np.ndarray = am.sma(self.fast_window, array=True)
self.fast_ma0 = fast_ma[-1]
self.fast_ma1 = fast_ma[-2]
slow_ma: np.ndarray = am.sma(self.slow_window, array=True)
self.slow_ma0 = slow_ma[-1]
self.slow_ma1 = slow_ma[-2]
cross_over: bool = self.fast_ma0 > self.slow_ma0 and self.fast_ma1 < self.slow_ma1
cross_below: bool = self.fast_ma0 < self.slow_ma0 and self.fast_ma1 > self.slow_ma1
if cross_over:
if self.pos == 0:
self.buy(bar.close_price, 1)
elif self.pos < 0:
self.cover(bar.close_price, 1)
self.buy(bar.close_price, 1)
elif cross_below:
if self.pos == 0:
self.short(bar.close_price, 1)
elif self.pos > 0:
self.sell(bar.close_price, 1)
self.short(bar.close_price, 1)
self.put_event()
def on_order(self, order: OrderData) -> None:
"""
Callback of new order data update.
"""
pass
def on_trade(self, trade: TradeData) -> None:
"""
Callback of new trade data update.
"""
self.put_event()
def on_stop_order(self, stop_order: StopOrder) -> None:
"""
Callback of stop order update.
"""
pass
@@ -0,0 +1,149 @@
from datetime import time
from vnpy_ctastrategy import (
CtaTemplate,
StopOrder,
TickData,
BarData,
TradeData,
OrderData,
BarGenerator,
ArrayManager,
)
class DualThrustStrategy(CtaTemplate):
""""""
author = "用Python的交易员"
fixed_size: int = 1
k1: float = 0.4
k2: float = 0.6
day_open: float = 0
day_high: float = 0
day_low: float = 0
day_range: float = 0
long_entry: float = 0
short_entry: float = 0
long_entered: bool = False
short_entered: bool = False
parameters = ["k1", "k2", "fixed_size"]
variables = ["day_range", "long_entry", "short_entry"]
def on_init(self) -> None:
"""
Callback when strategy is inited.
"""
self.write_log("策略初始化")
self.bg: BarGenerator = BarGenerator(self.on_bar)
self.am: ArrayManager = ArrayManager()
self.bars: list[BarData] = []
self.exit_time: time = time(hour=14, minute=55)
self.load_bar(10)
def on_start(self) -> None:
"""
Callback when strategy is started.
"""
self.write_log("策略启动")
def on_stop(self) -> None:
"""
Callback when strategy is stopped.
"""
self.write_log("策略停止")
def on_tick(self, tick: TickData) -> None:
"""
Callback of new tick data update.
"""
self.bg.update_tick(tick)
def on_bar(self, bar: BarData) -> None:
"""
Callback of new bar data update.
"""
self.cancel_all()
self.bars.append(bar)
if len(self.bars) <= 2:
return
else:
self.bars.pop(0)
last_bar: BarData = self.bars[-2]
if last_bar.datetime.date() != bar.datetime.date():
if self.day_high:
self.day_range = self.day_high - self.day_low
self.long_entry = bar.open_price + self.k1 * self.day_range
self.short_entry = bar.open_price - self.k2 * self.day_range
self.day_open = bar.open_price
self.day_high = bar.high_price
self.day_low = bar.low_price
self.long_entered = False
self.short_entered = False
else:
self.day_high = max(self.day_high, bar.high_price)
self.day_low = min(self.day_low, bar.low_price)
if not self.day_range:
return
if bar.datetime.time() < self.exit_time:
if self.pos == 0:
if bar.close_price > self.day_open:
if not self.long_entered:
self.buy(self.long_entry, self.fixed_size, stop=True)
else:
if not self.short_entered:
self.short(self.short_entry,
self.fixed_size, stop=True)
elif self.pos > 0:
self.long_entered = True
self.sell(self.short_entry, self.fixed_size, stop=True)
if not self.short_entered:
self.short(self.short_entry, self.fixed_size, stop=True)
elif self.pos < 0:
self.short_entered = True
self.cover(self.long_entry, self.fixed_size, stop=True)
if not self.long_entered:
self.buy(self.long_entry, self.fixed_size, stop=True)
else:
if self.pos > 0:
self.sell(bar.close_price * 0.99, abs(self.pos))
elif self.pos < 0:
self.cover(bar.close_price * 1.01, abs(self.pos))
self.put_event()
def on_order(self, order: OrderData) -> None:
"""
Callback of new order data update.
"""
pass
def on_trade(self, trade: TradeData) -> None:
"""
Callback of new trade data update.
"""
self.put_event()
def on_stop_order(self, stop_order: StopOrder) -> None:
"""
Callback of stop order update.
"""
pass
@@ -0,0 +1,149 @@
from vnpy_ctastrategy import (
CtaTemplate,
StopOrder,
TickData,
BarData,
TradeData,
OrderData,
BarGenerator,
ArrayManager,
)
class KingKeltnerStrategy(CtaTemplate):
""""""
author = "用Python的交易员"
kk_length: int = 11
kk_dev: float = 1.6
trailing_percent: float = 0.8
fixed_size: int = 1
kk_up: float = 0
kk_down: float = 0
intra_trade_high: float = 0
intra_trade_low: float = 0
parameters = ["kk_length", "kk_dev", "trailing_percent", "fixed_size"]
variables = ["kk_up", "kk_down"]
def on_init(self) -> None:
"""
Callback when strategy is inited.
"""
self.write_log("策略初始化")
self.bg: BarGenerator = BarGenerator(self.on_bar, 5, self.on_5min_bar)
self.am: ArrayManager = ArrayManager()
self.long_vt_orderids: list[str] = []
self.short_vt_orderids: list[str] = []
self.vt_orderids: list[str] = []
self.load_bar(10)
def on_start(self) -> None:
"""
Callback when strategy is started.
"""
self.write_log("策略启动")
def on_stop(self) -> None:
"""
Callback when strategy is stopped.
"""
self.write_log("策略停止")
def on_tick(self, tick: TickData) -> None:
"""
Callback of new tick data update.
"""
self.bg.update_tick(tick)
def on_bar(self, bar: BarData) -> None:
"""
Callback of new bar data update.
"""
self.bg.update_bar(bar)
def on_5min_bar(self, bar: BarData) -> None:
""""""
for orderid in self.vt_orderids:
self.cancel_order(orderid)
self.vt_orderids.clear()
am: ArrayManager = self.am
am.update_bar(bar)
if not am.inited:
return
self.kk_up, self.kk_down = am.keltner(self.kk_length, self.kk_dev)
if self.pos == 0:
self.intra_trade_high = bar.high_price
self.intra_trade_low = bar.low_price
self.send_oco_order(self.kk_up, self.kk_down, self.fixed_size)
elif self.pos > 0:
self.intra_trade_high = max(self.intra_trade_high, bar.high_price)
self.intra_trade_low = bar.low_price
sell_orderids: list[str] = self.sell(
self.intra_trade_high * (1 - self.trailing_percent / 100),
abs(self.pos),
True
)
self.vt_orderids.extend(sell_orderids)
elif self.pos < 0:
self.intra_trade_high = bar.high_price
self.intra_trade_low = min(self.intra_trade_low, bar.low_price)
cover_orderids: list[str] = self.cover(
self.intra_trade_low * (1 + self.trailing_percent / 100),
abs(self.pos),
True
)
self.vt_orderids.extend(cover_orderids)
self.put_event()
def on_order(self, order: OrderData) -> None:
"""
Callback of new order data update.
"""
pass
def on_trade(self, trade: TradeData) -> None:
"""
Callback of new trade data update.
"""
if self.pos != 0:
if self.pos > 0:
for short_orderid in self.short_vt_orderids:
self.cancel_order(short_orderid)
elif self.pos < 0:
for buy_orderid in self.long_vt_orderids:
self.cancel_order(buy_orderid)
for orderid in (self.long_vt_orderids + self.short_vt_orderids):
if orderid in self.vt_orderids:
self.vt_orderids.remove(orderid)
self.put_event()
def send_oco_order(self, buy_price: float, short_price: float, volume: float) -> None:
""""""
self.long_vt_orderids = self.buy(buy_price, volume, True)
self.short_vt_orderids = self.short(short_price, volume, True)
self.vt_orderids.extend(self.long_vt_orderids)
self.vt_orderids.extend(self.short_vt_orderids)
def on_stop_order(self, stop_order: StopOrder) -> None:
"""
Callback of stop order update.
"""
pass
@@ -0,0 +1,231 @@
from vnpy_ctastrategy import (
StopOrder,
TickData,
BarData,
TradeData,
OrderData,
BarGenerator,
ArrayManager,
CtaSignal,
TargetPosTemplate
)
class RsiSignal(CtaSignal):
""""""
def __init__(self, rsi_window: int, rsi_level: float) -> None:
"""Constructor"""
super().__init__()
self.rsi_window: int = rsi_window
self.rsi_level: float = rsi_level
self.rsi_long: float = 50 + self.rsi_level
self.rsi_short: float = 50 - self.rsi_level
self.bg: BarGenerator = BarGenerator(self.on_bar)
self.am: ArrayManager = ArrayManager()
def on_tick(self, tick: TickData) -> None:
"""
Callback of new tick data update.
"""
self.bg.update_tick(tick)
def on_bar(self, bar: BarData) -> None:
"""
Callback of new bar data update.
"""
self.am.update_bar(bar)
if not self.am.inited:
self.set_signal_pos(0)
rsi_value: float = self.am.rsi(self.rsi_window)
if rsi_value >= self.rsi_long:
self.set_signal_pos(1)
elif rsi_value <= self.rsi_short:
self.set_signal_pos(-1)
else:
self.set_signal_pos(0)
class CciSignal(CtaSignal):
""""""
def __init__(self, cci_window: int, cci_level: float) -> None:
""""""
super().__init__()
self.cci_window: int = cci_window
self.cci_level: float = cci_level
self.cci_long: float = self.cci_level
self.cci_short: float = -self.cci_level
self.bg: BarGenerator = BarGenerator(self.on_bar)
self.am: ArrayManager = ArrayManager()
def on_tick(self, tick: TickData) -> None:
"""
Callback of new tick data update.
"""
self.bg.update_tick(tick)
def on_bar(self, bar: BarData) -> None:
"""
Callback of new bar data update.
"""
self.am.update_bar(bar)
if not self.am.inited:
self.set_signal_pos(0)
cci_value: float = self.am.cci(self.cci_window)
if cci_value >= self.cci_long:
self.set_signal_pos(1)
elif cci_value <= self.cci_short:
self.set_signal_pos(-1)
else:
self.set_signal_pos(0)
class MaSignal(CtaSignal):
""""""
def __init__(self, fast_window: int, slow_window: int) -> None:
""""""
super().__init__()
self.fast_window: int = fast_window
self.slow_window: int = slow_window
self.bg: BarGenerator = BarGenerator(self.on_bar, 5, self.on_5min_bar)
self.am: ArrayManager = ArrayManager()
def on_tick(self, tick: TickData) -> None:
"""
Callback of new tick data update.
"""
self.bg.update_tick(tick)
def on_bar(self, bar: BarData) -> None:
"""
Callback of new bar data update.
"""
self.bg.update_bar(bar)
def on_5min_bar(self, bar: BarData) -> None:
""""""
self.am.update_bar(bar)
if not self.am.inited:
self.set_signal_pos(0)
fast_ma: float = self.am.sma(self.fast_window)
slow_ma: float = self.am.sma(self.slow_window)
if fast_ma > slow_ma:
self.set_signal_pos(1)
elif fast_ma < slow_ma:
self.set_signal_pos(-1)
else:
self.set_signal_pos(0)
class MultiSignalStrategy(TargetPosTemplate):
""""""
author = "用Python的交易员"
rsi_window: int = 14
rsi_level: int = 20
cci_window: int = 30
cci_level: int = 10
fast_window: int = 5
slow_window: int = 20
parameters = ["rsi_window", "rsi_level", "cci_window",
"cci_level", "fast_window", "slow_window"]
def on_init(self) -> None:
"""
Callback when strategy is inited.
"""
self.write_log("策略初始化")
self.rsi_signal: RsiSignal = RsiSignal(self.rsi_window, self.rsi_level)
self.cci_signal: CciSignal = CciSignal(self.cci_window, self.cci_level)
self.ma_signal: MaSignal = MaSignal(self.fast_window, self.slow_window)
self.signal_pos: dict[str, int] = {
"rsi": 0,
"cci": 0,
"ma": 0
}
self.load_bar(10)
def on_start(self) -> None:
"""
Callback when strategy is started.
"""
self.write_log("策略启动")
def on_stop(self) -> None:
"""
Callback when strategy is stopped.
"""
self.write_log("策略停止")
def on_tick(self, tick: TickData) -> None:
"""
Callback of new tick data update.
"""
super().on_tick(tick)
self.rsi_signal.on_tick(tick)
self.cci_signal.on_tick(tick)
self.ma_signal.on_tick(tick)
self.calculate_target_pos()
def on_bar(self, bar: BarData) -> None:
"""
Callback of new bar data update.
"""
super().on_bar(bar)
self.rsi_signal.on_bar(bar)
self.cci_signal.on_bar(bar)
self.ma_signal.on_bar(bar)
self.calculate_target_pos()
def calculate_target_pos(self) -> None:
""""""
self.signal_pos["rsi"] = self.rsi_signal.get_signal_pos()
self.signal_pos["cci"] = self.cci_signal.get_signal_pos()
self.signal_pos["ma"] = self.ma_signal.get_signal_pos()
target_pos: int = 0
for v in self.signal_pos.values():
target_pos += v
self.set_target_pos(target_pos)
def on_order(self, order: OrderData) -> None:
"""
Callback of new order data update.
"""
super().on_order(order)
def on_trade(self, trade: TradeData) -> None:
"""
Callback of new trade data update.
"""
self.put_event()
def on_stop_order(self, stop_order: StopOrder) -> None:
"""
Callback of stop order update.
"""
pass
@@ -0,0 +1,138 @@
from vnpy_ctastrategy import (
CtaTemplate,
StopOrder,
TickData,
BarData,
TradeData,
OrderData,
BarGenerator,
ArrayManager,
)
class MultiTimeframeStrategy(CtaTemplate):
""""""
author = "用Python的交易员"
rsi_signal: int = 20
rsi_window: int = 14
fast_window: int = 5
slow_window: int = 20
fixed_size: int = 1
rsi_value: float = 0
rsi_long: float = 0
rsi_short: float = 0
fast_ma: float = 0
slow_ma: float = 0
ma_trend: float = 0
parameters = ["rsi_signal", "rsi_window",
"fast_window", "slow_window",
"fixed_size"]
variables = ["rsi_value", "rsi_long", "rsi_short",
"fast_ma", "slow_ma", "ma_trend"]
def on_init(self) -> None:
"""
Callback when strategy is inited.
"""
self.write_log("策略初始化")
self.rsi_long: float = 50 + self.rsi_signal
self.rsi_short: float = 50 - self.rsi_signal
self.bg5: BarGenerator = BarGenerator(self.on_bar, 5, self.on_5min_bar)
self.am5: ArrayManager = ArrayManager()
self.bg15: BarGenerator = BarGenerator(self.on_bar, 15, self.on_15min_bar)
self.am15: ArrayManager = ArrayManager()
self.load_bar(10)
def on_start(self) -> None:
"""
Callback when strategy is started.
"""
self.write_log("策略启动")
def on_stop(self) -> None:
"""
Callback when strategy is stopped.
"""
self.write_log("策略停止")
def on_tick(self, tick: TickData) -> None:
"""
Callback of new tick data update.
"""
self.bg5.update_tick(tick)
def on_bar(self, bar: BarData) -> None:
"""
Callback of new bar data update.
"""
self.bg5.update_bar(bar)
self.bg15.update_bar(bar)
def on_5min_bar(self, bar: BarData) -> None:
""""""
self.cancel_all()
self.am5.update_bar(bar)
if not self.am5.inited:
return
if not self.ma_trend:
return
self.rsi_value = self.am5.rsi(self.rsi_window)
if self.pos == 0:
if self.ma_trend > 0 and self.rsi_value >= self.rsi_long:
self.buy(bar.close_price + 5, self.fixed_size)
elif self.ma_trend < 0 and self.rsi_value <= self.rsi_short:
self.short(bar.close_price - 5, self.fixed_size)
elif self.pos > 0:
if self.ma_trend < 0 or self.rsi_value < 50:
self.sell(bar.close_price - 5, abs(self.pos))
elif self.pos < 0:
if self.ma_trend > 0 or self.rsi_value > 50:
self.cover(bar.close_price + 5, abs(self.pos))
self.put_event()
def on_15min_bar(self, bar: BarData) -> None:
""""""
self.am15.update_bar(bar)
if not self.am15.inited:
return
self.fast_ma = self.am15.sma(self.fast_window)
self.slow_ma = self.am15.sma(self.slow_window)
if self.fast_ma > self.slow_ma:
self.ma_trend = 1
else:
self.ma_trend = -1
def on_order(self, order: OrderData) -> None:
"""
Callback of new order data update.
"""
pass
def on_trade(self, trade: TradeData) -> None:
"""
Callback of new trade data update.
"""
self.put_event()
def on_stop_order(self, stop_order: StopOrder) -> None:
"""
Callback of stop order update.
"""
pass
@@ -0,0 +1,160 @@
from vnpy_ctastrategy import (
CtaTemplate,
StopOrder,
Direction,
TickData,
BarData,
TradeData,
OrderData,
BarGenerator,
ArrayManager,
)
class TurtleSignalStrategy(CtaTemplate):
""""""
author = "用Python的交易员"
entry_window: int = 20
exit_window: int = 10
atr_window: int = 20
fixed_size: int = 1
entry_up: float = 0
entry_down: float = 0
exit_up: float = 0
exit_down: float = 0
atr_value: float = 0
long_entry: float = 0
short_entry: float = 0
long_stop: float = 0
short_stop: float = 0
parameters = ["entry_window", "exit_window", "atr_window", "fixed_size"]
variables = ["entry_up", "entry_down", "exit_up", "exit_down", "atr_value"]
def on_init(self) -> None:
"""
Callback when strategy is inited.
"""
self.write_log("策略初始化")
self.bg: BarGenerator = BarGenerator(self.on_bar)
self.am: ArrayManager = ArrayManager()
self.load_bar(20)
def on_start(self) -> None:
"""
Callback when strategy is started.
"""
self.write_log("策略启动")
def on_stop(self) -> None:
"""
Callback when strategy is stopped.
"""
self.write_log("策略停止")
def on_tick(self, tick: TickData) -> None:
"""
Callback of new tick data update.
"""
self.bg.update_tick(tick)
def on_bar(self, bar: BarData) -> None:
"""
Callback of new bar data update.
"""
self.cancel_all()
self.am.update_bar(bar)
if not self.am.inited:
return
# Only calculates new entry channel when no position holding
if not self.pos:
self.entry_up, self.entry_down = self.am.donchian(
self.entry_window
)
self.exit_up, self.exit_down = self.am.donchian(self.exit_window)
if not self.pos:
self.atr_value = self.am.atr(self.atr_window)
self.long_entry = 0
self.short_entry = 0
self.long_stop = 0
self.short_stop = 0
self.send_buy_orders(self.entry_up)
self.send_short_orders(self.entry_down)
elif self.pos > 0:
self.send_buy_orders(self.entry_up)
sell_price: float = max(self.long_stop, self.exit_down)
self.sell(sell_price, abs(self.pos), True)
elif self.pos < 0:
self.send_short_orders(self.entry_down)
cover_price: float = min(self.short_stop, self.exit_up)
self.cover(cover_price, abs(self.pos), True)
self.put_event()
def on_trade(self, trade: TradeData) -> None:
"""
Callback of new trade data update.
"""
if trade.direction == Direction.LONG:
self.long_entry = trade.price
self.long_stop = self.long_entry - 2 * self.atr_value
else:
self.short_entry = trade.price
self.short_stop = self.short_entry + 2 * self.atr_value
def on_order(self, order: OrderData) -> None:
"""
Callback of new order data update.
"""
pass
def on_stop_order(self, stop_order: StopOrder) -> None:
"""
Callback of stop order update.
"""
pass
def send_buy_orders(self, price: float) -> None:
""""""
t: float = self.pos / self.fixed_size
if t < 1:
self.buy(price, self.fixed_size, True)
if t < 2:
self.buy(price + self.atr_value * 0.5, self.fixed_size, True)
if t < 3:
self.buy(price + self.atr_value, self.fixed_size, True)
if t < 4:
self.buy(price + self.atr_value * 1.5, self.fixed_size, True)
def send_short_orders(self, price: float) -> None:
""""""
t: float = self.pos / self.fixed_size
if t > -1:
self.short(price, self.fixed_size, True)
if t > -2:
self.short(price - self.atr_value * 0.5, self.fixed_size, True)
if t > -3:
self.short(price - self.atr_value, self.fixed_size, True)
if t > -4:
self.short(price - self.atr_value * 1.5, self.fixed_size, True)
+20 -1
View File
@@ -1,7 +1,7 @@
"""Tests for sanguo_api.strategy_registry (Task S1.3)."""
from sanguo_api.strategy_registry import (
list_strategies, strategy_params, STRATEGY_NAMES,
list_strategy_files, read_strategy_file,
list_strategy_files, read_strategy_file, get_strategy_class,
)
@@ -57,3 +57,22 @@ def test_read_strategy_file_returns_code():
content = read_strategy_file(f["name"])
assert "code" in content and isinstance(content["code"], str)
assert content["class_name"] == f["class_name"]
def test_vnpy_builtin_copies_classified_cta():
"""灌入的 vnpy 内置模板(from vnpy_ctastrategy import ...)必须判成 cta。"""
data = list_strategy_files()
cta_files = {f["name"]: f for f in data["files"] if f["dir"] == "sanguo_trader/strategy/"}
assert "double_ma_strategy.py" in cta_files, "内置策略模板未灌入 sanguo_trader/strategy"
assert cta_files["double_ma_strategy.py"]["type"] == "cta"
assert cta_files["double_ma_strategy.py"]["class_name"] == "DoubleMaStrategy"
def test_get_strategy_class_self_owned_priority_or_fallback():
"""有 vnpy_ctastrategy 环境:自研目录类可加载;无:优雅返 None/降级不崩。"""
cls = get_strategy_class("DoubleMaStrategy")
# 本机 dev 无 vnpy_ctastrategy 时 pip 侧也拿不到 → None 不崩即可;
# 容器内应返回 sanguo_trader.strategy.double_ma_strategy 的类(自研优先)
if cls is not None:
assert cls.__name__ == "DoubleMaStrategy"
assert cls.__module__.startswith("sanguo_trader.strategy")