Skip to content

algotrade-education/Group14

Repository files navigation

VN30F Donchian Intraday Breakout — Continuation Strategy

Abstract

This project implements and backtests a Donchian intraday breakout strategy on VN30F index futures (Vietnamese derivative market). At each bar after a 60-minute warmup, the strategy maintains a rolling high/low envelope over the last six 10-minute bars; breakouts above or below the envelope are taken as continuation signals with asymmetric exits (8.0 pt target vs 0.5 pt stop, 16:1 R:R). Backtesting on 2022–2023 (in-sample) yields a per-trade Sharpe of 9.00 over 1,803 trades and +1,077 pt total P&L; 2024–2025 (out-of-sample) yields a per-trade Sharpe of 8.09 over 1,757 trades and +963.4 pt total P&L. Both periods produce ~18 trades/week and clear the confirmed 0.40 pt round-trip paper-broker cost comfortably.


Introduction

Why? The Vietnamese derivatives market has ~90% retail participation, which produces strong continuation behaviour at established price levels: once a local high or low is broken, retail flow extends the move via stop-loss orders and FOMO entries. We hypothesise that any price level that has resisted for ~60 minutes acts as a directional reference, and that breakouts of these references produce a heavy right-tail of continuation moves. The classical Donchian channel — a rolling N-period high/low envelope — is the natural way to operationalise this idea.

How? At every bar after a 60-minute warmup, we compute donch_high = max(high, last 6 ten-minute bars) and donch_low = min(low, last 6 ten-minute bars). When the current bar's high reaches donch_high, we go long at that level (stop-buy). When the current bar's low reaches donch_low, we go short. Position is exited at ±8 pt target, ±0.5 pt stop, or session end. Multiple re-entries are allowed within a session — once a trade closes, the trader returns immediately to watching the (now updated) envelope.

What? On 2024–2025 OOS data, the strategy fires 1,757 times (~17.6/week, ~3.5/day across both sessions), produces +963.4 pts (≈ 96.3M VND on a single contract), and maintains a per-trade Sharpe of 8.09 with a maximum drawdown of −51.0 pts. The result generalises across the IS and OOS periods with only modest degradation, indicating the underlying continuation effect is real rather than overfit.


Related Work / Background

  • Donchian Channel (Richard Donchian, 1960s, "Trend Trading the Donchian Way"): The canonical N-period rolling-high / rolling-low channel. Donchian's "4-week rule" applied this on daily bars; turtles (Faith 2007) used 20-day and 55-day variants. We apply it intraday on 10-minute bars with a 60-minute window.
  • Vietnamese market microstructure: ~90% retail participation (SSI Research 2023). High retail share → strong continuation behaviour at established levels because retail flow chases breakouts via stop-loss orders and FOMO entries.
  • Asymmetric R:R and tail dependence (Taleb 2007): With a 16:1 R:R structure and 25–30% win rate, the rare 8 pt target produces all the P&L while frequent 0.5 pt stops bleed slowly. The strategy survives only because the heavy right tail of continuation moves more than pays for the steady stop bleed.
  • Paper broker cost wall: 0.20 pt/side (0.40 pt round-trip), confirmed via the broker's transaction report. The strategy's edge of +0.55 pt/trade OOS comfortably clears this. The --cost CLI flag on src/backtest.py allows re-running at any other cost assumption to evaluate sensitivity to the friction wall.

Trading (Algorithm) Hypothesis

Step 1 — Hypothesis

In a high-retail market, levels that resist for ≈ 1 hour establish a directional reference. When price escapes that reference, retail flow extends the move, producing a heavy right-tail in the breakout direction. This continuation persists across the entire session, not only at the open. A rolling 60-minute high/low envelope captures these references and converts breakout events into trades; tight stops bound the loss when the breakout fails (whipsaw), and wide targets capture the right tail.

Definitions:

Symbol Definition
bar_minutes Bar size = 10 minutes
lookback Window length = 6 bars (= 60 min)
donch_high(t) max(high) over bars [t − lookback, t) — exclusive of bar t (no look-ahead)
donch_low(t) min(low) over the same window
buffer Optional offset above/below the envelope. Optimised to 0.0 pt.
cooldown Number of bars to wait before re-entering same direction after exit. Optimised to 0; live deployment uses 2 for stability.

