Đi tới nội dung chính
Chuyển tới nội dung chính

Bài 9 — Chiến lược đầu tiên

Bạn sẽ học: chuyển từ chỉ báo sang chiến lược — khai báo khi nào mua, khi nào bán, thêm dừng lỗ, chốt lời, dừng lỗ kéo theo, giữ tối đa bao lâu; vào lệnh bằng lệnh giới hạn / lệnh dừng; hiểu lệnh được khớp thế nào.

Chỉ để học

Các chiến lược trong chương là ví dụ để học cách viết, không phải khuyến nghị đầu tư. Chiến lược là lệnh giấy: script không có đường nào tới tài khoản giao dịch thật.

9.1. Khung của một chiến lược​

strategy({
name: "Giá cắt MA 50",
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),
};
},
plot: (d, p) => ({ [`MA ${p.len}`]: line(ta.sma(d.close, p.len), { color: "ref" }) }),
});
PhầnVai trò
strategy({ … })Khai báo chiến lược (thay cho indicator)
signals: (d, p) => ({ entry, exit })entry — điều kiện mua; exit — điều kiện bán (đóng vị thế). Cả hai là chuỗi điều kiện như ở Bài 3
plotTuỳ chọn: vẽ thêm đường để thấy vì sao có tín hiệu. Chiến lược mặc định vẽ chồng lên giá

Thêm vào biểu đồ: tín hiệu mua hiện ▲, tín hiệu bán ▼. Thẻ Kiểm thử chiến lược chạy chiến lược trên nến của biểu đồ và hiện lợi nhuận, các giao dịch (Bài 10).

Chiến lược Giá cắt MA 50 trên FPT: ▲ / ▼ tại tín hiệu, ngăn vốn và sụt giảm sau khi kiểm thử

Ảnh trên là chiến lược ở 9.1 chạy trên FPT toàn bộ lịch sử: 140 giao dịch, tỷ lệ thắng 23 %, lỗ so với vốn và thua xa Mua và giữ — cắt MA 50 đơn thuần cho rất nhiều tín hiệu giả khi giá đi ngang. Đó là lý do các bài sau thêm bộ lọc, quản lý rủi ro và kiểm thử cẩn thận.

9.2. Tín hiệu được thực hiện thế nào​

  • Tín hiệu xét ở nến đã đóng; lệnh khớp ở giá mở cửa của nến kế tiếp (đổi bằng execution, Tham chiếu).
  • Đang không có vị thế mà entry đúng → mua. Đang có vị thế mà exit đúng → bán. Cùng một nến có cả entry và exit thì không mua.
  • Mặc định mỗi lần mua dùng toàn bộ vốn (1 tỷ đồng), làm tròn lô 100 cổ phiếu, không mua thêm khi đang giữ.
  • Luật thị trường Việt Nam áp tự động theo loại mã: cổ phiếu T+2 (hàng mua về chiều ngày T+2 mới bán được; tín hiệu bán sớm hơn thì chờ), biên độ giá theo sàn, không khớp khi trắng bên ở giá trần / sàn, phí 0,15 % mỗi chiều, thuế bán 0,1 %, trượt giá 0,1 %. Hợp đồng tương lai (VN30F…) được bán khống, tính phí theo hợp đồng.
Luôn khai báo exit hoặc risk

Thiếu exit, dấu ▼ trên biểu đồ hiện khi tín hiệu mua hết đúng, nhưng bảng kiểm thử giữ vị thế cho tới khi chạm dừng lỗ / chốt lời / hết thời gian giữ trong risk. Viết rõ exit để hai nơi khớp nhau.

9.3. Dừng lỗ, chốt lời, dừng lỗ kéo theo​

risk quản lý vị thế sau khi mua, độc lập với exit:

strategy({
name: "EMA 20/50 có quản lý rủi ro",
params: {
fast: param.int(20, { min: 5, max: 60, label: "EMA nhanh" }),
slow: param.int(50, { min: 20, max: 200, label: "EMA chậm" }),
},
signals: (d, p) => {
const f = ta.ema(d.close, p.fast);
const s = ta.ema(d.close, p.slow);
return { entry: f.crossAbove(s), exit: f.crossBelow(s) };
},
risk: {
stopLoss: 0.07,
takeProfit: 0.25,
trailingStop: 0.1,
maxHoldBars: 60,
},
});
riskNghĩa
stopLoss: 0.07Bán khi giá giảm 7 % so với giá mua
takeProfit: 0.25Bán khi lãi 25 %
trailingStop: 0.1Bán khi giá giảm 10 % so với mức cao nhất từ lúc mua
maxHoldBars: 60Bán sau 60 nến giữ (nến của khung đang tính)
stopMode: "close" / "intraday""close" (mặc định): xét ngưỡng theo giá đóng cửa, bán ở nến sau. "intraday": khớp ngay tại ngưỡng trong nến (mở cửa đã vượt ngưỡng thì khớp giá mở cửa)

