A structured mini-project for researching and backtesting a widely discussed weekly liquidity pattern commonly referred to as the Monday Range sweep-and-fade idea.
The repo is organised into two layers:
- Research module: empirically studies what tends to happen after Monday range sweeps (hit rates, conditional outcomes, distributions).
- Backtest module: implements a risk-managed, parameterised strategy informed by the research.
The emphasis is on:
- clear signal definitions (avoid “hand-wavy” rules),
- reproducible experiments (same inputs → same outputs),
- risk-first position sizing,
- transparent reporting (trade logs + metrics + plots).
Note: This project is for educational/research purposes only and is not trading advice.
For each week:
- Monday High: (H_M)
- Monday Low: (L_M)
- Range size: (R = H_M - L_M)
- Midpoint: (M = (H_M + L_M) / 2)
This creates a weekly reference “box” used as a liquidity boundary.
From Tuesday onward, look for a sweep beyond the Monday range followed by rejection back inside:
Long setup (sweep below)
- A candle trades below (L_M)
- Then closes back above (L_M)
- → enter long on the next bar open (or on the close, depending on the chosen convention)
Short setup (sweep above)
- A candle trades above (H_M)
- Then closes back below (H_M)
- → enter short on the next bar open
Interpretation: price runs external liquidity (stops), then mean-reverts back into the range.
Typical structure:
.
├── research_monday_range.py # empirical analysis of sweep outcomes
├── backtest_monday_range.py # strategy backtest informed by research
├── src/ # reusable utilities (data, signals, sizing, metrics)
├── results/
│ ├── research/ # research CSV/JSON outputs
│ ├── backtests/ # trade logs + metrics JSON
│ └── plots/ # equity curves + diagnostic plots
├── requirements.txt
└── README.md
(Exact filenames may differ slightly depending on the version it is using.)
research_monday_range.py is meant to answer questions like:
- How often does a Monday sweep occur?
- Conditional probabilities of reaching:
- midpoint (M),
- the opposite boundary (full reversal),
- or failing and continuing the breakout.
- Distributions of:
- maximum adverse excursion (MAE),
- maximum favourable excursion (MFE),
- time-to-target (how many bars until TP is hit).
- Outcomes measured in units of Monday range (e.g. 0.5R, 1.0R).
The key goal is to quantify:
“If a sweep happens, what is the empirical chance of mean reversion to M / opposite side?”
The research module supports multiple instruments, e.g.:
BTC-USD^GSPC(S&P 500)^DJI(Dow Jones)^IXIC(Nasdaq)EURUSD=X(FX via Yahoo tickers)- any other Yahoo Finance symbol with sufficient history
Tip: when adding more indices, keep the intervals consistent (e.g. 1h or 4h) to compare like-for-like.
python research_monday_range.py --symbol BTC-USD --interval 4h --period 2y
python research_monday_range.py --symbol ^GSPC --interval 1h --period 5yOutputs are saved under results/ (CSV + summary JSON).
backtest_monday_range.py implements a basic event-driven backtest loop with:
- signal generation (sweep-and-fade),
- entries/exits,
- position sizing via risk per trade,
- trade logging and performance reporting.
The backtest starts with a configurable initial equity, e.g.:
initial_capital = 100_000
Rather than “buy 1 unit”, the strategy sizes positions based on a fixed fraction of equity:
risk_fraction = 0.01(1% of equity per trade)
Then:
- risk amount =
equity * risk_fraction - stop distance = (entry price − stop loss) in price units
- position size =
risk_amount / stop_distance
This is the standard risk parity per trade idea used in systematic strategies.
Stops/targets are typically expressed relative to the Monday range:
- Stop loss beyond sweep extreme (optionally with a multiplier)
- Take-profit levels tied to:
- TP1: midpoint (M)
- TP2: opposite boundary (full reversal)
The strategy supports layering, i.e. partial exits:
- At TP1, close a fraction of the position:
tp1_fraction = 0.5means take 50% off at midpoint
- The remainder runs toward TP2
Why this is useful:
- reduces variance,
- locks in partial profits,
- keeps upside if the full reversal happens.
Key configurable parameters include:
symbol,interval,periodinitial_capitalrisk_fractiontp1_fraction- stop placement rules (range-multiple, fixed, ATR-based)
- end-of-week exit (e.g. close on Friday)
Backtests produce:
equity_curve.pngtrades.csv(trade-by-trade log)metrics.json(summary stats)
Typical summary metrics:
- total return
- max drawdown
- win rate
- average win / average loss
- profit factor
- Sharpe (if using bar returns)
- exposure and trade count
This repo is intentionally simple but “quant-structured”:
- separation of research vs execution/backtest code,
- risk-based sizing rather than fixed position units,
- layered exits (a realistic trade-management technique),
- explicit parameterisation,
- repeatable output artefacts.
The Monday range idea is widely known. In modern markets:
- simple rules often degrade as they become crowded,
- edge is frequently regime-dependent (trend vs mean-reversion),
- transaction costs/slippage can eliminate apparent backtest edge.
So a “vanilla” sweep-and-fade strategy is unlikely to be robust in current conditions without additional structure.
This project is not a production trading engine:
- no realistic slippage model by default,
- limited transaction cost modelling,
- depends on Yahoo Finance data quality,
- no corporate actions nuance for some instruments,
- assumes clean fills at bar boundaries.
This is expected for a portfolio/research demo.
To evolve this into something genuinely research-grade:
Only trade when conditions are favourable, e.g.:
- moving average slope filter (trend/mean-reversion regime),
- volatility filter (e.g. ATR percentile),
- VIX filter (for indices),
- range size filter (trade only when Monday range is “normal”).
- ATR-based stops/targets
- trailing stops
- time stops (exit after N bars if no follow-through)
- multiple staggered take-profits beyond just TP1/TP2
- walk-forward testing
- sensitivity analysis (heatmaps over TP1 fraction, risk fraction, stop multiplier)
- sub-period performance (year-by-year)
Instead of maximising F1/win-rate, optimise expected value:
- include per-trade fee/slippage assumptions,
- maximise expectancy or risk-adjusted return under constraints.
Use ML not to “predict price”, but to decide when the pattern is worth trading.
Example:
- Build a dataset where each sweep event is a row.
- Features: Monday range size, volatility, trend indicators, day/time, macro proxies.
- Label: whether midpoint/opposite boundary was reached.
- Train a classifier (logistic regression / gradient boosting) to output a probability of success.
- Trade only if probability exceeds a threshold (and size positions proportionally).
This often works better than trying to predict raw returns.
- Splitting research and backtesting code makes the process clearer and more defensible.
- In imbalanced/conditional setups, it’s critical to quantify hit rates and failure modes.
- A well-known discretionary pattern is rarely robust “as-is”; regime filters and execution realism matter.
- The right next step is time-series aware validation (walk-forward) and robustness checks, not just adding complexity.
Install dependencies:
pip install -r requirements.txtRun research:
python research_monday_range.py --symbol BTC-USD --interval 4h --period 2yRun backtest:
python backtest_monday_range.py --symbol BTC-USD --interval 4h --period 2yThis repository is provided for educational purposes only. Past performance does not guarantee future results.