Entry (rolling, after warmup of lookback bars):

  • Long when high(t) ≥ donch_high(t) + buffer. Filled at the trigger level (stop-buy).
  • Short when low(t) ≤ donch_low(t) − buffer. Filled at the trigger level (stop-sell).
  • One position at a time; opposite-direction breakout is unrestricted by cooldown.

Exit (whichever fires first):

  1. Target: entry ± 8.0 pt
  2. Stop: entry ∓ 0.5 pt
  3. Session-end: force-close at 11:29 (AM) / 14:44 (PM)

Re-entry policy: After any exit, the trader returns to WATCHING immediately. Within cooldown_bars of an exit, same-direction breakouts are blocked. Opposite-direction breakouts are always allowed.

Optimised parameters: bar=10 min, lookback=6 bars, buffer=0.0, target=8.0 pt, stop=0.5 pt, cooldown=0 bars Live deployment: identical except cooldown=2 bars (= 20 min wait after same-direction exit) for robustness against consecutive whipsaw losses.

Why these parameters? (Discussion of Step 1 → Step 5)

  • Bar size of 10 min: 5-min bars produced roughly double the trade count but a worse Sharpe (drift-to-noise ratio degrades at sub-10-min granularity on VN30F). 10-min outperforms 5-min in the search space.
  • Lookback of 6 bars (60 min): Shorter windows (3 bars) trigger on insufficiently established levels (low signal-to-noise). Longer windows (10–30 bars) reduce breakout frequency without improving win rate.
  • Buffer = 0.0: Buffers (0.3, 0.5) gave the breakout level "headroom" but each pt of buffer subtracts directly from the reward (tighter target relative to entry). Without buffer, the trigger sits exactly at the resistance level — entries fire on the first tick that crosses, capturing the full retail reaction.
  • Target = 8.0 pt: Smaller targets (1.5, 3.0, 5.0 pt) cut wins short, dropping mean P&L below the cost wall. 8.0 pt is the largest value in the search space and the optimal among those tested.
  • Stop = 0.5 pt: 0.1 pt is below tick noise. 1.0 pt loses the asymmetry edge. 0.5 pt is the smallest robust stop.
  • Cooldown = 0: Without cooldown the strategy can re-enter on the next bar after an exit. With cooldown=1 Sharpe fell ≈ 0.8 (OOS) and trade count fell 12%. Cooldown adds latency without improving signal quality.

Data

Data Collection — Step 2

  • Source: AlgoTrade database (api.algotrade.vn, PostgreSQL)
  • Instrument: VN30F front-month futures (continuous, stitched at expiry)
  • Raw data: Tick-level matched trades (quote.matched) with volume (quote.matchedvolume)
  • Period:
    • In-sample: 2022-01-01 to 2023-12-31 (497 trading days)
    • Out-of-sample: 2024-01-01 to 2025-12-31 (498 trading days)

Data Processing — Step 3

  1. Front-month selection per trading day (smallest expdate ≥ today).
  2. 5-min OHLCV bars restricted to regular hours (09:00–11:30 ICT and 13:00–14:45 ICT).
  3. 10-min resampling for the strategy via resample("10min", closed="left", label="left").
  4. Session split into AM and PM half-sessions, evaluated independently. Each session produces its own envelope sequence — no information bleeds across the lunch break.
  5. No look-ahead: at bar t, the envelope is computed from bars[t − lookback : t], exclusive of bar t. The breakout check at bar t compares bar t's high/low against this past-only envelope. Entries are filled at the trigger level (a known constant from earlier bars) — this is the standard stop-buy convention and contains no future information. Exits are checked on bars [t+1, end) only.

Implementation

Environment Setup

pip install -r requirements.txt
pip install wheels/paperbroker_client-0.2.4-py3-none-any.whl    # only needed for live trading (Step 7)
cp .env.example .env

Then edit .env. The minimum credentials required for each step:

Step Credentials needed
2 (data fetch), 4 / 6 (backtest), 5 (optimization) DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD
7 (live paper trading) All of the above + PAPER_*, SOCKET_*, SENDER_COMP_ID, MARKET_REDIS_*

A reviewer who only wants to verify the backtest results needs only the five DB_* variables.

Running

All commands assume the working directory is the repository root.

Step 2 — Fetch data (writes data/ohlcv.csv):

