ARTICLE · 1329490
AiTrader 内置策略模板:参数校验、沙箱冒烟和一键建机器人
AiTrader 的“策略工场 → 策略模板”里放了 6 个现成的策略,其中网格、定投、趋势、均值回归 4 个可以直接建机器人,资金费套利和跨所价差 2 个只做机会扫描。咱们这篇看后端 strategy_template 模块是怎么把一份模板变成一个停止状态的机器人的,所以先从模板的数据结构说起。
1. 模板在后端是一个字典
新建的模板都写在 backend/app/plugin/module_quant/strategy_template/templates.py 里,每个模板包含名称、是否可执行、建议周期、风险等级、参数定义和策略源码。参数定义统一用 P() 生成,网格策略的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 "grid": { "name": "等差网格", "category": "grid", "executable": True, "timeframe": "15m", "risk": "中", "source_code": GRID, "params": [ P("lower_price", "区间下限", 0, min=0, desc="0 表示以启动价 -10% 自动设定"), P("upper_price", "区间上限", 0, min=0, desc="0 表示以启动价 +10% 自动设定"), P("grid_count", "网格数", 10, min=2, max=200, step=1), P("capital_pct_per_grid", "每格资金比例", 0.08, min=0.005, max=0.5, step=0.01), P("take_profit_grids", "反弹止盈格数", 2, min=1, max=50, step=1), P("close_out_of_range", "越界平仓", True, kind="bool"), ],}, 字段说明:executable 为 False 的模板没有 source_code,列表页会打上“机会扫描”的标签;min 和 max 既给前端的数字输入框用,也给后端校验用,所以只要在这里改一次,页面和接口会同时生效。
2. 页面截图
前端只有一个 strategy_template/Index.vue,6 张卡片来自 /strategy_template/list 接口,点开卡片会从右侧弹出抽屉,抽屉里的表单是按 params 循环生成的,不需要为每个模板单独写页面。


点“沙箱校验”之后,页面上显示“合成行情 400 根产生 13 个信号”,这是后端拿一段合成行情把策略真实跑了一遍的结果。
3. 参数校验 coerce_params
用户提交的参数不能直接塞给策略,不然网格数填 1 或者填个字母,策略跑起来才会报错。coerce_params 先取默认值,再逐项覆盖,超出范围的直接抛 ValueError,没定义过的键会被忽略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def coerce_params(key, values): out = template_defaults(key) for p in TEMPLATES[key]["params"]: if not values or p["key"] not in values or values[p["key"]] is None: continue v = values[p["key"]] if p["type"] == "bool": out[p["key"]] = v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes", "on") continue num = float(v) if p["min"] is not None and num < p["min"]: raise ValueError(f"参数 {p['label']} 不能小于 {p['min']}") if p["max"] is not None and num > p["max"]: raise ValueError(f"参数 {p['label']} 不能大于 {p['max']}") out[p["key"]] = int(num) if isinstance(p["default"], int) and float(num).is_integer() else num return out 我们在本地直接调用了一遍,结果如下:
- {"grid_count": 20, "close_out_of_range": "false", "foo": 1}: 返回 grid_count 为 20,close_out_of_range 为 False,foo 被丢掉,其余参数用默认值;
- {"grid_count": 1}: 报“参数 网格数 不能小于 2”;
- {"capital_pct_per_grid": 0.9}: 报“参数 每格资金比例 不能大于 0.5”。
4. 一键创建机器人
StrategyTemplateService.instantiate 做两件事:先在策略库里找一条名为“[模板] 等差网格”的策略,没有才新建,有就复用,不然同一个模板点几次会在策略库里堆出几份一样的源码;然后创建一个 CodeBasedStrategy 类型的机器人,默认 trading_mode="virtual",建好是停止状态,不会自己去下单。
1 2 3 4 5 6 bot = await QuantStrategyService(self.auth, self.db).create_strategy(QuantStrategyCreateSchema( name=data.name or f"{t['name']} {data.symbols[0]}", strategy_type="CodeBasedStrategy", strategy_id=lib_id, trading_mode=data.trading_mode, initial_capital=Decimal(str(data.initial_capital)), max_leverage=data.max_leverage, decide_interval=TIMEFRAME_SECONDS.get(tf, 3600), symbols=list(data.symbols), parameters=params, exchange_id=data.exchange_id, exchange_apikey_id=data.exchange_apikey_id, llm_provider=None,)) decide_interval 来自 TIMEFRAME_SECONDS,15m 对应 900 秒,1h 对应 3600 秒;llm_provider=None 表示这个机器人不调用大模型,纯按源码里的 on_bar 运行。
5. 两个只做扫描的模板
资金费套利和跨所价差需要同时在两条腿或两个账户下单,所以放在 scanners.py 里只读不下单。funding_opportunities 的成本按 4 腿 × 单边费率计算,我们给了三个假的费率试了一下:
XYZ/USDT:USDT:费率 0.0008,年化 87.6%,30 天净收益 6.8%,回本 1.67 天; ABC/USDT:USDT:费率 -0.0012,周期 4 小时,年化 262.8%,标记 needs_margin,因为要借币; BTC/USDT:USDT:费率 0.0001,年化 10.95%,回本 13.33 天,刚好过 10% 的默认门槛。
跨所价差用同样的思路,示例里 okx 卖一 100000、binance 买一 100090,毛价差 9 bps,扣掉双边 20 bps 手续费后净值是 -11 bps,所以 opportunity 为 null。截图里的扫描表需要联网访问交易所,沙箱里没有点。

6. 新增一个模板
在 templates.py 里增加一个源码字符串和一条 TEMPLATES 记录就可以,前端不用改,注意源码类要继承 BaseStrategy,并且只使用沙箱允许的函数,不然“沙箱校验”过不了。
#AiTrader #指标 #客户端 #Fastapi #Python #管理系统 #量化 #交易 #策略 #CCXT















































































