债券双均线交易策略
X-Bond 交易围绕债券买卖报价、日内价格变化与持仓风险展开,交易者通过行情判断、头寸约束与止损机制管理日内敞口。本文先阐述策略的基本思路,再以双均线驱动的日内交易案例,说明数据准备、交易逻辑与回测分析方法。
1. 策略介绍
债券交易需统筹报价策略与持仓管理:买卖价差提供潜在收益,持仓则使交易者暴露于价格波动风险。行情判断决定交易倾向,风险约束决定可承担的头寸规模,二者共同影响交易决策。
1.1 核心因子
设第 k 个有效收盘样本为 Ck,快慢均线分别采用 10 个和 100 个样本计算移动平均线:
,
当有效样本数达到 100 个后,开始更新均线。首次获得有效的快慢均线值时,仅保存当前均线值作为前值,不产生交叉信号。
-
多头信号:前一次快线不高于慢线,本次快线高于慢线,且最新收盘价不低于慢线加 0.005。
-
空头信号:前一次快线不低于慢线,本次快线低于慢线,且最新收盘价不高于慢线减 0.005。
其中,0.005 表示以每百元债券报价为单位的绝对价格差值,并非收益率或百分比。
均线计算所使用的历史样本跨交易日保留,不会在每个交易日重新预热。行情按来源分组后,同一分钟可能产生多条 bar,而回调没有区分行情来源。因此,均线窗口严格来说分别为最近 10 个和 100 个回调样本;只有在每分钟恰好收到一条有效 bar 且数据连续的情况下,才分别对应 10 分钟和 100 分钟。
1.2 交易标的
本文案例所用的交易标的是银行间债券 250210。引擎使用的证券代码为 250210.T1,订单来源固定为 X-Bond。策略支持多空交易,并配置了债券借贷功能。订单数量按脚本中的盈亏公式采用面额口径,价格按每百元面额报价理解;实际使用时,需核对部署版本中债券数量的定义及相关计算口径。标的基本信息由脚本硬编码,不会自动从证券主数据中读取。
策略使用的信号价格并非直接来自成交分钟线,而是由快照行情构造:快照中的买卖一价均为正数时,取两者的中间价,否则取两者中的较大值,再聚合为一分钟 OHLC 数据。成交消息仅用于更新最近成交价,该价格用于估算快照中的浮动盈亏。
1.3 交易规则
策略按以下顺序处理快照、信号与日终平仓:
-
收到快照时,读取买卖一价和引擎当前持仓,以多头数量减空头持仓数量,计算净头寸。
-
当单日估算已实现盈亏与浮盈之和不大于 -10 万元时,触发当日停机;当累计估算已实现盈亏不大于 -100 万元时,触发永久停机。累计盈亏条件不包含当前浮动盈亏。
-
若已触发停机,则先提交平掉全部多头和空头持仓的订单,再结束本次快照回调。
-
未停机时,仅在 09:00:00 至 12:00:00(含端点)以及 13:30:00 至 16:25:00(不含端点)的时段内执行正常交易逻辑。午间休市及其他非交易时段,尚未消费的信号会继续保留。
-
收到多头信号时,先提交平空订单,再按卖一价提交开多订单;收到空头信号时,先提交平多订单,再按买一价提交开空订单。开仓数量固定为 5,000 万,提交前检查推算后的净头寸是否处于 -5 亿至 5 亿的范围内。
-
日终回调使用最近保存的买一价和卖一价,为全部多头和空头持仓提交平仓订单。
信号由 onBar 回调根据一分钟 bar 生成,订单在快照回调提交。同一次交叉信号不会在每次快照到达时重复触发开仓:信号在消费前会被清零。若未出现新的交叉,onBar 不会清除已有信号;若出现新的交叉,则新信号可以覆盖旧信号。需要注意,触发止损后,策略不会立即清除已有信号,而是优先执行停机分支中的平仓逻辑。永久停机后,正常交易分支不再执行。
2. 策略回测
本案例的回测输入由深度快照、快照聚合的一分钟 bar 和债券成交三类消息组成。回测前需要导入附件 CSV 文件,并加载 Backtest 和 MatchingEngineSimulator 插件。本案例已在 DolphinDB Server 3.00.6.1、Backtest 3.00.6.9 与 MatchingEngineSimulator 3.00.6.9 上运行。服务器需要安装与 DolphinDB Server 版本匹配的插件;创建引擎前,脚本会检查并加载所需插件。
2.1 准备数据
本案例使用附录提供的三份模拟数据。先将配套附件保存到 DolphinDB 服务器可读取的目录,再将路径变量修改为对应 CSV 文件在服务器上的实际路径。
snapshotCsvPath = "xbondsnapshot.csv"
ohlcCsvPath = "xbond_ohlc.csv"
transactionCsvPath = "xbond_transaction.csv"
snapshotSchema = table(["symbol", "timestamp", "datetime", "bidQty", "bidPrice", "askQty", "askPrice", "source", "preSettlementPrice", "prevPrice", "openPrice", "closePrice", "settlementPrice", "HighPrice", "LowPrice", "lastPrice", "volume", "turnover", "openInterest", "askYTM", "lastYTM", "bidYTM"] as name, ["STRING", "LONG", "TIMESTAMP", "LONG[]", "DOUBLE[]", "LONG[]", "DOUBLE[]", "STRING", "DOUBLE", "DOUBLE", "DOUBLE", "DOUBLE", "DOUBLE", "DOUBLE", "DOUBLE", "DOUBLE", "LONG", "DOUBLE", "LONG", "DOUBLE[]", "DOUBLE", "DOUBLE[]"] as type)
ohlcSchema = table(["symbol", "timestamp", "datetime", "source", "period", "priceType", "open", "high", "low", "close", "volume", "openYTM", "highYTM", "lowYTM", "closeYTM"] as name, ["STRING", "LONG", "TIMESTAMP", "STRING", "INT", "INT", "DOUBLE", "DOUBLE", "DOUBLE", "DOUBLE", "LONG", "DOUBLE", "DOUBLE", "DOUBLE", "DOUBLE"] as type)
transactionSchema = table(["timestamp", "symbol", "source", "qty", "price", "ytm", "openInterest", "turnover", "openClose", "marketOrderId", "direction"] as name, ["TIMESTAMP", "STRING", "STRING", "LONG", "DOUBLE", "DOUBLE", "LONG", "DOUBLE", "INT", "INT", "INT"] as type)
snapshot = loadText(snapshotCsvPath, ",", snapshotSchema, containHeader=true, arrayDelimiter=",")
ohlc = loadText(ohlcCsvPath, ",", ohlcSchema, containHeader=true)
txnMsg = loadText(transactionCsvPath, ",", transactionSchema, containHeader=true)
快照 CSV 中的买卖数量和价格字段以数组形式存储,导入时分别指定为 LONG[] 和 DOUBLE[]。价格按每百元面额报价,数量按本案例的模拟面额口径,收益率按百分数形式提供,并非根据债券价格通过定价模型反算得到。快照和一分钟 bar 数据均包含 symbol、source 和 datetime 字段,可据此识别对应的标的、行情来源和时间;成交数据则通过 symbol、source 和 timestamp 识别记录。上述字段用于标识数据记录,不代表已在表中建立数据库主键约束。
| 字段 | 类型 |
|---|---|
| symbol | STRING |
| timestamp | LONG |
| datetime | TIMESTAMP |
| bidQty | LONG[] |
| bidPrice | DOUBLE[] |
| askQty | LONG[] |
| askPrice | DOUBLE[] |
| source | STRING |
| preSettlementPrice | DOUBLE |
| prevPrice | DOUBLE |
| openPrice | DOUBLE |
| closePrice | DOUBLE |
| settlementPrice | DOUBLE |
| HighPrice | DOUBLE |
| LowPrice | DOUBLE |
| lastPrice | DOUBLE |
| volume | LONG |
| turnover | DOUBLE |
| openInterest | LONG |
| askYTM | DOUBLE[] |
| lastYTM | DOUBLE |
| bidYTM | DOUBLE[] |
每根一分钟 bar 的 OHLC 均根据该分钟内的两条快照数据生成,volume 是该分钟内的快照条数,最后一根下午 bar 的时间为 16:30:00。
| 字段 | 类型 |
|---|---|
| symbol | STRING |
| timestamp | LONG |
| datetime | TIMESTAMP |
| source | STRING |
| period | INT |
| priceType | INT |
| open | DOUBLE |
| high | DOUBLE |
| low | DOUBLE |
| close | DOUBLE |
| volume | LONG |
| openYTM | DOUBLE |
| highYTM | DOUBLE |
| lowYTM | DOUBLE |
| closeYTM | DOUBLE |
成交消息比对应快照晚 1 秒,成交数量固定为 100 万,成交价格取对应快照的买卖一档中间价,成交金额为 qty * price / 100。openInterest 与 openClose 均设为 0,direction=1 为模拟方向标签,marketOrderId 为递增 INT 编号。上述成交消息用于模拟市场成交行情,不代表策略订单的实际成交记录。
| 字段 | 类型 |
|---|---|
| timestamp | TIMESTAMP |
| symbol | STRING |
| source | STRING |
| qty | LONG |
| price | DOUBLE |
| ytm | DOUBLE |
| openInterest | LONG |
| turnover | DOUBLE |
| openClose | INT |
| marketOrderId | INT |
| direction | INT |
2.2 编写策略逻辑
策略由均线信号计算、初始化、盘前处理、分钟行情处理、快照交易处理、成交行情处理和盘后处理组成。分钟行情回调用于更新均线并生成交易信号;快照回调负责读取盘口、检查停机条件和交易时段,并根据待处理信号提交订单。成交行情回调仅更新最近成交价,不用于确认策略订单的成交结果。
均线信号函数
calcMaSignal
参数 closes 为 DOUBLE 向量,lastPx 与 entryFilter 为数值标量,prevFast 与 prevSlow 分别表示前一次计算得到的快、慢均线值,初始时可以为 NULL。由 onBar 调用,当有效收盘价样本不足 100 个时,函数不计算均线,并返回原有均线值;样本数达到 100 个后,计算最近 10 个和 100 个样本的均值。只有在前一次均线值已初始化的情况下,函数才判断金叉或死叉,并结合最新收盘价与慢均线之间的价差过滤信号。函数返回包含信号标志和更新后均线值的 dict(STRING, ANY) 字典。
def calcMaSignal(closes, lastPx, entryFilter, prevFast, prevSlow){
out = dict(STRING, ANY)
out["golden"] = false
out["death"] = false
out["prevFast"] = prevFast
out["prevSlow"] = prevSlow
if (size(closes) < 100) return out
fast = avg(tail(closes, 10))
slow = avg(tail(closes, 100))
if (!isNull(prevFast) and !isNull(prevSlow)){
if (prevFast <= prevSlow and fast > slow and lastPx >= slow + entryFilter) out["golden"] = true
if (prevFast >= prevSlow and fast < slow and lastPx <= slow - entryFilter) out["death"] = true
}
out["prevFast"] = fast
out["prevSlow"] = slow
return out
}
初始化回调
initialize
唯一形参 context 为可变 DICTIONARY。引擎初始化时,回调将交易标的、单笔开仓数量、净持仓限额、盈亏停机阈值、信号过滤参数以及行情缓存、均线历史、盈亏估算值和停机标志等状态写入上下文。当前代码未创建独立的日志表。
def initialize(mutable context){
context["tradeSym"] = "250210.T1"
context["Lot"] = 50000000l
context["MaxPos"] = 500000000l
context["dayStop"] = -100000.0
context["cumStop"] = -1000000.0
context["filter"] = 0.005
context["sl"] = 0.10
context["tp"] = 0.20
context["bid1"] = NULL
context["ask1"] = NULL
context["lastPx"] = NULL
context["entryPx"] = NULL
context["closes"] = array(DOUBLE, 0)
context["prevFast"] = NULL
context["prevSlow"] = NULL
context["sig"] = 0
context["dayPnl"] = 0.0
context["cumPnl"] = 0.0
context["dayHalt"] = false
context["dead"] = false
}
盘前回调
beforeTrading
唯一形参 context 为可变 DICTIONARY。每日开盘前,回调将当日盈亏估算值清零,重置当日停机标志和待处理信号,并清空买一价、卖一价缓存。均线历史、前次均线值、最近成交价、入场价格锚点、累计盈亏估算值和永久停机标志均予以保留。
def beforeTrading(mutable context){
context["dayPnl"] = 0.0
context["dayHalt"] = false
context["sig"] = 0
context["bid1"] = NULL
context["ask1"] = NULL
}
分钟行情回调
onBar
context 为可变 DICTIONARY,data 为引擎传入。回调从引擎传入的 bar 记录中读取 close,更新最近价格并追加到收盘价历史向量,再调用 calcMaSignal 计算均线和交叉信号。计算结果用于更新上下文中的前次均线值及待处理信号。该回调只生成信号,不直接提交订单。
def onBar(mutable context, data){
context["lastPx"] = data.close
context["closes"].append!(context["lastPx"])
s = calcMaSignal(context["closes"], context["lastPx"], context["filter"], context["prevFast"], context["prevSlow"])
context["prevFast"] = s["prevFast"]
context["prevSlow"] = s["prevSlow"]
if (s["golden"]) context["sig"] = 1
if (s["death"]) context["sig"] = -1
}
成交行情回调
onTransaction
context 为可变 DICTIONARY,data 为成交记录。回调从成交记录中读取 price,更新上下文中的最近成交价。该价格用于后续浮盈估算;由于回调处理的是市场成交消息,而非策略订单成交确认,因此不会据此维护策略订单的实际成交成本或已实现盈亏。
def onTransaction(mutable context, data){
context["lastPx"] = data.price
}
快照交易回调
onSnapshot
context 为可变 DICTIONARY,data 为快照记录。回调读取快照中的买卖一档价格并更新盘口缓存,然后查询当前多空持仓,估算浮动盈亏,并检查当日及累计盈亏停机条件。若触发当日或累计盈亏停机条件,则提交现有多空持仓的平仓订单并返回;否则,继续检查交易时段,并根据待处理的均线信号决定是否提交订单。
当日浮动盈亏根据最近成交价 lastPx、入场价格锚点 entryPx 和当前净持仓估算。若当日盈亏估算值与浮动盈亏之和不高于 dayStop,则触发当日停机;若累计盈亏估算值不高于 cumStop,则触发永久停机。触发停机后,回调会尝试为现有多空持仓提交平仓订单,并清空 entryPx。由于清空该变量和提交平仓订单均不代表订单已成交,实际持仓是否归零仍取决于撮合结果。
def onSnapshot(mutable context, data){
bid1 = data.bidPrice[0]
ask1 = data.askPrice[0]
context["bid1"] = bid1
context["ask1"] = ask1
sym = context["tradeSym"]
lot = context["Lot"]
t = second(data.datetime)
lastPx = context["lastPx"]
pos = Backtest::getPosition(context["engine"], sym)
longQ = long(pos.longPosition)
shortQ = long(pos.shortPosition)
net = longQ - shortQ
upnl = 0.0
if (net > 0 and !isNull(context["entryPx"])) upnl = (lastPx - context["entryPx"]) * double(net) / 100.0
if (net < 0 and !isNull(context["entryPx"])) upnl = (context["entryPx"] - lastPx) * double(-net) / 100.0
if (context["dayPnl"] + upnl <= context["dayStop"]) context["dayHalt"] = true
if (context["cumPnl"] <= context["cumStop"]) context["dead"] = true
// 强平:止损停机
if (context["dead"] or context["dayHalt"]){
if (longQ > 0){
Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", longQ, 5, 2, 2, bid1)), "强平")
if (!isNull(context["entryPx"])) {
pnl = (bid1 - context["entryPx"]) * double(longQ) / 100.0
context["dayPnl"] += pnl
context["cumPnl"] += pnl
}
}
if (shortQ > 0){
Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", shortQ, 5, 1, 2, ask1)), "强平")
if (!isNull(context["entryPx"])) {
pnl = (context["entryPx"] - ask1) * double(shortQ) / 100.0
context["dayPnl"] += pnl
context["cumPnl"] += pnl
}
}
context["entryPx"] = NULL
return
}
if (!((t >= 09:00:00 and t <= 12:00:00) or (t >= 13:30:00 and t < 16:25:00))) return
// 均线信号:先平反向,再开 Lot
sig = context["sig"]
if (sig == 0) return
context["sig"] = 0
if (sig == 1){
if (shortQ > 0){
Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", shortQ, 5, 1, 2, ask1)), "平空")
if (!isNull(context["entryPx"])) {
pnl = (context["entryPx"] - ask1) * double(shortQ) / 100.0
context["dayPnl"] += pnl
context["cumPnl"] += pnl
}
context["entryPx"] = NULL
net = longQ
}
if (net + lot <= context["MaxPos"]){
Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", lot, 5, 1, 1, ask1)), "开多")
context["entryPx"] = ask1
}
}
if (sig == -1){
if (longQ > 0){
Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", longQ, 5, 2, 2, bid1)), "平多")
if (!isNull(context["entryPx"])) {
pnl = (bid1 - context["entryPx"]) * double(longQ) / 100.0
context["dayPnl"] += pnl
context["cumPnl"] += pnl
}
context["entryPx"] = NULL
net = -shortQ
}
if (net - lot >= -context["MaxPos"]){
Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", lot, 5, 2, 1, bid1)), "开空")
context["entryPx"] = bid1
}
}
}
盘后回调
afterTrading
唯一形参 context 为可变 DICTIONARY。回调查询当前多空持仓,并使用上下文中缓存的买一价和卖一价提交平仓订单,随后清空入场价格锚点。该回调不等待订单成交,也不更新当日或累计盈亏估算值,因此盘后提交平仓订单并不代表持仓已经全部平掉。
def afterTrading(mutable context){
sym = context["tradeSym"]
pos = Backtest::getPosition(context["engine"], sym)
if (long(pos.longPosition) > 0)
Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", long(pos.longPosition), 5, 2, 2, context["bid1"])), "收盘")
if (long(pos.shortPosition) > 0)
Backtest::submitOrder(context["engine"], dict(`symbol`source`qty`orderType`direction`openClose`price, (sym, "XBOND", long(pos.shortPosition), 5, 1, 2, context["ask1"])), "收盘")
context["entryPx"] = NULL
}
本案例中的多头浮盈按 (lastPx - entryPx) * net / 100 估算,空头浮盈按反向价差乘以空头面额再除以 100 估算。平仓盈亏估算分别使用买一价或卖一价。上述估算未扣除佣金、债券借贷费及其他交易成本,且其准确性取决于入场价格锚点与实际持仓是否一致。
2.3 设置回测参数
以下配置包括标的池、数据读取范围和回测引擎参数。universe 是长度为 1 的 ANY 向量,其元素为交易标的;数据库查询使用不含结算后缀的 SYMBOL 向量 codes,而标的池和债券基础信息使用带后缀的代码。迁移至其他标的时,需同步修改相关配置和基础信息。
// 设置标的池
universe = array(ANY, 1)
universe[0] = ["250210.T1"]
// 设置回测日期、数据读取时段及引擎参数
begt = 2026.05.18
endt = 2026.05.29
config = dict(STRING, ANY)
config["startDate"]=begt;
config["endDate"]=endt;
config["strategyGroup"]="FICC"
config["cash"]=1e10
config["dataType"]=3
config["matchingMode"]=3
config["universe"]=universe
config["enableBondLending"]=true
config["outputOrderInfo"]=true
-
begt、endt:DATE 标量,分别为数据查询和回测的起始日期与结束日期。本例设置为 2026 年 5 月 18 日至 2026 年 5 月 29 日。
-
codes:SYMBOL 向量,用于筛选数据库中的债券数据。本例仅包含 250210。
-
startDate、endDate:DATE,分别引用 begt 和 endt,确保回测日期与数据查询范围一致。
-
strategyGroup:STRING,表示策略分组标识,本例设置为 “FICC”。
-
cash:DOUBLE,初始资金,设置为 100 亿元。
-
dataType:INT,行情数据类型配置。本例设置为 3,其具体含义应以当前 Backtest 版本的接口定义为准。
-
matchingMode:INT,撮合模式配置。本例设置为 3(表示见价成交模式,此模式不使用模拟撮合引擎,而是回测本身立即成交)。具体成交规则应以当前 Backtest 版本的接口定义为准。
-
universe:标的池,ANY 向量,本例仅包含 250210.T1 标的组。
-
enableBondLending:BOOL,设置为 true,表示允许债券 openClose 为 2。
-
outputOrderInfo:BOOL,设置为 true,表示在订单交易明细中输出风控日志。
基础信息表完整配置如下:
bondRef = table(1:0,
`symbol`multiplier`priceTick`couponRate`frequency`valueDate`maturityDate`bondLendingRate`commission`marginRatio`bondType`issuePrice`faceValue,
[STRING,DOUBLE,DOUBLE,DOUBLE,INT,DATE,DATE,DOUBLE,DOUBLE,DOUBLE,STRING,DOUBLE,DOUBLE])
insert into bondRef values("250210.T1",100.0,0.0001,1.80,1,2025.04.02,2035.04.02,0.0,0.0001,0.0,"FixedRateBond",100.0,100.0)
securityReference = {"xbond": bondRef}
2.4 创建回测引擎
将前文定义的回调函数注册到 callbacks 字典中,再调用 Backtest::createBacktester 创建回测引擎。创建前,脚本会尝试删除同名引擎,因此在同一节点上运行多个回测任务时,应使用不同的引擎名称,避免相互干扰。
// 注册策略回调
callbacks = dict(STRING, ANY)
callbacks["initialize"]=initialize
callbacks["beforeTrading"]=beforeTrading
callbacks["onSnapshot"]=onSnapshot
callbacks["onBar"]=onBar
callbacks["onTransaction"]=onTransaction
callbacks["afterTrading"]=afterTrading
// 创建回测引擎
strategyName = "XBOND_intraday2"
try{ Backtest::dropBacktestEngine(strategyName) }catch(ex){}
go
engine = Backtest::createBacktester(strategyName, config, callbacks, false, securityReference)
createBacktester 的第四个参数设置为 false,表示本例采用非 JIT 模式;securityReference 用于提供债券基础信息。
2.5 执行回测
将 2.1 节准备好的 snapshot、ohlc、transaction 数据传入 msg 字典,并通过 Backtest::appendQuotationMsg 提交给回测引擎。
所有行情提交完成后,调用 Backtest::appendEndMarker 发送结束标记,以结束行情输入并触发盘后处理。
msg = dict(STRING, ANY)
msg["snapshot"] = snapshot
msg["ohlc"] = ohlc
msg["transaction"] = txnMsg
Backtest::appendQuotationMsg(engine, msg)
Backtest::appendEndMarker(engine)
2.6 获取回测结果
行情提交结束后,通过以下接口查询回测交易明细、收益统计、每日组合权益和当前持仓,并将结果保存到会话变量中。
tradeDetails = select * from Backtest::getTradeDetails(engine)
returnSummary = Backtest::getReturnSummary(engine)
TotalPortfolios = Backtest::getDailyTotalPortfolios(engine)
//print(TotalPortfolios)
position = Backtest::getPosition(engine)
各接口的详细说明参见 Backtest 结果查询接口。
3. 回测结果分析
在上述软件版本和非 JIT 模式下,使用附录提供的模拟数据进行回测。本次回测处理 8,640 条行情记录,初始权益为 10,000,000,000.00 元,区间总收益为 -1.0417%。
由于输入数据为模拟数据,且回测结果受行情生成方式、成交价格假设和成本配置影响,这些指标仅用于说明策略在本案例数据上的运行结果,不能据此评价策略在真实市场中的表现。
| 指标 | 本次结果 | 解读 |
|---|---|---|
| 区间总收益 | -1.0417% | 期末权益相对初始资金的累计变化 |
| 年化收益 | -23.1929% | 引擎按回测日收益年化后的结果 |
| 年化波动率 | 0.8445% | 价格路径波动幅度 |
| 夏普比率 | -27.4619 | 去掉风险波动后的收益 |
| 最大回撤 | 1.0417% | 期内净值相对先前高点的最大下跌 |
| 回撤比率 | -22.2655 | 引擎返回的收益与回撤相关统计 |
| 日胜率 | 0% | 每日收益为正的交易日占比 |
4. 总结
本案例以债券快照生成的一分钟价格序列计算双均线方向信号,并在快照回调中根据买卖一档价格提交开平仓订单。策略通过当日和累计盈亏阈值控制停机,并将均线历史、行情缓存、入场价格锚点及风险状态保存在上下文中,展示了从行情输入、信号生成、订单提交到回测结果查询的完整流程。
需要注意的是,策略中的盈亏和停机判断基于估算值,平仓订单提交也不代表订单已成交。将该示例用于真实历史数据研究前,应结合实际数据核对信号与行情的时间对应关系、成交价格和交易成本口径、未成交订单对持仓限额的影响,以及日终平仓订单的成交情况。
5. 附录
完整源脚本包含 CSV 导入、行情转换、回调、参数配置、引擎创建及结果查询。