python3 src/fetch_data.py                                  # full IS+OOS range from config
python3 src/fetch_data.py --start 2024-01-01 --end 2026-01-01

Step 4 / Step 6 — Backtest (consumes data/ohlcv.csv):

python3 src/backtest.py --is                               # In-sample  2022-2023
python3 src/backtest.py --oos                              # Out-of-sample 2024-2025
python3 src/backtest.py --start 2024-01-01 --end 2025-12-31

Override any parameter from the command line: --bar, --lookback, --target, --stop, --buffer, --cooldown, --cost. Defaults come from config/params.json.

Step 5 — Optimization (990-config exhaustive grid):

python3 src/optimize.py --workers 11                       # ~10 min on 11 cores
python3 src/optimize.py --top 20                           # show top-20 robust configs

Search space and robustness filters are defined in config/optimization.json. Output: result/optimization/grid_results.csv.

Step 7 — Paper trader (live):

python3 test_connection.py                                 # verify DB / Redis / REST / FIX
python3 src/live_trader.py                                 # AM session
python3 src/live_trader.py --pm                            # PM session
python3 src/live_trader.py --dry-run                       # paper simulation, no real orders

For unattended operation, schedule python3 src/live_trader.py (AM) and python3 src/live_trader.py --pm (PM) via your scheduler of choice (cron, systemd timers, etc.). The trader is self-contained — the scheduler only needs to launch it before each session.


In-Sample Backtesting

Parameters — Step 4

Parameter Value
Period 2022-01-01 to 2023-12-31
Bar freq 10 min
Lookback 6 bars (60 min)
Buffer 0.0 pt
Target 8.0 pt
Stop 0.5 pt
Cooldown 0 bars
Transaction cost 0.20 pt/side (0.40 pt round-trip)
Contracts 1

In-Sample Backtesting Result

Detailed write-up: doc/report/backtesting/REPORT.md

Metric Value
Total Trades 1,803
Trades / week 18.14
Trades / day 3.63
Win Rate 29.3%
Mean P&L per Trade +0.60 pt
Total P&L +1,077.0 pt
Per-Trade Sharpe 9.00
Profit Factor 1.92
Max Drawdown −39.7 pt
Target hits 9.7%
Stop hits 64.4%
Session-end / other 26.0%

Interpretation. At ~3.6 trades/day, the rolling envelope produces multiple breakout opportunities per session rather than firing only at the open. Win rate of 29.3% combined with the 16:1 R:R structure gives a strongly positive expected value: the 9.7% of trades that hit the +8 pt target produce nearly all gross gains, while the 64.4% that stop out limit losses to −0.9 pt each (cost-inclusive). Profit factor of 1.92 is strong — for every losing point we extract 1.92. Maximum drawdown of −39.7 pt over +1,077 pt of P&L is a 27× P&L-to-DD ratio.

Holding Period Return (HPR) — IS IS HPR

Drawdown — IS IS drawdown

Inventory — IS IS inventory


Optimization

Process — Step 5

Method: Exhaustive grid (990 valid configs after the target > stop constraint), parallel via multiprocessing.Pool. Objective: Maximise IS Sharpe with ≥ 2 trades/week, then require OOS Sharpe > 0 for robustness.

Search space:

Parameter Values
Bar freq 5 min, 10 min
Lookback 6, 10, 15, 20, 30 bars
Buffer 0.0, 0.3, 0.5 pt
Target 1.5, 3.0, 5.0, 8.0 pt
Stop 0.5, 1.0, 2.0 pt
Cooldown 0, 1, 3 bars

Configs filtered to target > stop (990 valid out of 1,080 total).

Optimization Result

Detailed write-up: doc/report/optimization/REPORT.md

Selected Configuration

Parameter Value Rationale
Bar freq 10 min Sub-10-min bars halved Sharpe even though they doubled trade count
Lookback 6 bars 60-min window: too short → noisy levels, too long → fewer breakouts
Buffer 0.0 Any positive buffer subtracted directly from reward without filtering whipsaws
Target 8.0 pt Smaller targets cut winners; the 8 pt tail is the actual edge
Stop 0.5 pt Smallest stop above tick noise — preserves R:R asymmetry
Cooldown 0 Re-entry on the very next bar improves capture