Số dạng tỷ lệ: 0,07 = 7 %. Muốn người dùng chỉnh được, viết risk thành hàm của tham số:

strategy({
name: "Bứt phá có dừng lỗ chỉnh được",
params: {
n: param.int(55, { min: 10, max: 120, label: "Đỉnh n phiên" }),
sl: param.pct(0.08, { min: 0.02, max: 0.2, label: "Dừng lỗ" }),
trail: param.pct(0.15, { min: 0.05, max: 0.3, label: "Dừng lỗ kéo theo" }),
},
signals: (d, p) => ({
entry: d.close.gt(d.high.highest(p.n).shift(1)),
exit: d.close.lt(d.low.lowest(20).shift(1)),
}),
risk: (p) => ({ stopLoss: p.sl, trailingStop: p.trail }),
});

9.4. Lọc điều kiện vào lệnh​

filter là điều kiện cho phép vào lệnh (ví dụ chỉ mua khi thanh khoản đủ, khi thị trường thuận) — tách riêng cho dễ đọc, tương đương thêm .and(filter) vào entry:

strategy({
name: "RSI 2 hồi về trung bình (có lọc)",
params: {
buy: param.int(10, { min: 2, max: 30, label: "Mua khi RSI 2 dưới" }),
sell: param.int(70, { min: 50, max: 95, label: "Bán khi RSI 2 trên" }),
},
filter: (d) => d.close.gt(ta.sma(d.close, 200)).and(ta.sma(d.volume, 20).gt(100000)),
signals: (d, p) => {
const r = ta.rsi(d.close, 2);
return { entry: r.lt(p.buy), exit: r.gt(p.sell) };
},
risk: { stopLoss: 0.08, maxHoldBars: 15 },
});

9.5. Vào bằng lệnh giới hạn hoặc lệnh dừng​

Mặc định lệnh vào là lệnh thị trường (giá mở cửa nến sau). Thêm limit hoặc stop (mức giá) để đặt lệnh có giá ở nến kế tiếp:

  • limit — lệnh giới hạn: chỉ mua nếu giá xuống tới mức đó (mua rẻ hơn).
  • stop — lệnh dừng: chỉ mua nếu giá vượt lên mức đó (mua khi bứt phá thật sự xảy ra).
strategy({
name: "Mua bứt phá bằng lệnh dừng",
signals: (d) => {
const dinh = d.high.highest(20);
const tang = d.close.gt(ta.sma(d.close, 50));
return {
entry: tang.and(d.close.gt(dinh.mul(0.97))),
stop: dinh.mul(1.001),
exit: d.close.lt(ta.sma(d.close, 20)),
};
},
risk: { stopLoss: 0.06 },
});

Ở mỗi nến giá đóng cửa cách đỉnh 20 phiên dưới 3 % trong xu hướng tăng, chiến lược đặt lệnh mua dừng ở mức vượt đỉnh 0,1 % cho nến sau; giá không vượt thì lệnh hết hiệu lực (thêm execution: { validBars: 3 } để giữ lệnh 3 nến).

9.6. Bán khống (phái sinh)​

Với hợp đồng tương lai, thêm short: { entry, exit }. Cổ phiếu Việt Nam không bán khống được — lệnh bán khống bị từ chối.

strategy({
name: "Đảo chiều theo Supertrend (phái sinh)",
signals: (d) => {
const st = ta.supertrend(d, 10, 3);
return {
entry: st.dir.crossAbove(0),
exit: st.dir.crossBelow(0),
short: { entry: st.dir.crossBelow(0), exit: st.dir.crossAbove(0) },
};
},
});

9.7. Tín hiệu kèm cảnh báo​

Chiến lược cũng có alerts như chỉ báo: thêm để nhận thông báo khi có tín hiệu trên biểu đồ đang mở.

strategy({
name: "MACD cắt tín hiệu có cảnh báo",
signals: (d) => {
const m = ta.macd(d.close, 12, 26, 9);
return { entry: m.macd.crossAbove(m.signal).and(m.macd.lt(0)), exit: m.macd.crossBelow(m.signal) };
},
alerts: (d) => {
const m = ta.macd(d.close, 12, 26, 9);
return { "Tín hiệu mua": m.macd.crossAbove(m.signal).and(m.macd.lt(0)), "Tín hiệu bán": m.macd.crossBelow(m.signal) };
},
risk: { stopLoss: 0.07 },
});

Bài tập​

Viết chiến lược mua khi MACD histogram đổi từ âm sang dương và giá trên EMA 100; bán khi histogram đổi sang âm; dừng lỗ 6 %, giữ tối đa 40 phiên.

Lời giải
strategy({
name: "MACD histogram đổi dấu",
signals: (d) => {
const h = ta.macd(d.close, 12, 26, 9).hist;
return {
entry: h.crossAbove(0).and(d.close.gt(ta.ema(d.close, 100))),
exit: h.crossBelow(0),
};
},
risk: { stopLoss: 0.06, maxHoldBars: 40 },
});

Tiếp theo: Bài 10 — Kiểm thử và đọc kết quả