Bài 10 — Kiểm thử và đọc kết quả
- 🇻🇳 Tiếng Việt
- 🇬🇧 English
- 🇰🇷 한국어
- 🇨🇳 中文
Bạn sẽ học: chạy kiểm thử chiến lược trên biểu đồ, hiểu từng chỉ số kết quả, đặt vốn / phí / cách khớp lệnh ngay trong mã, và những câu hỏi cần đặt ra trước khi tin một kết quả đẹp.
10.1. Chạy kiểm thử
- Thêm chiến lược vào biểu đồ (ví dụ chiến lược ở Bài 9).
- Khung script › thẻ Kiểm thử chiến lược › chọn kỳ (Toàn bộ lịch sử, Từ 2018, 5 năm, 3 năm).
- Bấm Chạy kiểm thử. Biểu đồ thêm ngăn Vốn (triệu đồng) và Sụt giảm %, mũi tên tại các lệnh vào / ra.
Kiểm thử chạy trên chính nến của biểu đồ — mã và khung đang xem (phút, giờ, ngày…). Đổi mã / khung / tham số sau khi chạy thì bảng ghi Mã / tham số đã đổi — chạy lại. Giao diện chi tiết: Kiểm thử chiến lược.

10.2. Đọc các chỉ số
| Chỉ số | Nghĩa | Đọc thế nào |
|---|---|---|
| Lợi nhuận | Lãi / lỗ cả kỳ so với vốn ban đầu, đã trừ phí, thuế, trượt giá | So với Mua và giữ cùng mã cùng kỳ: chiến lược có đáng công không |
| CAGR | Lợi nhuận kép mỗi năm | So được giữa các kỳ dài ngắn khác nhau |
| Sharpe | Lợi nhuận trên mỗi đơn vị biến động (theo năm) | Trên 1 là khá; trên 2 trên một mã thường là dấu hiệu quá khớp |
| Sụt giảm tối đa | Mức giảm sâu nhất của vốn từ một đỉnh | Bạn có chịu nổi mức giảm này bằng tiền thật không |
| Số giao dịch | Số lần mua – bán đã đóng | Dưới 30 là mẫu nhỏ: kết quả có thể chỉ là may |
| Tỷ lệ thắng | Phần trăm giao dịch có lãi | Không tự nói lên gì: thắng 35 % vẫn lãi nếu lãi lớn, lỗ nhỏ |
| Profit factor | Tổng lãi / tổng lỗ của các giao dịch | Trên 1 là lãi; 1,5–2 là tốt; rất cao với ít giao dịch thì đáng ngờ |
| Thời gian có vị thế | Phần trăm thời gian đang giữ cổ phiếu | Thấp = vốn nhàn rỗi nhiều, rủi ro thấp hơn mua và giữ |
Thẻ Giao dịch liệt kê từng lệnh: Vào, Ra, giá, khối lượng, lãi / lỗ, số phiên giữ, Lý do ra (Tín hiệu bán, Dừng lỗ, D ừng lỗ kéo theo, Chốt lời, Hết thời gian giữ, Đang giữ…), MAE (lỗ tạm lớn nhất trong lúc giữ) và MFE (lãi tạm lớn nhất). MFE lớn mà lãi chốt nhỏ → thoát quá muộn; MAE thường xuyên gần mức dừng lỗ → dừng lỗ quá chặt. Thẻ Theo tháng cho thấy lợi nhuận có đều hay dồn vào vài tháng may mắn.
10.3. Đặt vốn, phí, cách khớp trong mã
Không khai báo gì thì kiểm thử dùng mặc định của FireAnt Terminal (vốn 1 tỷ đồng, toàn bộ vốn mỗi lệnh, luật cổ phiếu Việt Nam). Khai báo trong mã khi chiến lược cần khác:
strategy({
name: "Bollinger hồi về giữa dải",
signals: (d) => {
const bb = ta.bb(d.close, 20, 2);
return { entry: d.close.crossBelow(bb.lower), exit: d.close.crossAbove(bb.mid) };
},
portfolio: { capital: 200000000, sizing: 0.5 },
costs: { fees: 0.002, sellTax: 0.001, slippage: 0.002 },
execution: { at: "open", delay: 1 },
rules: { maxAdvPct: 0.1 },
risk: { stopLoss: 0.08, maxHoldBars: 20 },
});
| Mục | Ý nghĩa trong ví dụ |
|---|---|
portfolio.capital | Vốn ban đầu 200 triệu đồng |
portfolio.sizing | Mỗi lần mua dùng 50 % vốn hiện có (số 0–1); "equal" = mặc định |
portfolio.pyramiding | Số lần được mua thêm cùng chiều (mặc định 1: không nhồi lệnh) |
costs.fees | Phí mỗi chiều 0,2 % (mặc định 0,15 %) |
costs.sellTax | Thuế bán 0,1 % |
costs.slippage | Trượt giá 0,2 % mỗi lệnh |
execution.at, delay | Khớp giá mở cửa ("open") hoặc đóng cửa ("close") của nến thứ delay sau tín hiệu |
rules.maxAdvPct | Mỗi lệnh tối đa 10 % khối lượng trung bình 20 phiên (đây cũng là mặc định) — tránh giả định mua được khối lượng lớn ở mã kém thanh khoản |
Đủ các mục (rules.limitLock, settlement, lotSize, phái sinh feePerContract, multiplier, flatAt…): Tham chiếu › Chiến lược.
Khớp ngay ở giá đóng cửa của chính nến sinh tín hiệu nghĩa là bạn biết giá đóng cửa trước khi đặt lệnh ATC. Có thể dùng để thử chiến lược kiểu ATC, nhưng kết quả sẽ tốt hơn thực tế; mặc định (mở cửa nến sau) an toàn hơn.
10.4. Tuỳ chọn chạy, Monte Carlo, xuất kết quả
- Chiến lược
onBar(lệnh giấyctx.buy/ctx.sell/ctx.order/ctx.exit/ctx.closePosition) chạy được trong bảng kiểm thử như chiến lượcsignals; nến khởi động (ctx.warmup) không đặt lệnh — Lệnh trong onBar. - Khớp trong nến (thanh của bảng): theo thiết lập, đường đi O–H–L–C (ước — lệnh khớp kiểu này có dấu ≈), hoặc kính lúp nến 1 phút: dừng lỗ / chốt lời / lệnh chờ trong nến ngày hay nến giờ khớp theo nến 1 phút thật bên trong, biết lệnh nào chạm trước. Nến cũ hơn dữ liệu 1 phút (khoảng 555 phiên gần nhất) lùi về đường đi.
- Trễ lệnh 1–3 nến: lệnh thị trường khớp ở nến thứ mấy sau nến đặt lệnh — kiểm xem chiến lược còn chạy khi bạn chậm tay.
- Thẻ Monte Carlo: xáo thứ tự (hoặc lấy mẫu lại) danh sách giao dịch hàng nghìn lần → dải đường vốn, sụt giảm có thể sâu tới đâu, xác suất lỗ và xác suất sụt giảm chạm ngưỡng. Hạt giống cố định: chạy lại cho cùng số — Monte Carlo.
- Xuất Chuyên nghiệp: Giao dịch (CSV), Tổng quan (CSV), Báo cáo in được (PDF)…, Tải báo cáo HTML — Xuất kết quả.
- Trình soạn đang mở script: rãnh trái hiện thời gian theo dòng của lần tính gần nhất (như Pine Profiler) — dòng tốn từ 5 ms tô cam.
10.5. Trước khi tin một kết quả đẹp
- Đủ giao dịch chưa? Dưới 30 giao dịch thì vài lệnh may mắn quyết định cả kết quả.
- Hơn mua và giữ không? Một chiến lược thua mua và giữ cả về lợi nhuận lẫn sụt giảm thì không đáng dùng.
- Chạy trên mã khác thì sao? Đổi mã trên biểu đồ (cùng ngành, khác ngành) rồi chạy lại. Chiến lược chỉ chạy tốt trên đúng mã bạn đã chọn để viết thường là may. Chạy trên cả rổ bằng Quant AI Studio cho câu trả lời chắc hơn (Bài 12).
- Chạy trên kỳ khác thì sao? So 5 năm với 3 năm và Từ 2018: kết quả có giữ được qua các giai đoạn thị trường khác nhau.
- Phí và trượt giá thực tế chưa? Tăng
slippagelên 0,3 % với mã thanh khoản thấp; chiến lược giao dịch dày rất nhạy với phí. - Tham số có phải "vừa khít" không? Đổi tham số lên xuống một chút: kết quả sụp đổ là dấu hiệu quá khớp — Bài 11.
Ngôn ngữ chặn đọc nến sau (shift(-1)…) và tín hiệu chỉ khớp từ nến kế tiếp, nên kết quả kiểm thử không bị "biết trước". Phần còn lại — chọn mã, chọn kỳ, chọn tham số sau khi đã nhìn kết quả — là thiên lệch do người kiểm thử, ngôn ngữ không chặn được.
Bài tập
Lấy chiến lược Giá cắt MA 50 ở Bài 9, thêm risk.stopLoss 5 % rồi kiểm thử trên 3 mã khác nhau (một ngân hàng, một bất động sản, một công nghệ), kỳ 5 năm. Ghi lại lợi nhuận, sụt giảm tối đa, số giao dịch và so với mua và giữ. Chiến lược có tốt đều trên cả ba không?
Mã của bài tập
strategy({
name: "Giá cắt MA 50 + dừng lỗ 5 %",
params: { len: param.int(50, { min: 10, max: 200, label: "Chu kỳ MA" }) },
signals: (d, p) => {
const ma = ta.sma(d.close, p.len);
return { entry: d.close.crossAbove(ma), exit: d.close.crossBelow(ma) };
},
risk: { stopLoss: 0.05 },
plot: (d, p) => ({ [`MA ${p.len}`]: line(ta.sma(d.close, p.len), { color: "ref" }) }),
});
Thường bạn sẽ thấy chiến lược cắt MA chạy tốt ở mã có xu hướng dài, và thua mua và giữ ở mã đi ngang nhiều — đó là đặc tính của chiến lược theo xu hướng, không phải lỗi của mã.
Tiếp theo: Bài 11 — Tối ưu tham số và tránh quá khớp
You will learn: how to run a strategy backtest on the chart, what each result metric means, how to set capital / fees / fill method right in the code, and which questions to ask before trusting a good-looking result.
10.1. Running a backtest
- Add a strategy to the chart (for example, the strategy from Lesson 9).
- Script pane › Kiểm thử chiến lược (Strategy backtest) tab › choose a period (Toàn bộ lịch sử (Full history), Từ 2018 (Since 2018), 5 năm (5 years), 3 năm (3 years)).
- Click Chạy kiểm thử (Run backtest). The chart adds the Vốn (triệu đồng) (Equity, VND mn) and Sụt giảm % (Drawdown %) panes, plus arrows at entries / exits.
The backtest runs on the chart's own candles — the ticker and timeframe you are viewing (minutes, hours, days…). If you change the ticker / timeframe / parameters after running, the table shows Mã / tham số đã đổi — chạy lại (Ticker / parameters changed — run again). Interface details: Strategy backtest.