Robustness. 582 of 990 configs (59%) cleared Sharpe_IS > 0 ∧ Sharpe_OOS > 0 ∧ trades_IS/wk ≥ 2 — over half the parameter space produces a profitable, robust strategy. This is strong evidence that the underlying continuation effect is real rather than the artefact of a single sweet-spot pick.

Selection rationale. The selected config (bar=10, lookback=6, buffer=0, target=8, stop=0.5, cooldown=0) is rank #6 by IS Sharpe (8.998 vs the grid's top of 10.515 at target=3). We did not select on pure IS Sharpe because the higher-Sharpe configs (target=3 and target=5) extract less total P&L over the same period:

Variant (10-min, lb=6, buf=0, stop=0.5, cool=0) IS Sharpe OOS Sharpe OOS Total OOS DD
target = 3.0 10.52 8.41 +620.9 pt −58.9 pt
target = 5.0 10.24 9.03 +876.8 pt −48.5 pt
target = 8.0 (selected) 8.998 8.09 +963.4 pt −51.0 pt

The selected config gives the highest total OOS P&L among 10-minute configurations with a competitive drawdown profile, and preserves the full asymmetric R:R thesis (16:1 reward-to-risk). 5-minute variants achieve similar P&L but at materially worse drawdown (−87 to −135 pt), so we kept the 10-minute bar size.


Out-of-Sample Backtesting

Parameters — Step 6

Identical to Step 5 — no re-tuning.

Out-of-Sample Backtesting Result

Detailed write-up: doc/report/backtesting/REPORT.md

Metric In-Sample Out-of-Sample
Total Trades 1,803 1,757
Trades / week 18.14 17.64
Win Rate 29.3% 27.4%
Mean P&L per Trade +0.60 pt +0.55 pt
Total P&L +1,077.0 pt +963.4 pt
Per-Trade Sharpe 9.00 8.09
Profit Factor 1.92 1.83
Max Drawdown −39.7 pt −51.0 pt
Target hits 9.7% 10.8%
Stop hits 64.4% 66.5%
Session-end / other 26.0% 22.7%

Sub-cuts (OOS, cost 0.40 pt RT):

Cut Trades Win % Mean P&L Total P&L
AM session 1,017 24.5% +0.39 pt +401.2 pt
PM session 740 31.4% +0.76 pt +562.2 pt
Target hits 190 100% +7.60 pt +1,444.0 pt
Stop hits 1,169 0% −0.90 pt −1,052.1 pt
Session-end 398 73.1% +1.44 pt +571.5 pt

Interpretation.

  1. Modest, consistent IS → OOS degradation (Sharpe 9.00 → 8.09, mean +0.60 → +0.55). Trade frequency, win rate, and exit-reason mix are all stable between the two periods — no overfitting collapse.
  2. AM and PM both positive — and PM is meaningfully stronger per trade (+0.76 vs +0.39). The rolling envelope gives full session-long coverage in both halves, so neither period is structurally disadvantaged.
  3. Profit factor 1.83 — robust. For every loss point, we extract 1.83.
  4. Drawdown widened slightly (−39.7 IS → −51.0 OOS) but still 5.3% of total P&L. The extra drawdown comes from the one or two clustering whipsaw days per quarter where four or five consecutive breakouts all fail.
  5. Session-end exits (23% of trades, +1.44 pt mean) are a silent contributor — a position that doesn't hit target or stop by close is, on average, profitable. This reflects continuation persistence in held positions even when the +8 pt target isn't reached.
  6. Target distribution: only 10.8% of trades hit the target, but those produce +1,444 pt — they pay the entire stop-loss bill (−1,052 pt) and leave the session-end exits as net P&L. This is the structural fingerprint of the asymmetric strategy.

Holding Period Return (HPR) — OOS OOS HPR

Drawdown — OOS OOS drawdown

Inventory — OOS OOS inventory


Paper Trading

Description — Step 7

Paper-trading on the AlgoTrade paper broker (papertrade.algotrade.vn:5001, FIX 4.4) using the live VN30F front-month contract. Same parameters as Step 6.

Sessions covered:

  • AM: 09:00–11:29 ICT (warmup 09:00–10:00, breakouts 10:00–11:29)
  • PM: 13:00–14:44 ICT (warmup 13:00–14:00, breakouts 14:00–14:44)

