策略编写指南

Lite 策略语言参考,覆盖日 K 回测常用场景与 API 说明。

Lite 是云栖量化平台自研的回测策略语言,面向 A 股日线回测。策略参数写在代码顶部,回测引擎负责 T+1、100 股整手、佣金与滑点等规则。

语言概览

能力Lite 写法
回测参数config.* 配置块
策略逻辑on bar { ... }
全局跨日状态state.xxx
单标的独立状态local.xxx
固定股票池config.symbols
指数成分股池config.pool_index + config.max_pool
业绩基准自动对比上证综合指数 000001
手续费 / 滑点config.fee / config.slippage
信号复权config.fq(撮合仍用不复权价)
行情字段open / high / low / close / volume
跨标的引用data("sh.000001").close
技术指标ma / ema / rsi / macd_* / highest / lowest
运行时计算emar(key, val, span) 运行时EMA / prev_val(key, val, n) 任意值历史
涨停复制类指标limit_up_count / xys0 / hhjsjdb 等(见指标节)
按数量下单buy / sell
按金额 / 目标调仓buy_value / target / target_value
仓位 sizingpct_equity / cash(金额) / lots / equal_cash()
账户查询position / equity / cash() / avg_price / hold_bars

运行时刻:每个交易日,对股票池内每个标的依次触发 on bar;当日产生的委托在下一交易日开盘价撮合。

多标的同日下单:若多个标的在同一天发出买入,使用 equal_cash() 可在策略层表达「均分当日可用现金」;引擎在当日全部标的评估完毕后统一计算份额并下单(对标 PTrade 的 cash / len(candidates))。


语法要点

1
2
3
4
5
6
7
8
# 单行注释
// 单行注释

let x = ma(close, 10)          # 变量声明
if x > 0 and position("main") == 0 { ... }
if not local.flag { return }     # not / and / or

config.symbols = ["601857.SS"]   # 字符串与数组
  • 代码块用 { / }闭合 } 需与 if 同级缩进(子句多缩进 2 格)
  • 比较:>>=<<===
  • 逻辑:andornot

策略配置

配置项一览

配置项类型说明
config.title字符串策略名称(可选)
config.symbols数组固定股票池,如 ["600519.SS","601857.SS"]
config.pool_index字符串指数成分股池,如 "000300.SS"(沪深300)
config.max_pool数字成分股池上限,默认 400
config.start日期回测开始 YYYY-MM-DD
config.end日期回测结束 YYYY-MM-DD
config.capital数字初始资金
config.fee数字手续费(小数),0.0001 = 万分之一
config.slippage数字滑点 百分比,可选
config.pyramiding数字同标的同 id 最大加仓次数,1 = 不加仓
config.fq字符串信号取数复权:none / pre / post / dypre(推荐 dypre
config.order_volume数字策略内 order_volume 变量默认值(可选)

config.symbolsconfig.pool_index 二选一;使用 pool_index 时回测自动拉取成分股并排除基准指数。

写法示例

1
2
3
4
5
6
7
8
config.symbols = ["600519.SS", "601857.SS"]
config.start = "2021-01-01"
config.end = "2026-06-01"
config.capital = 1000000
config.fee = 0.0001      # 万分之一
config.slippage = 0      # 无滑点
config.pyramiding = 1    # 不加仓
config.fq = "dypre"      # 信号用动态前复权,撮合用不复权价

指数池:

1
2
3
4
5
config.pool_index = "000300.SS"
config.max_pool = 400
config.capital = 1000000
config.start = "2021-01-01"
config.end = "2026-06-01"

块写法:

1
2
3
4
5
6
7
config {
  symbols: ["600519.SS"]
  start: "2021-01-01"
  end: "2026-06-01"
  capital: 100000
  fee: 0.0001
}

事件入口

1
2
3
on bar {
  # 每个交易日、股票池内每个标的执行一次
}
  • 多标的时,on bar 对池中每个标的各执行一遍,共享同一账户与 state
  • 每个标的另有独立 local 对象,用于买卖标志等 per-symbol 状态

行情与数据

名称说明
open / high / low / close / volume当前标的、当前 Bar 的 OHLCV(受 config.fq 影响,用于信号)
symbol当前标的代码(如 sh.601857
date当前交易日 YYYY-MM-DD
bar_index当前 Bar 在全局交易日历中的索引
symbol_bars当前标的已有 K 线根数 − 1(跳过停牌日;用于「至少 N 根 K 线」过滤)
data("sh.000001").close引用其他标的同期行情
prev(close, 1)前 N 根 Bar 的 close / open / high / low(仅 OHLC 字段,不支持计算值)
计算值历史prev_val(key, value, n) — 支持任意表达式的历史值(见运行时计算节)
1
2
3
4
5
6
let idx_close = data("sh.000001").close
let ma60 = ma(data("sh.000001").close, 60)

if symbol_bars < 90 {
  return   # 上市/数据不足 90 根 K 线则跳过
}

技术指标

通用指标

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
ma(close, 10)
ema(close, 20)
rsi(close, 14)
highest(high, 20)
lowest(low, 20)

macd_dif(close, 12, 26)
macd_dea(close, 12, 26, 9)
macd_hist(close, 12, 26, 9)

ma(data("sh.000001").close, 60)

交叉

1
2
cross_up(fast, slow)     # 上穿
cross_down(fast, slow)   # 下穿

涨停复制 / 通达信类指标

以下函数为引擎预计算,Bar 内 O(1) 读取,适合性能敏感场景:

函数说明
limit_up_count(n)近 n 日涨停次数(收盘价较前一日涨幅 > 10% 计 1 次)
xys0()捕捞季节线变化率
xys1()xys0 的 2 日均线
xys2()xys0 的 1 日均线
hhjsjdb()智能辅助线 D 值
hhjsjdc()智能辅助线 5 日平滑

如需自行实现或修改公式,可用下方的 emar / prev_val 在策略代码中重写(见示例 9)。

指标在回测开始前向量化预计算,Bar 内 O(1) 读取。

运行时计算(对任意计算值)

以下函数不是预计算的,而是在 on bar 执行过程中对任意表达式计算,使用 key 字符串维护 per-symbol 内部状态:

函数说明示例
emar(key, value, span)value 做 span 期 EMA(运行时,非预计算)emar("wy1002", wy1001, 4)
prev_val(key, value, offset)存当前 value、取 offset 天前的值prev_val("hhjsjda", v, 5)

用途举例:

1
2
3
4
5
# 代替 hidden 的 xys0/xys1/xys2
let wy1001 = (2*close + high + low) / 4
let wy1002 = emar("wy1002", wy1001, 4)
let prev_wy1004 = prev_val("wy1004", wy1004, 1)
let xys0_v = (wy1004 - prev_wy1004) / prev_wy1004 * 100

注意:

  • key 必须是字符串字面量("xxx"),同一 key 在同一 Bar 多次调用只存一次
  • value 可以是 close、计算表达式(如 (close+high)/2)、或其他变量
  • offset=0 返回今天刚存的值,offset=1 返回昨天存的,依此类推
  • 数据不足 offset 时返回 NaN

下单与仓位

Sizing 函数(用于 buy / sell 的数量参数,或 buy_value 的金额参数)

函数含义示例
pct_equity(pct)按账户总权益的百分比换算股数(100 股整数倍)buy("main", pct_equity(90))
cash(金额)按指定金额换算股数buy("main", cash(50000))
lots(n)n 手(1 手 = 100 股)buy("main", lots(10))
equal_cash()占位符:参与当日「均分可用现金」队列(见下文)buy_value("main", equal_cash())
cash()账户可用现金(元),不是 sizingbuy_value("main", cash()) 全仓

注意:cash() 无参数 = 查账户现金;cash(50000) 带参数 = 按 5 万元换算股数。二者不同。

equal_cash() 行为说明

对标 PTrade:

1
order_value(security, context.portfolio.cash / len(candidates))

Lite 策略层写法:

1
2
3
buy_value("main", equal_cash())
# 或
buy("main", equal_cash())

引擎行为:

  1. 当日每个标的的 on bar 中,凡传入 equal_cash() 的买单先登记意图,不立即算具体股数
  2. 当日全部标的评估结束后,统计登记数 N,每只分配 可用现金 / N(预留手续费),向下取整至 100 股整数倍
  3. N = 1 时等价于 buy_value("main", cash()) 全仓
  4. 分配金额买不起 1 手的标的跳过;委托在下一交易日开盘撮合

按数量

1
2
3
4
5
6
7
buy("main", pct_equity(90))
buy("main", cash(50000))
buy("main", lots(10))
buy("main", equal_cash())       # 见 equal_cash 说明

sell("main")                    # 全部卖出
sell("main", lots(5))           # 卖出 5 手

按目标调仓

1
2
3
4
5
6
7
target("main", 0)               # 清仓
target("main", lots(10))         # 调仓至 1000 股

buy_value("main", cash())       # 用当前全部可用现金买入(单标的全仓)
buy_value("main", equal_cash()) # 多标的同日均分现金

target_value("main", 50000)     # 调仓至 5 万市值

持仓与账户

1
2
3
4
5
position("main")      # 当前标的、entry id 的持仓股数
equity()              # 账户总权益(现金 + 持仓市值)
cash()                # 可用现金(元)
avg_price("main")     # 持仓成本价
hold_bars("main")     # 当前持仓已持有 Bar 数(日历 Bar)

同一 id(如 "main")可区分不同逻辑仓位;pyramiding = 1 时已有同 id 持仓则忽略新买单。


状态:statelocal

对象作用域用途
state全局,跨标的、跨日持久组合级计数、择时标志
local单标的独立,跨日持久买卖标志、防重复下单
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
on bar {
  # 全局
  if state.day_count == null {
    state.day_count = 0
  }

  # 单标的(推荐多标的策略用 local 存 exit_pending、buy_flag 等)
  if not local.exit_pending and position("main") > 0 {
    sell("main")
    local.exit_pending = true
  }
  if position("main") == 0 {
    local.exit_pending = false
  }
}

多标的共用 state 时请加前缀或按业务拆分 key,避免冲突。


场景示例

1. 双均线 + 全仓买卖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
config.symbols = ["600570.SS"]
config.start = "2021-01-01"
config.end = "2026-06-01"
config.capital = 100000
config.fee = 0.0001

on bar {
  let fast = ma(close, 5)
  let slow = ma(close, 20)

  if cross_up(fast, slow) and position("main") == 0 {
    buy_value("main", cash())
  }
  if cross_down(fast, slow) and position("main") > 0 {
    target("main", 0)
  }
}

2. MACD 金叉死叉

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
config.symbols = ["600519.SS"]
config.start = "2020-01-01"
config.end = "2026-06-01"
config.capital = 100000
config.fee = 0.0003

on bar {
  let dif = macd_dif(close, 12, 26)
  let dea = macd_dea(close, 12, 26, 9)
  let pdif = prev(dif, 1)
  let pdea = prev(dea, 1)

  if dif > dea and pdif <= pdea and position("main") == 0 {
    buy_value("main", cash())
  }
  if dif < dea and pdif >= pdea and position("main") > 0 {
    target("main", 0)
  }
}

3. 指数过滤(大盘择时)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
config.symbols = ["601857.SS", "600570.SS"]
config.start = "2021-01-01"
config.end = "2026-06-01"
config.capital = 100000
config.fee = 0.0001

on bar {
  let fast = ma(close, 10)
  let slow = ma(close, 30)
  let idx_ma = ma(data("sh.000001").close, 60)

  if cross_up(fast, slow) and close > idx_ma {
    buy("main", pct_equity(90))
  }
  if cross_down(fast, slow) {
    sell("main")
  }
}

4. 多标的同日均分现金(推荐)

旧写法需手动估 pct_equity(45);现用 equal_cash() 由引擎按当日信号数自动均分:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
config.symbols = ["601857.SS", "600570.SS"]
config.start = "2021-01-01"
config.end = "2026-06-01"
config.capital = 100000
config.fee = 0.0001

on bar {
  let fast = ma(close, 10)
  let slow = ma(close, 30)

  if cross_up(fast, slow) and position("main") == 0 {
    buy_value("main", equal_cash())
  }
  if cross_down(fast, slow) and position("main") > 0 {
    target("main", 0)
  }
}

5. 持仓 N 日强制平仓

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
config.symbols = ["600519.SS"]
config.start = "2021-01-01"
config.end = "2026-06-01"
config.capital = 100000
config.fee = 0.0001

on bar {
  let fast = ma(close, 5)
  let slow = ma(close, 15)

  if cross_up(fast, slow) and position("main") == 0 {
    buy("main", pct_equity(90))
  }
  if cross_down(fast, slow) {
    sell("main")
  }
  if hold_bars("main") > 20 and position("main") > 0 {
    target("main", 0)
  }
}

6. RSI 超买超卖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
config.symbols = ["600519.SS"]
config.start = "2021-01-01"
config.end = "2026-06-01"
config.capital = 100000
config.fee = 0.0001

on bar {
  let r = rsi(close, 14)

  if r < 30 and position("main") == 0 {
    buy("main", pct_equity(90))
  }
  if r > 70 and position("main") > 0 {
    target("main", 0)
  }
}

7. 突破 N 日高点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
config.symbols = ["601857.SS"]
config.start = "2021-01-01"
config.end = "2026-06-01"
config.capital = 100000
config.fee = 0.0001

on bar {
  let hh = highest(high, 20)
  let prev_hh = prev(hh, 1)

  if close > prev_hh and position("main") == 0 {
    buy("main", pct_equity(90))
  }
  if close < ma(close, 10) and position("main") > 0 {
    target("main", 0)
  }
}

8. 指数池、专用指标与均分现金(写法片段)

以下为** API 组合示例**,非完整策略;具体选股与买卖条件请自行编写。

配置指数成分股池:

1
2
3
4
5
6
config.pool_index = "000300.SS"
config.max_pool = 400
config.fq = "dypre"
config.capital = 1000000
config.start = "2021-01-01"
config.end = "2026-06-01"

读取专用指标(片段):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
if symbol_bars < 90 {
  return
}

let lu = limit_up_count(10)
let x0 = xys0()
let x1 = xys1()
let x2 = xys2()
let db = hhjsjdb()
let dc = hhjsjdc()
# 在此组合你的条件,例如 lu > 0 and db > dc and ...

9. 自定义指标(emar / prev_val 用法)

用运行时计算函数在策略代码中自行实现指标,不依赖引擎预计算。以下示例等价于 xys0/xys1/xys2 + hhjsjdb/hhjsjdc 的涨停复制买入条件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
config.pool_index = "000300.SS"
config.max_pool = 400
config.capital = 1000000
config.start = "2021-01-01"
config.end = "2026-06-01"
config.fee = 0.0001
config.fq = "dypre"

on bar {
  if symbol_bars < 90 { return }

  # --- 捕捞季节 xys0/xys1/xys2 ---
  let wy1001 = (2*close + high + low) / 4
  let wy1002 = emar("wy1002", wy1001, 4)
  let wy1003 = emar("wy1003", wy1002, 4)
  let wy1004 = emar("wy1004", wy1003, 4)
  let prev_wy1004 = prev_val("wy1004", wy1004, 1)
  let xys0_v = 0
  if prev_wy1004 > 0 {
    xys0_v = (wy1004 - prev_wy1004) / prev_wy1004 * 100
  }
  let xys1_v = emar("xys1", xys0_v, 2)
  let xys2_v = xys0_v

  # --- 智能辅助线 hhjsjdb/hhjsjdc ---
  let hhjsjda = (3*close + open + low + high) / 6
  let w20 = 20*prev_val("hhjsjda", hhjsjda, 0)
  let w19 = 19*prev_val("hhjsjda", hhjsjda, 1)
  let w18 = 18*prev_val("hhjsjda", hhjsjda, 2)
  let w17 = 17*prev_val("hhjsjda", hhjsjda, 3)
  let w16 = 16*prev_val("hhjsjda", hhjsjda, 4)
  let w15 = 15*prev_val("hhjsjda", hhjsjda, 5)
  let w14 = 14*prev_val("hhjsjda", hhjsjda, 6)
  let w13 = 13*prev_val("hhjsjda", hhjsjda, 7)
  let w12 = 12*prev_val("hhjsjda", hhjsjda, 8)
  let w11 = 11*prev_val("hhjsjda", hhjsjda, 9)
  let w10 = 10*prev_val("hhjsjda", hhjsjda, 10)
  let w9 = 9*prev_val("hhjsjda", hhjsjda, 11)
  let w8 = 8*prev_val("hhjsjda", hhjsjda, 12)
  let w7 = 7*prev_val("hhjsjda", hhjsjda, 13)
  let w6 = 6*prev_val("hhjsjda", hhjsjda, 14)
  let w5 = 5*prev_val("hhjsjda", hhjsjda, 15)
  let w4 = 4*prev_val("hhjsjda", hhjsjda, 16)
  let w3 = 3*prev_val("hhjsjda", hhjsjda, 17)
  let w2 = 2*prev_val("hhjsjda", hhjsjda, 18)
  let w1 = 1*prev_val("hhjsjda", hhjsjda, 19)
  let hhjsjdb_v = (w20 + w19 + w18 + w17 + w16 + w15 + w14 + w13 + w12 + w11 + w10 + w9 + w8 + w7 + w6 + w5 + w4 + w3 + w2 + w1) / 210
  let hhjsjdc_v = emar("hhjsjdc", hhjsjdb_v, 5)

  if limit_up_count(10) > 0 and hhjsjdb_v > hhjsjdc_v and xys0_v > 0.5 and xys2_v > xys1_v and xys2_v > 0 {
    buy_value("main", equal_cash())
  }
}

多标的均分现金 + local 防重复卖单(片段):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
if position("main") > 0 {
  if not local.exit_pending {
    # 在此编写卖出条件
    # sell("main")
    # local.exit_pending = true
  }
  return
}
local.exit_pending = false

# 买入条件满足时:
# buy_value("main", equal_cash())

撮合与 A 股规则

  • T+1:当日买入的仓位当日不可卖出(卖单在撮合阶段检查可卖数量)
  • 整手:买卖数量向下取整至 100 股整数倍
  • 佣金:买卖双向收取,fee = 0.0001 表示万分之一
  • 滑点config.slippage 为百分比,买入加价、卖出减价
  • 加仓pyramiding = 1 时,已有同 id 持仓则忽略新买单
  • 信号 vs 撮合config.fq 仅影响指标与 close 等信号价;实际成交价、市值仍用不复权行情
  • 基准:绩效报告与权益图自动对比上证综合指数(000001);权益图 Y 轴为累计收益率(%)

暂不支持(后续扩展)

  • 分钟 / Tick 级回测
  • 限价单 / 止损单(仅市价、下一日开盘撮合)
  • 做空、融资融券
  • 财务基本面选股
  • 涨跌停、停牌专门过滤
  • 分时段定时任务(逻辑统一写在 on bar
  • on day 单次全池循环(当前为 per-symbol on bar

调试建议

  1. 先用单标的、双均线示例验证框架
  2. 多标的同日买入优先用 equal_cash(),避免手动写死 pct_equity 比例
  3. 多标的状态用 local,组合级用 state
  4. 长周期指标加 symbol_bars < N 过滤,避免上市初期 NaN
  5. 使用 config.fee = 0.0003(万分之三)更接近实盘佣金
  6. 绩效 Tab 查看夏普、最大回撤、基准超额;摘要行可看标的数、成交笔数、信号次数