指南 · 策略测试器
回测控制台:用代码测试交易策略
每个图表都有策略按钮。它会打开一个控制台,在历史 K 线上回放策略,并在图表上显示每笔交易、盈利、回撤、胜率以及与买入持有对比的资金曲线。先从内置策略开始,然后用几行 JavaScript 编写您自己的策略。
设计上即安全。您的脚本只在您的浏览器中运行,处于无法访问互联网的沙箱内:它只能读取提供给它的 K 线,别无其他(无法访问您的账户、本网站或其他网站)。您编写的任何内容都不会在我们的服务器上运行。
一分钟上手
- 打开任意图表,例如 EURUSD,并选择要测试的周期(H1、D1…)。
- 点击图表工具栏中的策略。控制台会在图表下方打开。
- 选择一个内置策略,例如均线交叉。它会立即运行:图表上的箭头标出每次入场和出场。
- 修改一个输入参数(比如把慢速均线从 30 改为 50):测试会自动重新运行。
- 在概览中查看汇总,在交易中查看每笔交易,在资金曲线中查看账户随时间的变化。
- 点击编辑副本将其变为您自己的脚本,修改后点击运行测试(或 Ctrl+Enter)。
控制台逐项说明
| 部分 | 作用 |
|---|---|
| 策略列表 | 内置:六个现成策略(均线交叉、RSI 反转、布林带均值回归、MACD 信号线交叉、唐奇安突破、SuperTrend)。我的策略:您保存的策略(需登录)。新建脚本…会新建一个空白脚本。 |
| 查看代码 / 编辑副本 | 查看代码可准确了解内置策略的逻辑。编辑副本会将其复制到编辑器中作为您自己的脚本,随时可修改。 |
| 参数 | 脚本中的每个 input(…) 都会在这里变成一个字段(数字或列表)。修改任意一个都会重新运行测试。默认值可恢复它们。 |
| 设置 | 资金、订单大小、成本、K 线数量以及是否允许做空(见下文)。 |
| 编辑器 | 策略的 JavaScript 代码。Tab 缩进,Ctrl/⌘+Enter 运行。出错时会显示错误信息和行号。 |
| 参考号 | 控制台内列出脚本可用的全部内容的简短清单。本页是详细版本。 |
| 保存 / 另存为… | 将脚本及其名称、输入参数和设置保存到您的账户(保存 • 上的圆点表示有未保存的更改)。您的策略会列在每个图表上。 |
| 结果标签页 | 概览、交易(含下载 CSV)、资金曲线和 Log。交易和绘制的线也会显示在图表上;点击交易即可跳转到该处。 |
设置
| 设置 | 含义 | 推荐初始值 |
|---|---|---|
| 初始资金 | 测试的初始账户金额,以价格所用货币计(EURUSD 为美元)。 | 10,000 |
| 订单数量 | 权益百分比:每笔交易使用当时账户的这一比例(100% = 整个账户,不加杠杆)。单位:固定数量(外汇以基础货币单位计:100,000 = 一标准手)。脚本可通过 { qty } 为每个订单设置自己的数量。 | 权益的 100%,或固定手数 |
| 单边佣金 % | 按成交额百分比收取的费用,入场时收取一次,出场时再收取一次。 | 外汇 0.002–0.01,股票和加密货币 0.05–0.1 |
| 滑点 / 点差(价格) | 每次成交都按不利方向加入,以价格单位计。K 线为买价,因此请在此填写典型点差,使买入更贴近现实。 | EURUSD 0.00010(1 点),黄金 0.30 |
| K 线 | 图表上:屏幕上已加载的 K 线。登录后选择最多 5,000 … 100,000 可加载同一周期更多历史数据 (本站最多允许 20,000). | 越多越好:更多 K 线、更多交易、更可靠的数据 |
| 允许做空交易 | 关闭:short() 只会平掉多单,不会开空单。便于在不改脚本的情况下只测试多头方向。 | 外汇开启,只做多投资时关闭 |
解读结果
| 数字 | 说明了什么 |
|---|---|
| 净利润 | 最终权益减去初始资金(已扣除佣金和滑点);也以资金百分比显示。 |
| 买入并持有 | 在第一根 K 线买入并持有到最后一根所能获得的收益。无法超越它,或只能以深得多的回撤超越它的策略,价值不大。 |
| 最大回撤 | 权益从峰值下跌的最大幅度,以金额和百分比表示。也就是您必须忍受的痛苦。许多交易者会看净利润 ÷ 最大回撤。 |
| 交易数、胜率 | 已平仓交易数量及盈利交易占比。若盈利交易远大于亏损交易,胜率低也没关系。 |
| 盈利因子 | 总盈利 ÷ 总亏损。大于 1 表示盈利;1.3–2 是稳健策略的常见水平;交易很少而数值很高通常只是运气。 |
| 平均每笔交易、平均盈利、平均亏损 | 每笔交易的平均盈利,以及盈利和亏损交易的典型大小。请将平均交易与您的成本比较。 |
| 最大盈利 / 亏损、连续亏损交易数 | 极端值。如果整个结果靠一笔巨大盈利支撑,这是警示信号。 |
| 夏普比率 | 每单位波动的收益,由逐根 K 线的权益变化年化而来。大于 1 为好,大于 2 为非常好(且少见)。 |
| 持仓时间占比 | 有持仓的 K 线所占比例。同样的盈利用时越少,风险越低。 |
| 多 / 空 | 多空两侧分别统计交易数、盈利和胜率:往往只有一侧在贡献全部收益。 |
交易标签页列出每笔交易的方向、入场和出场时间及价格、数量、以金额和百分比计的盈利、持续的 K 线数、平仓原因(信号、止损、止盈、反手、结束时仍未平仓)以及您的备注。下载 CSV 可导出到电子表格。资金曲线显示账户与买入持有的对比。Log 显示您的脚本通过 log() 写入的内容。
订单如何成交
- 不能偷看未来。在
onBar(i)中下的订单会以下一根 K 线的开盘价(i + 1)成交。策略绝不可能以当时无法知道的价格交易。 - K 线内止损。止损和止盈按每根 K 线的最高价和最低价检查。若两者可能在同一根 K 线内都被触及,按止损计算(谨慎假设)。跳空越过止损时按开盘价成交,与真实市场一致。
- 同一时间只有一个仓位。持空单时调用
long()会平空并开多(“反手”);已持多单时调用long()只会更新传入的止损止盈。 - 每次成交都有成本。滑点会让每次成交价格朝不利方向移动;入场和出场都收取佣金。
- 结束。最后一根 K 线之后仍未平仓的交易会按最后收盘价平仓,并标记为“结束时仍未平仓”。
编写您自己的策略
策略是一个由两部分组成的简短 JavaScript 程序:
- 顶部只运行一次:用
input()读取输入参数,用ta.*或ind()计算整条序列,再用plot()绘制。 function onBar(i) { … }会按从旧到新的顺序对每根 K 线运行。i是 K 线编号(0 = 最旧的)。在这里决定:long()、short()、exit()或什么都不做。
// 1. once: inputs and series
const len = input("Average length", 50, { min: 5, max: 400 });
const avg = ta.sma(close, len); // one value per candle
plot(avg, "SMA " + len, "#2F80ED"); // drawn on the price chart
// 2. every candle
function onBar(i) {
if (avg[i] == null) return; // the first len - 1 values are null (warming up)
if (position === 0 && close[i] > avg[i]) long();
if (position > 0 && close[i] < avg[i]) exit("below the average");
}
一开始就值得了解的事项:
- 序列是数组,每根 K 线一个值,按从旧到新排列:
close[i]是当前 K 线收盘价,close[i - 1]是前一根的。计算出的序列在 K 线足够之前为null;使用前请先检查。 - 在
onBar(i)中只能读取到i为止的 K 线。close[i + 1]会让测试报错停止:那等于偷看未来。 - 照常使用
const和let;您自己的变量会在 K 线之间保留其值(便于实现移动止损)。 - 价格以图表自身单位表示(EURUSD 为 1.0850,黄金为 2650.40);时间为 UTC 秒。
参考号
K 线与当前状态
| 名称 | 是什么 |
|---|---|
open, high, low, close, volume | K 线数值组成的数组,按从旧到新排列(成交量为 tick 成交量)。 |
time | 每根 K 线的开盘时间(UTC 秒)。new Date(time[i] * 1000).getUTCHours() 可得到小时。 |
bars, symbol, timeframe | K 线数量、代码(例如 "EURUSD")和周期(例如 "H1")。 |
position | 在 onBar 中:1 表示多头,-1 表示空头,0 表示空仓。 |
positionSize, entryPrice, entryBar | 当前交易的单位数量、成交价和 K 线编号(空仓时为 null)。i - entryBar = 持仓的 K 线数。 |
equity, cash | 此 K 线收盘时的账户价值(含持仓),以及不含持仓的现金。 |
参数
| 调用 | 结果 |
|---|---|
input("Period", 14, { min: 2, max: 100, step: 1 }) | 数字输入框(限制在 min 与 max 之间)。返回用户输入的值,或 14。 |
input("Average", "EMA", { options: ["SMA", "EMA"] }) | 一个列表。返回所选文本。 |
最多 30 个输入参数。其值随策略保存,并在您将策略发布到观点中时一并附带。
计算(ta.*)
| 函数 | 返回 |
|---|---|
ta.sma(src, n), ta.ema(src, n), ta.wma(src, n), ta.rma(src, n) | 任意序列的简单、指数、加权和 Wilder 移动平均。 |
ta.rsi(src, 14) | RSI (0–100). |
ta.atr(14), ta.tr() | 平均真实波幅(简单平均,与图表一致)以及每根 K 线的真实波幅。 |
ta.stdev(src, n) | n 根 K 线的标准差。 |
ta.highest(src, n), ta.lowest(src, n) | 最近 n 根 K 线(含当前)的最高值 / 最低值。判断突破时请与 [i - 1] 比较。 |
ta.change(src, n = 1), ta.roc(src, n = 1) | 与 n 根 K 线前的差值,以及对应的百分比。 |
ta.macd(src, 12, 26, 9) | { macd, signal, hist }. |
ta.bb(src, 20, 2) | 布林带 { upper, middle, lower }。 |
ta.stoch(14, 3, 3) | 随机指标 { k, d }(周期、%D 平滑、减速)。 |
ta.supertrend(10, 3) | { line, dir }:上升趋势中 dir 为 1,下降趋势中为 -1。 |
src 可以是任意序列:close、high、另一个结果或您自己的数组。每个函数都返回新数组;不会修改 K 线。
信号
| 函数 | 为真的条件… |
|---|---|
ta.crossover(a, b, i) | a 在 K 线 i 上向上穿越 b(在 i - 1 上小于或等于它)。b 可以是数字:ta.crossover(rsi, 30, i)。 |
ta.crossunder(a, b, i) | a 向下穿越 b。 |
ta.cross(a, b, i) | 两者皆可。 |
ta.rising(s, n, i), ta.falling(s, n, i) | s 在最近 n 根 K 线中每根都上涨(下跌)。 |
订单
| 调用 | 效果(在下一根 K 线开盘时) |
|---|---|
long(options)(也可用 buy) | 开多仓;若有空仓先平掉。 |
short(options)(也可用 sell) | 开空仓;若有多仓先平掉。关闭做空时只平多仓。 |
exit("reason") | 平掉当前持仓。原因会显示在交易列表中。 |
stopLoss(price), takeProfit(price) | 立即移动当前交易的止损 / 目标(null 表示删除)。可用于移动止损。 |
| 选项 | 含义 |
|---|---|
{ sl: 1.0820, tp: 1.0950 } | 以价格设置止损和止盈。 |
{ slPct: 2, tpPct: 4 } | 以相对成交价的百分比设置止损和目标。 |
{ qty: 50000 } | 此订单的单位数量(代替“订单大小”设置)。 |
{ comment: "breakout" } | 与交易一起显示的备注。 |
输出
| 调用 | 效果 |
|---|---|
plot(series, "Name", "#2F80ED") | 在价格图上绘制一条线(最多 12 条)。 |
log("text", value, …) | 在 Log 标签页写入一行,附带该 K 线的时间(最多 200 行)。 |
代码中的全部 71 个图表指标:ind()
ind(id, inputs) 会完全按图表的方式计算图表上的任意指标。它以序列形式返回第一条线;所有线在 .lines 中,名称在 .names 中。省略的参数取所示默认值。method 为 SMA、EMA、SMMA、LWMA 之一;price 为 close、open、high、low、median、typical、weighted 之一。
const st = ind("stoch", { k: 14, d: 3, slowing: 3 }); // st = %K, st.lines[1] = %D
const ich = ind("ichimoku"); // ich.names: Tenkan, Kijun, Span A, Span B, Chikou
const kijun = ich.lines[1];
| id | 指标 | 输入参数(默认值) | 线条 |
|---|---|---|---|
ma | Moving Average | period: 20, method: SMA, price: close, shift: 0 | MA |
ama | Adaptive Moving Average | period: 9, fast: 2, slow: 30, price: close | AMA |
adx | Average Directional Movement Index | period: 14 | ADX, +DI, −DI |
adxw | ADX Wilder | period: 14 | ADX, +DI, −DI |
bb | Bollinger Bands | period: 20, dev: 2, price: close, shift: 0 | Middle, Upper, Lower |
dema | Double Exponential Moving Average | period: 14, price: close | DEMA |
env | Envelopes | period: 14, method: SMA, dev: 0.1, price: close | Upper, Lower |
frama | Fractal Adaptive Moving Average | period: 14, price: close | FrAMA |
ichimoku | Ichimoku Kinko Hyo | tenkan: 9, kijun: 26, senkou: 52 | Tenkan, Kijun, Span A, Span B, Chikou |
sar | Parabolic SAR | step: 0.02, max: 0.2 | SAR |
stddev | Standard Deviation | period: 20, method: SMA, price: close | StdDev |
tema | Triple Exponential Moving Average | period: 14, price: close | TEMA |
vidya | Variable Index Dynamic Average | cmo: 9, ema: 12, price: close | VIDYA |
atr | Average True Range | period: 14 | ATR |
bears | Bears Power | period: 13 | Bears |
bulls | Bulls Power | period: 13 | Bulls |
cci | Commodity Channel Index | period: 14, price: typical | CCI |
chaikin | Chaikin Oscillator | fast: 3, slow: 10, method: EMA | CHO |
demarker | DeMarker | period: 14 | DeMarker |
force | Force Index | period: 13, method: SMA | Force |
macd | MACD | fast: 12, slow: 26, signal: 9, price: close | MACD, Signal |
momentum | Momentum | period: 14, price: close | Momentum |
osma | Moving Average of Oscillator | fast: 12, slow: 26, signal: 9, price: close | OsMA |
rsi | Relative Strength Index | period: 14, price: close | RSI |
rvi | Relative Vigor Index | period: 10 | RVI, Signal |
stoch | Stochastic Oscillator | k: 5, d: 3, slowing: 3, method: SMA | %K, %D |
trix | Triple Exponential Average | period: 14, price: close | TRIX |
wpr | Williams' Percent Range | period: 14 | %R |
ad | Accumulation/Distribution | 无 | A/D |
mfi | Money Flow Index | period: 14 | MFI |
obv | On Balance Volume | 无 | OBV |
volumes | Volumes | 无 | Volume |
ac | Accelerator Oscillator | 无 | AC |
alligator | Alligator | jaw: 13, jawShift: 8, teeth: 8, teethShift: 5, lips: 5, lipsShift: 3 | Jaw, Teeth, Lips |
ao | Awesome Oscillator | 无 | AO |
fractals | Fractals | 无 | Up, Down |
gator | Gator Oscillator | jaw: 13, teeth: 8, lips: 5 | Upper, Lower |
bwmfi | Market Facilitation Index | 无 | BW MFI |
zigzag | ZigZag | depth: 12, dev: 0.3 | ZigZag |
vwap | VWAP (volume-weighted average price) | 无 | VWAP |
supertrend | SuperTrend | period: 10, mult: 3 | Up trend, Down trend |
keltner | Keltner Channels | period: 20, atr: 10, mult: 2 | Middle, Upper, Lower |
donchian | Donchian Channels | period: 20 | Upper, Lower, Middle |
hma | Hull Moving Average | period: 20, price: close | HMA |
pivots | Pivot Points (classic) | 无 | P, R1, S1, R2, S2, R3, S3 |
stochrsi | Stochastic RSI | rsi: 14, stoch: 14, k: 3, d: 3 | %K, %D |
aroon | Aroon | period: 25 | Aroon up, Aroon down |
cmf | Chaikin Money Flow | period: 20 | CMF |
uo | Ultimate Oscillator | p1: 7, p2: 14, p3: 28 | UO |
chop | Choppiness Index | period: 14 | CHOP |
vwma | Volume-Weighted Moving Average | period: 20 | VWMA |
zlema | Zero-Lag Exponential Moving Average | period: 21, price: close | ZLEMA |
alma | Arnaud Legoux Moving Average | period: 9, offset: 0.85, sigma: 6, price: close | ALMA |
mcginley | McGinley Dynamic | period: 14 | McGinley |
lsma | Least Squares Moving Average | period: 25, price: close | LSMA |
chandelier | Chandelier Exit | period: 22, mult: 3 | Long exit, Short exit |
roc | Rate of Change | period: 12 | ROC |
cmo | Chande Momentum Oscillator | period: 9 | CMO |
tsi | True Strength Index | long: 25, short: 13, signal: 13 | TSI, Signal |
ppo | Percentage Price Oscillator | fast: 12, slow: 26, signal: 9 | Histogram, PPO, Signal |
dpo | Detrended Price Oscillator | period: 20 | DPO |
kst | Know Sure Thing | signal: 9 | KST, Signal |
vortex | Vortex Indicator | period: 14 | VI+, VI− |
eom | Ease of Movement | period: 14 | EOM |
mass | Mass Index | ema: 9, sum: 25 | Mass Index |
coppock | Coppock Curve | wma: 10, long: 14, short: 11 | Coppock |
hv | Historical Volatility | period: 20 | HV % |
fisher | Fisher Transform | period: 9 | Fisher, Trigger |
squeeze | Squeeze Momentum | bb: 20, bbk: 2, kc: 20, kck: 1.5 | Momentum, Squeeze on, Squeeze off |
crsi | Connors RSI | rsi: 3, streak: 2, rank: 100 | CRSI |
pvt | Price Volume Trend | 无 | PVT |
示例
每个示例都可直接运行:点击复制,打开控制台,选择新建脚本…,粘贴后点击运行测试。结果取决于代码、周期和时间段;示例用于教授方法,并非推荐。
1. Your first strategy: buy above a moving average
The smallest useful script. It is long while the close is above its 50-candle average and flat otherwise. The top of the script runs once; onBar(i) runs for every candle.
const len = input("Average length", 50, { min: 5, max: 400 });
const avg = ta.sma(close, len);
plot(avg, "SMA " + len, "#2F80ED");
function onBar(i) {
if (avg[i] == null) return; // still warming up
if (position === 0 && close[i] > avg[i]) long({ comment: "close above the average" });
if (position > 0 && close[i] < avg[i]) exit("close below the average");
}
2. Moving average crossover, long and short
Always in the market: long() while short reverses the position (and short() while long). Untick Allow short trades in the settings to make it long-only without changing the code.
const fast = ta.ema(close, input("Fast EMA", 12, { min: 2, max: 200 }));
const slow = ta.ema(close, input("Slow EMA", 26, { min: 3, max: 400 }));
plot(fast, "Fast", "#2F80ED");
plot(slow, "Slow", "#E39B2D");
function onBar(i) {
if (ta.crossover(fast, slow, i)) long();
if (ta.crossunder(fast, slow, i)) short();
}
3. Fixed stop loss and take profit in percent
slPct and tpPct are measured from the fill price. The stop and the target are checked inside every candle with its high and low; if both could have been hit in one candle, the stop counts.
const r = ta.rsi(close, 14);
const sl = input("Stop loss %", 1.5, { min: 0.1, max: 20, step: 0.1 });
const tp = input("Take profit %", 3, { min: 0.1, max: 50, step: 0.1 });
function onBar(i) {
if (position !== 0) return; // one trade at a time
if (ta.crossover(r, 30, i)) long({ slPct: sl, tpPct: tp, comment: "RSI back above 30" });
if (ta.crossunder(r, 70, i)) short({ slPct: sl, tpPct: tp, comment: "RSI back below 70" });
}
4. ATR stop and a 2:1 target in prices
sl and tp take prices. Here the stop sits 2 ATR from the close and the target twice as far (risk/reward 1:2). The fill is at the next open, so the real distance can differ slightly.
const a = ta.atr(14);
const hi = ta.highest(high, 20);
const mult = input("Stop (ATR multiple)", 2, { min: 0.5, max: 6, step: 0.5 });
function onBar(i) {
if (i < 1 || a[i] == null || hi[i - 1] == null || position !== 0) return;
if (close[i] > hi[i - 1]) { // a new 20-candle high
const stop = close[i] - mult * a[i];
const target = close[i] + 2 * mult * a[i];
long({ sl: stop, tp: target, comment: "breakout, risk " + (mult * a[i]).toFixed(5) });
}
}
5. A trailing stop that follows the price
stopLoss(price) moves the stop of the open trade. This one trails 3 ATR under the highest close since the entry and never moves down.
const a = ta.atr(14);
const fast = ta.ema(close, 20), slow = ta.ema(close, 50);
let stop = null;
function onBar(i) {
if (a[i] == null || slow[i] == null) return;
if (position === 0) {
stop = null;
if (ta.crossover(fast, slow, i)) long({ comment: "trend up" });
return;
}
const candidate = close[i] - 3 * a[i];
if (stop === null || candidate > stop) { // only ever raise it
stop = candidate;
stopLoss(stop);
}
}
6. Any chart indicator with ind(): ADX trend filter
ind(id, inputs) gives the first line of any of the chart's 71 indicators; the other lines are in .lines (their names in .names). Here: trade the MACD cross only when ADX says the market is trending.
const adx = ind("adx", { period: 14 }); // .lines[0] ADX, [1] +DI, [2] -DI
const plusDI = adx.lines[1], minusDI = adx.lines[2];
const m = ta.macd(close, 12, 26, 9);
const minAdx = input("Minimum ADX", 25, { min: 10, max: 50 });
function onBar(i) {
if (adx[i] == null || adx[i] < minAdx) return;
if (ta.crossover(m.macd, m.signal, i) && plusDI[i] > minusDI[i]) long();
if (ta.crossunder(m.macd, m.signal, i) && minusDI[i] > plusDI[i]) short();
}
7. Mean reversion with Bollinger Bands
Buys a close under the lower band, sells a close over the upper band, and exits at the middle band. A classic for ranging markets; it suffers in strong trends.
const b = ta.bb(close, input("Period", 20, { min: 5, max: 200 }), input("Width", 2, { min: 0.5, max: 4, step: 0.1 }));
plot(b.upper, "Upper", "#8E44AD");
plot(b.lower, "Lower", "#8E44AD");
function onBar(i) {
if (b.middle[i] == null) return;
if (position === 0 && close[i] < b.lower[i]) long({ comment: "below the lower band" });
else if (position === 0 && close[i] > b.upper[i]) short({ comment: "above the upper band" });
else if (position > 0 && close[i] >= b.middle[i]) exit("middle band");
else if (position < 0 && close[i] <= b.middle[i]) exit("middle band");
}
8. Trade only at certain hours (time filter)
time[i] is the candle's open in UTC seconds. This example only opens trades between 07:00 and 16:00 UTC (the London session) and closes anything still open at 20:00. Use it on an intraday timeframe.
const from = input("Start hour (UTC)", 7, { min: 0, max: 23 });
const to = input("End hour (UTC)", 16, { min: 1, max: 24 });
const fast = ta.sma(close, 10), slow = ta.sma(close, 30);
function onBar(i) {
const hour = new Date(time[i] * 1000).getUTCHours();
if (position !== 0 && hour >= 20) { exit("end of the day"); return; }
if (hour < from || hour >= to) return;
if (ta.crossover(fast, slow, i)) long();
if (ta.crossunder(fast, slow, i)) short();
}
9. Your own calculation: a volatility breakout
Series are plain arrays, so you can compute your own. This builds yesterday's range for every candle and buys a break above the open plus half of it. log() writes to the Log tab.
const k = input("Range multiple", 0.5, { min: 0.1, max: 2, step: 0.1 });
const range = high.map((h, i) => (i ? high[i - 1] - low[i - 1] : null)); // the previous candle's range
const trigger = open.map((o, i) => (range[i] == null ? null : o + k * range[i]));
plot(trigger, "Trigger", "#16A085");
function onBar(i) {
if (trigger[i] == null) return;
if (position === 0 && close[i] > trigger[i]) {
long({ slPct: 1, comment: "close above open + " + k + " x range" });
log("signal at", close[i], "trigger", trigger[i]);
} else if (position > 0 && i - entryBar >= 3) {
exit("held 3 candles");
}
}
10. Position size from risk (1% of equity per trade)
{ qty } sets the size in units for one order, overriding the Order size setting. Here the size is chosen so that hitting the stop loses about 1% of the current equity.
const a = ta.atr(14);
const riskPct = input("Risk per trade %", 1, { min: 0.1, max: 5, step: 0.1 });
const fast = ta.ema(close, 20), slow = ta.ema(close, 50);
function onBar(i) {
if (a[i] == null || slow[i] == null || position !== 0) return;
if (ta.crossover(fast, slow, i)) {
const stopDistance = 2 * a[i];
const qty = (equity * riskPct / 100) / stopDistance;
long({ qty: qty, sl: close[i] - stopDistance, tp: close[i] + 3 * stopDistance,
comment: "risk " + riskPct + "%" });
}
}
11. Two conditions in a row: pullback in an uptrend
Combines a trend filter (price above the 200 average), a pullback (RSI under 40 within the last 5 candles) and a trigger (close back above the 20 average).
const trend = ta.sma(close, 200), fast = ta.ema(close, 20), r = ta.rsi(close, 14);
function onBar(i) {
if (trend[i] == null || i < 5) return;
let dipped = false;
for (let k = 0; k < 5; k++) if (r[i - k] != null && r[i - k] < 40) dipped = true;
if (position === 0 && close[i] > trend[i] && dipped && ta.crossover(close, fast, i)) {
long({ slPct: 2, tpPct: 4, comment: "pullback bought" });
}
}
错误及修复方法
| 消息 | 原因与解决方法 |
|---|---|
| the script needs a function onBar(i) { … } | 脚本中没有 onBar,或拼写错误(onbar)。请在脚本顶层编写 function onBar(i) { … },不要放在其他函数里。 |
| close[124] is in the future: inside onBar(123) only candles up to 123 are known | 您读取了之后的 K 线。请使用 close[i] 及更早的值(close[i - 1])。判断突破时,将今天的收盘价与昨天的最高价比较:close[i] > hi[i - 1]。 |
| close cannot be changed | K 线是只读的。请改为创建您自己的数组:const mid = close.map((c, i) => (c + open[i]) / 2); |
| Cannot read properties of null / 结果中出现 NaN | 序列在开始时仍为 null(预热阶段)。在 onBar 开头加上 if (avg[i] == null) return;,并在使用 [i - 1] 前检查 i >= 1。 |
| unknown indicator "…" | 请使用指标表中的 id(小写),例如 ind("adx"),而不是 ind("ADX")。 |
| 没有交易:策略从未下单 | 这些条件从未同时成立。添加 log("rsi", r[i]) 查看数值,放宽某个条件,或加载更多 K 线。 |
| 策略运行超过 15 秒(死循环?) | 死循环,或 onBar 中的重型循环。请在顶部一次性计算序列,而不要在每根 K 线上遍历所有 K 线。 |
| Unexpected token / SyntaxError(第 N 行) | 该行有输入错误:缺少括号、引号或逗号。 |
值得信赖的测试
- 计入成本。频繁交易的策略在零成本下可能看起来很好,但在现实点差下会亏钱。请始终填写滑点(点差)和佣金。
- 交易数量要足够。少于约 30 笔交易时,数据大多是偶然结果。请加载更多 K 线或使用更小的周期。
- 不要拟合历史。反复尝试参数直到结果完美,找到的只是该时段的噪音,而不是有效的规则。在一部分历史上调参,然后在另一个时段和几个其他代码上检验相同参数。
- 与买入持有对比,并关注回撤,而不仅是盈利。
- 留意极端值。如果全部盈利来自一两笔交易,该策略依赖运气。
- 在投入资金前先练习。在模拟交易或 K 线回放中向前运行该策略,并为其信号设置提醒。
回测使用历史买价 K 线,不考虑隔夜利息、融资成本以及随市场变化的点差。过往表现不代表未来结果。这不构成投资建议。
保存、分享、发布
- 保存(需登录)可保留最多 50 个策略及其参数和设置;它们会出现在每个图表的列表中。
- 从图表发布观点并附上策略:读者可以打开您的图表,用您的参数自行运行该策略。参见观点。
- 在俱乐部中与团队分享您的分析。
- “交易”标签页中的下载 CSV 可将交易导出到电子表格。
- 如需更深入的研究,可通过价格 API(
GET /bars)将相同 K 线拉取到 Python、R 或 Excel。
限制
| 每次测试的 K 线数 | 访客:图表上的 K 线。登录用户:最多 20,000 (Premium 套餐可提高此上限;参见 Premium). |
| 脚本 | 50,000 个字符、30 个输入参数、12 条绘制线、200 行日志、列出 5,000 笔交易、15 秒运行时间。 |
| 已保存的策略 | 每个账户 50。 |
| 持仓 | 每次测试同一时间只有一个仓位(多、空或空仓);每次测试一个代码和一个周期。 |
问题
我需要会编程吗?
不需要。内置策略无需代码;您只需修改其输入参数。想要更多时,编辑副本会给您一个可运行的脚本,可逐行修改,上面的示例涵盖了常用的构建模块。
这和 Pine Script 一样吗?
思路相同(序列、输入参数、绘图、订单),语言则是普通 JavaScript。大多数简单的 Pine 策略可以逐行转换:ta.sma(close, 20) 保持为 ta.sma(close, 20),strategy.entry("L", strategy.long) 变为 long(),脚本主体放进 onBar(i),每个序列后加 [i]。
为什么我的结果与其他平台不同?
不同的 K 线(买价与中间价、券商的交易时段)、在下一根开盘而非收盘时成交、成本以及“先止损后目标”规则都会改变结果。请在相同条件下比较。
我的代码是私密的吗?
是的。代码在您的浏览器中运行;只有点击“保存”才会存入您的账户,只有您将其发布到观点中,其他人才能看到。