10.2. Reading the metrics
| Metric | Meaning | How to read it |
|---|---|---|
| Lợi nhuận (Return) | Profit / loss over the whole period versus starting capital, after fees, taxes and slippage | Compare with Mua và giữ (Buy and hold) for the same ticker and period: is the strategy worth the effort? |
| CAGR | Compounded return per year | Lets you compare periods of different lengths |
| Sharpe | Return per unit of volatility (annualized) | Above 1 is fairly good; above 2 on a single ticker is often a sign of overfitting |
| Sụt giảm tối đa (Max drawdown) | The deepest fall in equity from a peak | Could you stomach this drop with real money? |
| Số giao dịch (Number of trades) | Number of closed buy–sell round trips | Under 30 is a small sample: the result may be just luck |
| Tỷ lệ thắng (Win rate) | Percentage of profitable trades | Says little on its own: a 35% win rate can still be profitable if wins are big and losses small |
| Profit factor | Total gains / total losses across trades | Above 1 is profitable; 1.5–2 is good; very high with few trades is suspicious |
| Thời gian có vị thế (Time in market) | Percentage of time holding the stock | Low = more idle capital, lower risk than buy and hold |
The Giao dịch (Trades) tab lists every trade: Vào (Entry), Ra (Exit), price, volume, P/L, sessions held, Lý do ra (Exit reason) (Tín hiệu bán (Sell signal), Dừng lỗ (Stop loss), Dừng lỗ kéo theo (Trailing stop), Chốt lời (Take profit), Hết thời gian giữ (Max holding time), Đang giữ (Still open)…), MAE (largest open loss while held) and MFE (largest open gain). Large MFE but small realized gain → exiting too late; MAE often close to the stop-loss level → stop too tight. The Theo tháng (Monthly) tab shows whether returns are steady or concentrated in a few lucky months.
10.3. Setting capital, fees and fill method in code
If you declare nothing, the backtest uses FireAnt Terminal's defaults (VND 1 bn of capital, all capital per trade, Vietnamese stock rules). Declare settings in code when the strategy needs something different:
strategy({
name: "Bollinger hồi về giữa dải",
signals: (d) => {
const bb = ta.bb(d.close, 20, 2);
return { entry: d.close.crossBelow(bb.lower), exit: d.close.crossAbove(bb.mid) };
},
portfolio: { capital: 200000000, sizing: 0.5 },
costs: { fees: 0.002, sellTax: 0.001, slippage: 0.002 },
execution: { at: "open", delay: 1 },
rules: { maxAdvPct: 0.1 },
risk: { stopLoss: 0.08, maxHoldBars: 20 },
});
| Field | Meaning in the example |
|---|---|
portfolio.capital | Starting capital of VND 200 mn |
portfolio.sizing | Each buy uses 50% of current capital (a number 0–1); "equal" = default |
portfolio.pyramiding | How many times you may add in the same direction (default 1: no pyramiding) |
costs.fees | 0.2% fee per side (default 0.15%) |
costs.sellTax | 0.1% sales tax |
costs.slippage | 0.2% slippage per order |
execution.at, delay | Fill at the open ("open") or close ("close") of the candle delay candles after the signal |
rules.maxAdvPct | Each order is capped at 10% of the 20-session average volume (also the default) — avoids assuming you can buy large volumes of an illiquid ticker |
All fields (rules.limitLock, settlement, lotSize, derivatives feePerContract, multiplier, flatAt…): Reference › Strategy.
Filling at the close of the very candle that produced the signal means you know the closing price before placing an ATC order. You can use it to test ATC-style strategies, but the results will be better than reality; the default (open of the next candle) is safer.
10.4. Run options, Monte Carlo, exporting results
onBarstrategies (paper ordersctx.buy/ctx.sell/ctx.order/ctx.exit/ctx.closePosition) run in the backtest table just likesignalsstrategies; warm-up candles (ctx.warmup) do not place orders — Orders in onBar.- Intrabar fills (the table's toolbar): per settings, O–H–L–C path (estimated — trades filled this way are marked ≈), or the 1-minute candle magnifier: stop loss / take profit / pending orders inside a daily or hourly candle fill according to the real 1-minute candles within it, so you know which order was hit first. Candles older than the 1-minute data (roughly the latest 555 sessions) fall back to the path method.
- Order delay of 1–3 candles: how many candles after the order candle a market order fills — checks whether the strategy still works when you are slow to act.
- The Monte Carlo tab: shuffles (or resamples) the trade list thousands of times → a band of equity curves, how deep drawdowns could go, the probability of loss and the probability of drawdown hitting a threshold. Fixed seed: rerunning gives the same numbers — Monte Carlo.
- Export (Professional): Giao dịch (CSV) (Trades (CSV)), Tổng quan (CSV) (Summary (CSV)), Báo cáo in được (PDF)… (Printable report (PDF)…), Tải báo cáo HTML (Download HTML report) — Exporting results.
- When the editor has the script open: the left gutter shows per-line timing of the latest calculation (like Pine Profiler) — lines taking 5 ms or more are highlighted orange.
10.5. Before trusting a good-looking result
- Enough trades? With fewer than 30 trades, a few lucky trades decide the whole result.
- Better than buy and hold? A strategy that loses to buy and hold on both return and drawdown is not worth using.
- What about other tickers? Change the ticker on the chart (same sector, different sector) and run again. A strategy that only works on the exact ticker you wrote it for is usually luck. Running on a whole basket with Quant AI Studio gives a more reliable answer (Lesson 12).
- What about other periods? Compare 5 năm (5 years) with 3 năm (3 years) and Từ 2018 (Since 2018): does the result hold across different market phases?
- Realistic fees and slippage? Raise
slippageto 0.3% for illiquid tickers; high-frequency strategies are very sensitive to fees. - Are the parameters a "perfect fit"? Nudge the parameters up and down a little: if the result collapses, that is a sign of overfitting — Lesson 11.
The language blocks reading later candles (shift(-1)…) and signals only fill from the next candle, so backtest results cannot "know ahead". The rest — choosing tickers, periods and parameters after seeing the results — is tester bias, which the language cannot prevent.
Exercise
Take the Giá cắt MA 50 (Price crosses MA 50) strategy from Lesson 9, add a 5% risk.stopLoss, then backtest it on 3 different tickers (a bank, a real-estate company, a tech company) over 5 năm (5 years). Record the return, max drawdown and number of trades, and compare with buy and hold. Does the strategy perform consistently well on all three?
Exercise code
strategy({
name: "Giá cắt MA 50 + dừng lỗ 5 %",
params: { len: param.int(50, { min: 10, max: 200, label: "Chu kỳ MA" }) },
signals: (d, p) => {
const ma = ta.sma(d.close, p.len);
return { entry: d.close.crossAbove(ma), exit: d.close.crossBelow(ma) };
},
risk: { stopLoss: 0.05 },
plot: (d, p) => ({ [`MA ${p.len}`]: line(ta.sma(d.close, p.len), { color: "ref" }) }),
});
You will usually find that the MA-cross strategy works well on tickers with long trends and loses to buy and hold on tickers that move sideways a lot — that is a trait of trend-following strategies, not a bug in the code.
Next: Lesson 11 — Parameter optimization and avoiding overfitting
배울 내용: 차트에서 전략 테스트 실행하기, 결과 지표 하나하나 이해하기, 코드에서 바로 자본 / 수수료 / 체결 방식 설정하기, 그리고 보기 좋은 결과를 믿기 전에 던져야 할 질문들.
10.1. 테스트 실행
- 전략을 차트에 추가합니다(예: 9강의 전략).
- 스크립트 패널 › Kiểm thử chiến lược(전략 테스트) 탭 › 기간 선택(Toàn bộ lịch sử(전체 기간), Từ 2018(2018년부터), 5 năm(5년), 3 năm(3년)).
- Chạy kiểm thử(테스트 실행)를 누릅니다. 차트에 Vốn (triệu đồng)(자본(백만 동))과 Sụt giảm %(낙폭 %) 패널이 추가되고, 진입 / 청산 위치에 화살표가 표시됩니다.
테스트는 차트의 캔들 그대로, 즉 보고 있는 종목과 시간 프레임(분, 시간, 일…)에서 실행됩니다. 실행 후 종목 / 시간 프레임 / 매개변수를 바꾸면 표에 Mã / tham số đã đổi — chạy lại(종목 / 매개변수가 바뀜 — 다시 실행)가 표시됩니다. 화면 세부 사항: 전략 테스트.

10.2. 지표 읽기
| 지표 | 의미 | 읽는 법 |
|---|---|---|
| Lợi nhuận(수익) | 수수료, 세금, 슬리피지를 뺀 초기 자본 대비 전체 기간 손익 | 같은 종목, 같은 기간의 Mua và giữ(매수 후 보유)와 비교: 전략이 수고할 가치가 있는지 |
| CAGR | 연 복리 수익률 | 길이가 다른 기간끼리 비교 가능 |
| Sharpe | 변동성 단위당 수익(연율) | 1 이상이면 양호; 한 종목에서 2 이상이면 대개 과최적화의 신호 |
| Sụt giảm tối đa(최대 낙폭) | 고점 대비 자본의 가장 깊은 하락 | 실제 돈으로 이 하락을 견딜 수 있는지 |
| Số giao dịch(거래 수) | 청산된 매수 – 매도 횟수 | 30 미만은 표본이 작음: 결과가 운일 수 있음 |
| Tỷ lệ thắng(승률) | 수익 거래의 비율 | 그 자체로는 의미가 적음: 승률 35 %라도 수익은 크고 손실은 작으면 이익 |
| Profit factor | 거래들의 총이익 / 총손실 | 1 이상이면 이익; 1.5–2 는 양호; 거래가 적은데 매우 높으면 의심할 만함 |
| Thời gian có vị thế(포지션 보유 시간) | 주식을 보유한 시간의 비율 | 낮음 = 놀고 있는 자본이 많고, 매수 후 보유보다 위험이 낮음 |
Giao dịch(거래) 탭은 주문을 하나씩 나열합니다: Vào(진입), Ra(청산), 가격, 수량, 손익, 보유 세션 수, Lý do ra(청산 사유)(Tín hiệu bán(매도 신호), Dừng lỗ(손절), Dừng lỗ kéo theo(추적 손절), Chốt lời(익절), Hết thời gian giữ(보유 기간 만료), Đang giữ(보유 중)…), MAE(보유 중 최대 평가 손실), MFE(최대 평가 이익). MFE는 큰데 실현 이익이 작으면 → 너무 늦게 청산한 것이고, MAE가 자주 손절 수준에 가까우면 → 손절이 너무 빡빡한 것입니다. Theo tháng(월별) 탭은 수익이 고르게 나오는지, 아니면 운 좋은 몇 달에 몰려 있는지 보여 줍니다.
10.3. 코드에서 자본, 수수료, 체결 방식 설정
아무것도 선언하지 않으면 테스트는 FireAnt Terminal의 기본값(자본 10억 동, 주문마다 전체 자본, 베트남 주식 규칙)을 씁니다. 전략에 다른 설정이 필요하면 코드에 선언하세요.
strategy({
name: "Bollinger hồi về giữa dải",
signals: (d) => {
const bb = ta.bb(d.close, 20, 2);
return { entry: d.close.crossBelow(bb.lower), exit: d.close.crossAbove(bb.mid) };
},
portfolio: { capital: 200000000, sizing: 0.5 },
costs: { fees: 0.002, sellTax: 0.001, slippage: 0.002 },
execution: { at: "open", delay: 1 },
rules: { maxAdvPct: 0.1 },
risk: { stopLoss: 0.08, maxHoldBars: 20 },
});
| 항목 | 예제에서의 의미 |
|---|---|
portfolio.capital | 초기 자본 2억 동 |
portfolio.sizing | 매수할 때마다 현재 자본의 50 % 사용(0–1 사이 숫자); "equal" = 기본값 |
portfolio.pyramiding | 같은 방향 추가 매수 허용 횟수(기본값 1: 물타기 / 불타기 없음) |
costs.fees | 편도 수수료 0.2 %(기본값 0.15 %) |
costs.sellTax | 매도세 0.1 % |
costs.slippage | 주문마다 슬리피지 0.2 % |
execution.at, delay | 신호 후 delay번째 캔들의 시가("open") 또는 종가("close")에 체결 |
rules.maxAdvPct | 주문마다 20세션 평균 거래량의 최대 10 %(이것도 기본값) — 유동성이 낮은 종목에서 대량 매수가 가능하다고 가정하는 것을 방지 |
전체 항목(rules.limitLock, settlement, lotSize, 파생상품의 feePerContract, multiplier, flatAt…): 레퍼런스 › 전략.
신호가 나온 바로 그 캔들의 종가에 즉시 체결한다는 것 은 ATC 주문을 내기 전에 종가를 안다는 뜻입니다. ATC 방식 전략을 시험하는 데는 쓸 수 있지만 결과가 실제보다 좋게 나옵니다. 기본값(다음 캔들 시가)이 더 안전합니다.
10.4. 실행 옵션, Monte Carlo, 결과 내보내기
onBar전략(모의 주문ctx.buy/ctx.sell/ctx.order/ctx.exit/ctx.closePosition)은signals전략처럼 테스트 표에서 실행됩니다. 워밍업 캔들(ctx.warmup)에서는 주문을 내지 않습니다 — onBar 안의 주문.- 캔들 내 체결(표의 바): 설정에 따름, O–H–L–C 경로(추정 — 이 방식으로 체결된 주문에는 ≈ 표시), 또는 1분봉 돋보기: 일봉이나 시간봉 안의 손절 / 익절 / 대기 주문이 그 안의 실제 1분봉에 따라 체결되어 어느 주문이 먼저 닿았는지 압니다. 1분 데이터보다 오래된 캔들(최근 약 555세션 이전)은 경로 방식으로 되돌아갑니다.
- 주문 지연 1–3캔들: 시장가 주문이 주문 캔들 이후 몇 번째 캔들에 체결되는지 — 대응이 늦어도 전략이 통하는지 확인합니다.
- Monte Carlo 탭: 거래 목록의 순서를 수천 번 섞거나(또는 재표 본 추출) → 자본 곡선의 범위, 낙폭이 얼마나 깊어질 수 있는지, 손실 확률과 낙폭이 기준에 닿을 확률을 보여 줍니다. 시드가 고정되어 있어 다시 실행해도 같은 숫자가 나옵니다 — Monte Carlo.
- Xuất(내보내기) Professional: Giao dịch (CSV)(거래(CSV)), Tổng quan (CSV)(개요(CSV)), Báo cáo in được (PDF)…(인쇄용 보고서(PDF)…), Tải báo cáo HTML(HTML 보고서 다운로드) — 결과 내보내기.
- 편집기에 스크립트가 열려 있으면 왼쪽 여백에 가장 최근 계산의 줄별 시간이 표시됩니다(Pine Profiler처럼). 5 ms 이상 걸리는 줄은 주황색입니다.
10.5. 보기 좋은 결과를 믿기 전에
- 거래가 충분한가? 거래가 30건 미만이면 운 좋은 몇 건이 전체 결과를 좌우합니다.
- 매수 후 보유보다 나은가? 수익과 낙폭 모두 매수 후 보유보다 못한 전략은 쓸 가치가 없습니다.
- 다른 종목에서는? 차트의 종목을 바꿔(같은 업종, 다른 업종) 다시 실행하세요. 작성할 때 고른 종목에서만 잘 되는 전략은 대개 운입니다. Quant AI Studio로 바스켓 전체에서 실행하면 더 확실한 답을 얻습니다(12강).
- 다른 기간에서는? 5 năm(5년)을 3 năm(3년), Từ 2018(2018년부터)과 비교하세요. 여러 시장 국면에서 결과가 유지되는지 봅니다.
- 수수료와 슬리피지가 현실적인가? 유동성이 낮은 종목은
slippage를 0.3 %로 올리세요. 거래가 잦은 전략은 수수료에 매우 민감합니다. - 매개변수가 "딱 맞춰진" 것은 아닌가? 매개변수를 조금 올리고 내려 보세요. 결과가 무너지면 과최적화의 신호입니다 — 11강.
언어가 이후 캔들 읽기(shift(-1)…)를 막고 신호는 다음 캔들부터만 체결되므로, 테스트 결과가 "미리 아는" 일은 없습니다. 나머지 — 결과를 본 뒤 종목, 기간, 매개변수를 고르는 것 — 는 테스트하는 사람의 편향이며 언어가 막을 수 없습니다.
연습 문제
9강의 MA 50 가격 교차 전략에 risk.stopLoss 5 %를 추가한 뒤, 서로 다른 3종목(은행 하나, 부동산 하나, 기술 하나)에서 5 năm(5년) 기간으로 테스트하세요. 수익, 최대 낙폭, 거래 수를 기록하고 매수 후 보유와 비교하세요. 전략이 세 종목 모두에서 고르게 좋은가요?
연습 문제 코드
strategy({
name: "Giá cắt MA 50 + dừng lỗ 5 %",
params: { len: param.int(50, { min: 10, max: 200, label: "Chu kỳ MA" }) },
signals: (d, p) => {
const ma = ta.sma(d.close, p.len);
return { entry: d.close.crossAbove(ma), exit: d.close.crossBelow(ma) };
},
risk: { stopLoss: 0.05 },
plot: (d, p) => ({ [`MA ${p.len}`]: line(ta.sma(d.close, p.len), { color: "ref" }) }),
});
보통 MA 교차 전략은 긴 추세가 있는 종목에서는 잘 되고, 횡보가 많은 종목에서는 매수 후 보유보다 못합니다. 이는 추세 추종 전략의 특성이지 코드의 오류가 아닙니다.
您将学到: 在图表上运行策略测试,理解每项结果指标,直接在代码中设置资金 / 费用 / 成交方式,以及在相信一个漂亮的结果之前需要追问的问题。
10.1. 运行测试
- 把策略添加到图表(例如第 9 课中的策略)。
- 脚本窗格 › Kiểm thử chiến lược(策略测试)标签页 › 选择区间(Toàn bộ lịch sử(全部历史)、Từ 2018(2018 年起)、5 năm(5 年)、3 năm(3 年))。
- 点击 Chạy kiểm thử(运行测试)。图表会增加 Vốn (triệu đồng)(资金,百万越南盾)和 Sụt giảm %(回撤 %)窗格,并在入场 / 出场处显示箭头。
测试在图表自身的 K 线上运行——即当前查看的代码和周期(分钟、小时、日…)。运行后更改代码 / 周期 / 参数,面板会显示 Mã / tham số đã đổi — chạy lại(代码 / 参数已更改——请重新运行)。界面详情:策略测试。

10.2. 解读各项指标
| 指标 | 含义 | 如何解读 |
|---|---|---|
| Lợi nhuận(收益) | 整个区间相对初始资金的盈亏,已扣除费用、税、滑点 | 与同一代码同一区间的 Mua và giữ(买入并持有)比较:策略是否值得 |
| CAGR | 年化复合收益 | 可在长短不同的区间之间比较 |
| Sharpe | 每单位波动的收益(年化) | 高于 1 算不错;单个代码上高于 2 通常是过拟合的迹象 |
| Sụt giảm tối đa(最大回撤) | 资金从某个高点 下跌的最大幅度 | 用真金白银时,您能否承受这样的回撤 |
| Số giao dịch(交易次数) | 已平仓的买卖次数 | 少于 30 是小样本:结果可能只是运气 |
| Tỷ lệ thắng(胜率) | 盈利交易的百分比 | 本身说明不了什么:胜率 35% 只要赚多亏少仍可盈利 |
| Profit factor | 各笔交易的总盈利 / 总亏损 | 高于 1 为盈利;1.5–2 为良好;交易很少却非常高则值得怀疑 |
| Thời gian có vị thế(持仓时间) | 持有股票的时间百分比 | 低 = 资金闲置多,风险低于买入并持有 |
Giao dịch(交易)标签页列出每笔委托:Vào(入场)、Ra(出场)、价格、数量、盈亏、持有交易时段数、Lý do ra(出场原因:Tín hiệu bán(卖出信号)、Dừng lỗ(止损)、Dừng lỗ kéo theo(跟踪止损)、Chốt lời(止盈)、Hết thời gian giữ(持有时间到)、Đang giữ(持有中)…)、MAE(持仓期间最大浮亏)和 MFE(最大浮盈)。MFE 大而实现盈利小 → 出场太晚;MAE 经常接近止损位 → 止损太紧。Theo tháng(按月)标签页显示收益是否均匀,还是集中在少数幸运的月份。
10.3. 在代码中设置资金、费用、成交方式
不做任何声明时,测试使用 FireAnt Terminal 的默认值(资金 10 亿越南盾,每笔委托使用全部资金,越南股票规则)。当策略需要不同设置时,在代码中声明:
strategy({
name: "Bollinger hồi về giữa dải",
signals: (d) => {
const bb = ta.bb(d.close, 20, 2);
return { entry: d.close.crossBelow(bb.lower), exit: d.close.crossAbove(bb.mid) };
},
portfolio: { capital: 200000000, sizing: 0.5 },
costs: { fees: 0.002, sellTax: 0.001, slippage: 0.002 },
execution: { at: "open", delay: 1 },
rules: { maxAdvPct: 0.1 },
risk: { stopLoss: 0.08, maxHoldBars: 20 },
});
| 项 | 示例中的含义 |
|---|---|
portfolio.capital | 初始资金 2 亿越南盾 |
portfolio.sizing | 每次买入使用现有资金的 50%(0–1 的数字);"equal" = 默认 |
portfolio.pyramiding | 同方向允许加仓的次数(默认 1:不加仓) |
costs.fees | 每边手续费 0.2%(默认 0.15%) |
costs.sellTax | 卖出税 0.1% |
costs.slippage | 每笔委托滑点 0.2% |
execution.at, delay | 以信号后第 delay 根 K 线的开盘价("open")或收盘价("close")成交 |
rules.maxAdvPct | 每笔委托最多为 20 个交易时段平均成交量的 10%(这也是默认值)——避免假设能在流动性差的代码上买到大量股票 |
全部选项(rules.limitLock、settlement、lotSize,衍生品的 feePerContract、multiplier、flatAt…):参考 › 策略。
在产生信号的那根 K 线收盘价立即成交,意味着您在下 ATC 委托之前就已知道收盘价。可用来测试 ATC 类策略,但结果会好于实际;默认设置(下一根 K 线开盘)更稳妥。
10.4. 运行选项、Monte Carlo、导出结果
onBar策略(模拟单ctx.buy/ctx.sell/ctx.order/ctx.exit/ctx.closePosition)可以像signals策略一样在测试面板中运行;预热 K 线(ctx.warmup)不会下单——onBar 中的委托。- K 线内成交(面板的工具条):theo thiết lập(按设置)、đường đi O–H–L–C(O–H–L–C 路径)(估算——这种方式成交的委托带 ≈ 标记),或 kính lúp nến 1 phút(1 分钟 K 线放大镜):日 K 线或小时 K 线内的止损 / 止盈 / 挂单按内部真实的 1 分钟 K 线成交,能知道哪个委托先被触及。早于 1 分钟数据(约最近 555 个交易时段)的 K 线退回使用路径方式。
- Trễ lệnh(委托延迟)1–3 根 K 线:市价委托在下单 K 线之 后第几根成交——检验您手慢时策略是否仍然有效。
- Monte Carlo 标签页:将交易列表打乱顺序(或重新抽样)数千次 → 得到资金曲线带、回撤可能有多深、亏损概率以及回撤触及阈值的概率。随机种子固定:重新运行得到相同数字——Monte Carlo。
- Xuất(导出,Professional):Giao dịch (CSV)(交易,CSV)、Tổng quan (CSV)(概览,CSV)、Báo cáo in được (PDF)…(可打印报告,PDF…)、Tải báo cáo HTML(下载 HTML 报告)——导出结果。
- 编辑器打开脚本时:左侧边槽显示最近一次计算的逐行耗时(类似 Pine Profiler)——耗时 5 ms 以上的行标为橙色。
10.5. 在相信一个漂亮的结果之前
- 交易次数够吗? 少于 30 笔交易时,几笔幸运的委托就能决定整个结果。
- 比买入并持有好吗? 收益和回撤都输给买入并持有的策略不值得使用。
- 换个代码会怎样? 在图表上换代码(同行业、不同行业)后重新运行。只在您写策略时选的那个代码上表现好的策略,通常只是运气。用 Quant AI Studio 在整个篮子上运行能得到更可靠的答案(第 12 课)。
- 换个区间会怎样? 比较 5 năm(5 年)、3 năm(3 年)和 Từ 2018(2018 年起):结果能否在不同的市场阶段中保持。
- 费用和滑点符合实际吗? 对流动性低的代码把
slippage提高到 0.3%;交易频繁的策略对费用非常敏感。 - 参数是不是“刚刚好”? 把参数上下微调:结果崩塌就是过拟合的迹象——第 11 课。
语言禁止读取后面的 K 线(shift(-1)…),且信号只从下一根 K 线开始成交,所以测试结果不会“预先知道”。其余部分——看过结果后再选代码、选区间、选参数——是测试者造成的偏差,语言无法阻止。
练习
取第 9 课中的 Giá cắt MA 50(价格穿越 MA 50)策略,加上 5% 的 risk.stopLoss,然后在 3 个不同的代码上测试(一家银行、一家房地产、一家科技公司),区间 5 năm(5 年)。记录收益、最大回撤、交易次数,并与买入并持有比较。策略在三者上的表现都一样好吗?
练习代码
strategy({
name: "Giá cắt MA 50 + dừng lỗ 5 %",
params: { len: param.int(50, { min: 10, max: 200, label: "Chu kỳ MA" }) },
signals: (d, p) => {
const ma = ta.sma(d.close, p.len);
return { entry: d.close.crossAbove(ma), exit: d.close.crossBelow(ma) };
},
risk: { stopLoss: 0.05 },
plot: (d, p) => ({ [`MA ${p.len}`]: line(ta.sma(d.close, p.len), { color: "ref" }) }),
});
您通常会发现,MA 穿越策略在长期趋势明显的代码上表现好,而在经常横盘的代码上输给买入并持有——这是趋势跟踪策略的特性,不是代码的错误。
下一课: 第 11 课 — 参数优化与避免过拟合