Matteo Conti (MatFinOg) spent seven years as a market maker at a large investment bank before moving to run his own fund. In this video he takes a retail opening-range-breakout strategy from Fabio Valentini — the one strategy that survived his institutional testing series — and does three things with it: explains what it is, traces it back to the academic research that says why it should work, and then improves it using the component almost nobody talks about: position sizing.
The strategy that survived
The system is an opening range breakout (ORB) traded on NQ, the Nasdaq 100 futures, over a five-year sample of 878 trades. The “opening range” is simply the high and low of the first 30 minutes of the New York session. When a 5-minute candle closes above the range high, it goes long. It is long only — the mirrored short leg was tested and showed no real edge, so it was dropped.
Why it actually works: the research
What separates this from most retail strategies is that it has an academic backbone. The foundation is Market Intraday Momentum (Gao, Han, Li & Zhou, Journal of Financial Economics, 2018), which documents that the first half hour of the session carries information about its final stretch — the order imbalance set at the open tends to persist through the day. That persistence is the real engine an ORB is riding.
The improvement rests on two volatility-targeting papers — Volatility-Managed Portfolios (Moreira & Muir) and The Impact of Volatility Targeting (Harvey, Hoyle, Korgaonkar, Rattray, Sargaison & Van Hemert). Both reach the same conclusion: scaling exposure down when volatility is high raises risk-adjusted returns and shrinks drawdowns.
Stripping out what doesn’t work
Fabio’s original version adds a delta confirmation on the breakout candle. The video tests it across every possible threshold. It helped on the full sample — but it flipped sign between the first and second halves of the data. That is a coin flip, not an edge, so it gets removed. Attacking a component until it either proves itself out-of-sample or gets cut is the whole discipline here.
The one change that matters: position sizing
The base version bets a fixed one contract on every trade. A quiet, narrow-range day and a violent, wide-range day are treated identically — which means the account risks wildly different dollar amounts without meaning to. The improved version applies volatility targeting: size each trade so it risks the same dollar amount of the account. Fewer contracts on wide-range days, more on quiet days — equal dollar risk per trade. This single change to position sizing is the core of the video, and it measurably improves the strategy.
The full rule set
- Opening range: high and low of the first 30 minutes of the New York session.
- Entry: go long when a 5-minute candle closes above the opening range high. Long only.
- Stop: at the opening range low.
- Target: 1R — a clean 1-to-1.
- Time exit: flat by 2:00 PM if neither level is hit. One trade per day.
- Sizing: volatility-targeted so every trade risks the same dollar amount.
The backtesting and coding are done in MultiCharts with PowerLanguage.
A caveat worth repeating from the video: this is educational, not financial advice. The strategy was tested on a single instrument over a single five-year window, and the edge is concentrated in a recent, trend-friendly regime that may not persist. The point is not a finished system — it is the process: take a strategy with a documented foundation, strip out what doesn’t survive scrutiny, and improve what remains with a clear rationale.
Summary of the video “How to IMPROVE FaberVaale Strategy (LEVERAGING Trading RESEARCH)” by MatFinOg. Watch the full walkthrough above for the detailed analysis.
A sample implementation in NinjaTrader (NinjaScript)
To make the rules concrete, here is a working NinjaTrader 8 strategy that implements the improved variant end to end: the opening-range breakout, the range-low stop, the 1R target, the flat-by-time exit, one trade per day, and — the part the video is really about — volatility-targeted position sizing. It is written against a small in-house base class (AbstractManagedStrategy) that provides shared risk and logging helpers, but the logic that matters is all in plain view below.
This is educational sample code, not a finished trading system. It was written to illustrate the mechanics discussed above; test it yourself before risking real capital.
How the code works, block by block
- Building the opening range. On every bar the strategy checks whether the bar’s close time falls inside the first 30 minutes of the session (
09:30–10:00by default, set byORStartandORDurationMinutes). While it does, it tracks the running high and low. Once the window passes, the range is frozen — that high and low are the only two levels the rest of the day cares about. - The entry. After the range is set, the first bar that closes above the opening-range high fires a long. It is long only, and a
tradedTodayflag guarantees exactly one trade per session — even after the stop or target is hit, it will not re-enter that day. - Stop and target. The stop is placed at the opening-range low. The distance from entry to that stop is one unit of risk,
R. The target is set exactly1Rabove entry — a clean 1:1 — viaSetStopLossandSetProfitTargetin price mode, attached to the entry order. - Volatility-targeted sizing. This is the improvement from the video. Instead of a fixed one contract, the strategy converts the per-contract dollar risk (the stop distance times the instrument’s point value) into a contract count that risks the same fixed dollar amount,
RiskPerTradeUSD, on every trade. A wide-range (high-volatility) day therefore gets fewer contracts; a quiet day gets more. SwitchUseVolatilityTargetingoff and it falls back to the fixed one-contract base version, so you can compare the two directly. - Time exit and daily reset. If neither the stop nor the target is touched, the position is flattened at
FlatTime(14:00 by default). When the calendar date changes, the range, the once-per-day gate, and the drawing anchors all reset for the new session. - The visual. When
ShowOpeningRangeis on, the high and low are drawn as a per-day pair of lines (blue for the high, red for the low), extending across the session. Because those two levels are the entry trigger and the stop, the drawing doubles as a visual audit of every setup.
Drop the file into your NinjaTrader Custom\Strategies folder, compile, and run it on a 5-minute chart with a regular-session (RTH) template so 09:30 is the true session open. It was written with NQ (Nasdaq 100 futures) in mind, but the sizing math reads the instrument’s point value, so it adapts to whatever you load. Mind your platform time zone — the HHMM times use the chart’s clock.
The full strategy
using NinjaTrader.Cbi;
using NinjaTrader.Custom.Strategies;
using NinjaTrader.Custom.Util;
using NinjaTrader.Data;
using NinjaTrader.Gui;
using NinjaTrader.NinjaScript.DrawingTools;
using System;
using System.ComponentModel.DataAnnotations;
using System.Windows.Media;
// This namespace holder is required. Do not change it.
namespace NinjaTrader.NinjaScript.Strategies.TrendFollow
{
// FaberVaale - an Opening Range Breakout (ORB) after Fabio Valentini's strategy, as analysed in
// MatFinOg's video "How to IMPROVE FaberVaale Strategy (LEVERAGING Trading RESEARCH)".
//
// Rules implemented (video's improved variant):
// - Opening range = high/low of the first 30 minutes of the session (default 09:30-10:00 NY).
// - LONG when a bar CLOSES above the opening range high. Long only (the mirrored short leg
// showed no real edge in the study and is intentionally omitted).
// - Stop at the opening range low. Target at 1R (a clean 1:1 from entry).
// - Time exit: flat by 14:00 if neither level is hit. One trade per day.
// - Position sizing: volatility targeting - size each trade so it risks the same dollar amount
// of the account. Wide-range (high-volatility) days get fewer contracts, quiet days get more.
// Turn UseVolatilityTargeting off to fall back to the base fixed-one-contract version.
//
// Intended timeframe: a 5-minute primary series (matching the video). The opening-range window and
// the flat-by time are clock-based, so any intraday series works; only the granularity changes.
public class FaberVaale : AbstractManagedStrategy
{
private double openingRangeHigh;
private double openingRangeLow;
private bool openingRangeComplete;
private bool tradedToday;
private int openingRangeStartBar = -1; // first bar that fell inside the OR window today
protected override void OnStateChange()
{
base.OnStateChange();
if (State == State.SetDefaults)
{
Description = "Opening Range Breakout (FaberVaale). Long-only breakout of the first-30-minute range, "
+ "stop at range low, 1R target, flat by a fixed time, one trade per day, with volatility-targeted sizing.";
Name = "FaberVaale";
Calculate = Calculate.OnBarClose;
EntriesPerDirection = 1;
EntryHandling = EntryHandling.AllEntries;
IsExitOnSessionCloseStrategy = true;
ExitOnSessionCloseSeconds = 30;
StartBehavior = StartBehavior.WaitUntilFlat;
TimeInForce = TimeInForce.Gtc;
StopTargetHandling = StopTargetHandling.PerEntryExecution;
RealtimeErrorHandling = RealtimeErrorHandling.StopCancelClose;
BarsRequiredToTrade = 6;
IsInstantiatedOnEachOptimizationIteration = true;
// Position / risk defaults (some inherited from AbstractStrategy).
MaxEntries = 20; // upper cap so volatility sizing can actually scale contracts
MaxDailyLossPercent = 10;
MaxRiskPerTradePercent = 20;
// Strategy-specific defaults.
ORStart = 930; // HHMM, session open (NY RTH)
ORDurationMinutes = 30; // length of the opening range
FlatTime = 1400; // HHMM, flatten by this time if still in a trade
UseVolatilityTargeting = true;
RiskPerTradeUSD = 500; // equal dollar risk per trade when volatility targeting is on
ShowOpeningRange = true; // draw the OR high/low on the chart
}
else if (State == State.Configure)
{
openingRangeComplete = false;
tradedToday = false;
openingRangeHigh = double.MinValue;
openingRangeLow = double.MaxValue;
}
}
protected override void OnBarUpdate()
{
base.OnBarUpdate();
if (CurrentBar < 1) return;
// New calendar day -> reset the opening range and the once-per-day gate.
if (Time[0].Date != Time[1].Date)
ResetForNewDay();
DateTime orStart = TimeToday(ORStart);
DateTime orEnd = orStart.AddMinutes(ORDurationMinutes);
DateTime flat = TimeToday(FlatTime);
DateTime barClose = Time[0];
// Build the opening range from bars whose close falls inside (orStart, orEnd].
if (barClose > orStart && barClose <= orEnd)
{
if (openingRangeStartBar < 0) openingRangeStartBar = CurrentBar;
openingRangeHigh = Math.Max(openingRangeHigh, High[0]);
openingRangeLow = Math.Min(openingRangeLow, Low[0]);
openingRangeComplete = false;
return;
}
// Freeze the range once the window has passed.
if (barClose > orEnd && !openingRangeComplete && openingRangeHigh > double.MinValue)
{
openingRangeComplete = true;
Log("Opening range set: high=" + openingRangeHigh.ToString("F2") + " low=" + openingRangeLow.ToString("F2"));
}
// Draw the OR high/low as a per-day segment, extending from the range's first bar to the
// current bar until the flat-by time (then it freezes for the rest of the day).
if (ShowOpeningRange && openingRangeComplete && openingRangeStartBar >= 0 && barClose <= flat)
DrawOpeningRange();
// Flatten by the configured time.
if (Position.MarketPosition == MarketPosition.Long && barClose >= flat)
{
Log("Flat-by time reached (" + FlatTime + ") - exiting long.");
ExitLong();
return;
}
if (!openingRangeComplete) return; // no range yet, nothing to break out of
if (tradedToday) return; // one trade per day
if (Position.MarketPosition != MarketPosition.Flat) return;
if (barClose >= flat) return; // too late in the day to open a new trade
// Entry: a bar closes above the opening range high. Long only.
if (Close[0] > openingRangeHigh)
EnterBreakoutLong();
}
private void EnterBreakoutLong()
{
double entry = Close[0];
double stop = openingRangeLow;
double riskPoints = entry - stop;
if (riskPoints <= 0) return; // degenerate range, skip
double target = entry + riskPoints; // 1R, a clean 1:1
int contracts = 1;
if (UseVolatilityTargeting)
{
double riskPerContract = pointsToPrice(riskPoints); // $ risked per contract
if (riskPerContract > 0)
contracts = Math.Max(1, (int)(RiskPerTradeUSD / riskPerContract));
}
// Stop at the range low, target at 1R. Set before the entry so they attach to it.
SetStopLoss(CalculationMode.Price, stop);
SetProfitTarget(CalculationMode.Price, target);
EnterLong(contracts, Name + "_" + CurrentBar);
tradedToday = true;
Log("ORB long: entry~" + entry.ToString("F2") + " stop=" + stop.ToString("F2")
+ " target=" + target.ToString("F2") + " risk=" + riskPoints.ToString("F2")
+ "pts contracts=" + Quantity + (UseVolatilityTargeting ? " (vol-targeted $" + RiskPerTradeUSD.ToString("F0") + ")" : " (fixed)"));
}
private void ResetForNewDay()
{
openingRangeHigh = double.MinValue;
openingRangeLow = double.MaxValue;
openingRangeComplete = false;
tradedToday = false;
openingRangeStartBar = -1;
}
// Draw the high and low of the opening range as two line segments. The tag carries the date so
// each day keeps its own pair rather than overwriting the previous day's lines.
private void DrawOpeningRange()
{
int startBarsAgo = CurrentBar - openingRangeStartBar;
string day = Time[0].ToString("yyyyMMdd");
Draw.Line(this, "ORH_" + day, false, startBarsAgo, openingRangeHigh, 0, openingRangeHigh,
Brushes.DodgerBlue, DashStyleHelper.Solid, 2);
Draw.Line(this, "ORL_" + day, false, startBarsAgo, openingRangeLow, 0, openingRangeLow,
Brushes.OrangeRed, DashStyleHelper.Solid, 2);
}
// Build today's timestamp from an HHMM integer (e.g. 930 -> 09:30, 1400 -> 14:00).
private DateTime TimeToday(int hhmm)
{
int hour = hhmm / 100;
int minute = hhmm % 100;
DateTime d = Time[0];
return new DateTime(d.Year, d.Month, d.Day, hour, minute, 0);
}
#region Properties
[NinjaScriptProperty]
[Display(Name = "Opening range start (HHMM)", Order = 1, GroupName = "FaberVaale")]
public int ORStart { get; set; }
[NinjaScriptProperty]
[Range(1, 240)]
[Display(Name = "Opening range minutes", Order = 2, GroupName = "FaberVaale")]
public int ORDurationMinutes { get; set; }
[NinjaScriptProperty]
[Display(Name = "Flat-by time (HHMM)", Order = 3, GroupName = "FaberVaale")]
public int FlatTime { get; set; }
[NinjaScriptProperty]
[Display(Name = "Use volatility targeting", Order = 4, GroupName = "FaberVaale")]
public bool UseVolatilityTargeting { get; set; }
[NinjaScriptProperty]
[Range(1, double.MaxValue)]
[Display(Name = "Risk per trade (USD)", Order = 5, GroupName = "FaberVaale")]
public double RiskPerTradeUSD { get; set; }
[NinjaScriptProperty]
[Display(Name = "Show opening range", Order = 6, GroupName = "FaberVaale")]
public bool ShowOpeningRange { get; set; }
#endregion
}
}
Backtesting the sample strategy
To see whether the code behaves the way the research predicts, I ran it through NinjaTrader’s Strategy Analyzer on MNQ (Micro Nasdaq 100 futures) — the smaller sibling of NQ, useful here because the strategy sizes to a fixed dollar risk, which makes the P&L roughly instrument-agnostic. The test used 5-minute bars, a regular-session (RTH) template, $500 risk per trade, Tradovate commissions included, and second-resolution intrabar fills so the stop-versus-target ordering inside each bar is resolved realistically rather than guessed.
The point of the exercise is the one comparison the video is built around: does volatility-targeted position sizing actually beat a fixed one contract? Same entries, same exits — only the sizing differs.
| Metric | Volatility targeting ON | Fixed 1 contract |
|---|---|---|
| Total net profit | $3,748 | $2,185 |
| Profit factor | 1.17 | 1.12 |
| Max drawdown | ($4,531) | ($4,327) |
| Net profit / max drawdown | 0.83 | 0.50 |
| Avg. trade (expectancy) | $21.17 | $12.35 |
| Sharpe / Sortino | 0.21 / 0.31 | 0.21 / 0.31 |
| Total trades | 177 | 177 |
| Percent profitable | 57.6% | 57.6% |
The thesis holds, directionally. Sizing each trade to equal dollar risk lifted net profit and per-trade expectancy by about 71% while leaving maximum drawdown essentially unchanged — so return earned per unit of drawdown improved from 0.50 to 0.83. A telling detail: the single largest win and loss are identical across both runs, which is exactly right — on the widest-range days the volatility-targeted size floors to one contract too, so the extra return comes entirely from scaling up on the quiet days, precisely as the volatility-targeting papers describe.
Reading the result honestly
Volatility targeting is the clear winner, but it is important not to oversell what is underneath it. This is a thin edge, and the metrics say so: a profit factor of 1.1–1.2 is fragile enough that a modest increase in slippage or commissions could erase much of it, and a Sharpe ratio near 0.2 is very low — barely distinguishable from noise. The 57.6% win rate carries the whole thing, yet the average win is smaller than the average loss (a 0.86 ratio) even though the setup targets a clean 1:1 — a sign that stops slip through the range low and that the time-based exit closes trades before either level is reached. Sizing improves the return; it does not turn a slim edge into a robust one.
The most important caveat is about the sample. Although the date range was set to 2023–2026, the test ran on a single quarterly contract, whose intraday data only covers its own trading life. The trade math confirms it — 177 trades at 0.61 per day is roughly 14 months, not three years — which places the whole test inside the recent, trend-friendly regime the video itself warns about. A genuine multi-year verdict needs a continuous contract with deep intraday history, and until the trade count climbs well beyond 177 across a longer window, this should be read as a promising ~14-month result rather than a validated three-year edge.
Which brings the case study full circle: a positive backtest on a recent window is a hypothesis, not a conclusion. The natural next step is exactly the discipline covered in the companion piece — a walk-forward test and a permutation test — to ask whether this edge is real or simply the reward for having sampled a friendly stretch of market. These are the author’s own educational backtests, not the video’s figures, and nothing here is financial advice.
Leave a Reply
You must be logged in to post a comment.