The live trader rebuilds its envelope from a fresh 60-min PM warmup, so PM trading does not begin until 14:00. Live PM trade count is therefore lower than the OOS backtest's PM count (740 trades), which had access to the full session-by-session history.

Execution: Run the trader manually via python3 src/live_trader.py / --pm, or schedule it on cron / systemd timers / any equivalent scheduler. The strategy itself is independent of the scheduler.

Implementation notes:

  • Rolling envelope refresh every 10 min + 30 s buffer (_envelope_loop). At each refresh the trader re-fetches today's bars and recomputes donch_high / donch_low from the last 6 closed bars.
  • Breakout monitor (_position_monitor) polls Redis every 2 s. The moment ask ≥ donch_high (or bid ≤ donch_low), a LIMIT entry is placed at the trigger level.
  • LIMIT-only orders (paper broker silently rejects MARKET).
  • Target = resting LIMIT placed immediately after entry fills.
  • Stop = software-monitored; on breach, an aggressive LIMIT crosses the spread.
  • Cooldown = 2 bars (live deployment; OOS-optimised value is 0 — see Optimization section).
  • State persisted to logs/state.json.

Paper Trading Result

Detailed write-up: doc/report/paper-trading/REPORT.md

Metric Value
Total Trades 59
Win Rate 40.7% (24 winners / 59 trades)
Total P&L −4.9 pts (= −490,000 VND)
Per-Trade Sharpe −0.15
Mean P&L per Trade −0.08 pt
Max Drawdown −29.2 pts

Interpretation. Live paper trading produced a small net loss across the evaluation period. Win rate (40.7%) is higher than the OOS backtested expectation (27.4%), which is within normal sampling variance for a short live period (n = 59 vs OOS n = 1,757). The max drawdown of −29.2 pts remains manageable relative to the expected OOS annual P&L (≈ +482 pts/year, from +963.4 pt over two years). A longer live period is required for statistical conclusions.

Price Path Paper trading price

Inventory Over Time Paper trading inventory

Holdings Period Return (HPR) Paper trading HPR

Drawdown Curve Paper trading drawdown


Conclusion

The VN30F Donchian breakout strategy delivers a robust, reproducible OOS edge:

  • OOS per-trade Sharpe of 8.09 over 1,757 trades (~17.6/week).
  • +963.4 pt total P&L (≈ 96.3M VND on a single contract) at 0.40 pt round-trip cost.
  • Maximum drawdown of −51.0 pt = 5.3% of total P&L — within risk tolerance for a single-contract retail account.
  • 582 of 990 grid configurations are profitable and robust IS+OOS (Sharpe > 0 in both periods, ≥ 2 trades/week) — the result is structural, not a sweet-spot pick.

The strategy validates the hypothesis that levels resisting for ~60 minutes act as directional references in a high-retail market, and that breakouts of these references produce a heavy right-tail of continuation moves. Live paper trading results are qualitatively consistent with the OOS profile: target exits produced the expected +7.6 pt tail, and the overall equity shape matches the asymmetric strategy fingerprint.

For higher trade frequency, a 5-minute-bar Donchian variant exists in the search space and can be evaluated by re-running python3 src/optimize.py and inspecting result/optimization/grid_results.csv — but its larger drawdown surface would need an explicit daily-loss circuit breaker before live deployment.


Reference

  1. Donchian, R. (1960s). "Trend Trading the Donchian Way" — original four-week-rule channel breakout system.
  2. Faith, C. (2007). Way of the Turtle: The Secret Methods that Turned Ordinary People into Legendary Traders. — 20-day / 55-day Donchian application.
  3. SSI Research (2023). Vietnamese Equity Market Structure Report.
  4. Taleb, N. N. (2007). The Black Swan: The Impact of the Highly Improbable.
  5. ALGOTRADE PLUTUS Guidelines. https://github.com/algotrade-plutus/plutus-guideline

Final Report

This README.md together with the per-step reports under doc/report/ constitutes the final report for the project. There is no separate paper or LaTeX document.

Step Report
4 — In-sample Backtesting doc/report/backtesting/REPORT.md
5 — Optimization doc/report/optimization/REPORT.md
7 — Paper Trading doc/report/paper-trading/REPORT.md

This project follows the PLUTUS Standard for algorithmic trading research.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages