Trader Kane Strategy: The Lab Model NQ Trading System Explained

Quick answer: “Trader Kane” (publicly named by Business Insider as Kane Simons) is an NQ futures day trader and co-founder of The Lab, a trading education community. His system, “The Lab Model,” combines standard ICT/Smart Money concepts (SMT divergence, inversion Fair Value Gaps, premium/discount) with three things that are distinctly his: NQ-only execution with ES as a divergence reference, a 10:00 AM ET four-hour candle as the timing anchor, and unusually aggressive breakeven management that he says is the actual difference between the model working and not. No first-person written rulebook from Kane exists publicly – every mechanical version in circulation, including the one on this page, is a reconstruction. We built one anyway, tagged every rule by source, and backtested it live on TradingView. This guide covers what he’s actually said, what’s third-party, what’s standard ICT, and the full PickMyTrade Pine Script if you want to run it yourself.

Written by the PickMyTrade team from publicly available interviews, X posts, and third-party writeups, cross-checked against original sources wherever possible. Educational content, not financial advice.

Not the same “Kane.” This article is about Trader Kane (Kane Simons), the NQ futures scalper behind The Lab. It has nothing to do with Kane Trading (kanetrading.com), a Fibonacci/ABCD-pattern education site run by a trader named Jim since 2003, or with any hockey player, coin trader, or wrestler named Kane. If a “Kane trading strategy” search brought you here for one of those, this isn’t it.

Key takeaways

Who is Trader Kane?

“Kane” is a futures day trader who specializes in NQ (Nasdaq-100 e-mini futures). In a May 2026 as-told-to essay, Business Insider publicly named him Kane Simons (the outlet’s body text and URL slug disagree on the spelling – “Simons” vs. “Simmons” – and no second source confirms either). He’s the co-founder of The Lab (thelabtrading.com), a trading education community he runs with a partner known as Alex, and he separately runs propfirm.compare, an affiliate comparison site for prop trading firms.

His trading history, in his own words: FX, then crypto, then back to FX, then futures. He says he lost roughly $700,000 in the FTX collapse in November 2022, took a few months off, and came back trading futures in early 2023. He’s still active as of mid-2026 – Apex publicly re-onboarded him in February 2026, and The Lab’s livestreams are ongoing.

His branded system is “The Lab Model.” It is not the same thing as the ICT “Unicorn Model” – that name belongs to a different Lab community member, Juno, and the two are frequently and incorrectly conflated online.

Where to actually find him: X/Twitter @traderkane (~62K followers), YouTube @traderkane (~27K subscribers, 1.4M+ lifetime views), Instagram instagram.com/traderkane – note: no trailing underscore, the underscore handle he links from an old anti-scam post is dead.

What is “The Lab Model,” in his own words?

Asked to summarize his approach in one breath, Kane put it like this:

“I do use SMT… premium discount and inversions is basically my model, right, and then you add in standard deviations to cut out the noise – that’s it.”

And, pressed for the underlying premise:

“I really believe that the market just delivers into 50% of ranges and redelivers into a 50% of another range… I built a model around just grabbing that 50% to 50%. And that’s all I need. I don’t need a big move, I just need to grab that base hit every single day and then I’m done, I move on.”

Important: Kane doesn’t claim to have invented the 50%-rebalance idea – he credits it explicitly to “market maker theory.” His stated originality is narrow: “I built a model around that.”

What’s genuinely his vs. standard ICT

Nearly every individual building block of the Lab Model – Fair Value Gaps, inversion FVGs, SMT divergence, premium/discount, Power of Three (accumulation -> manipulation -> distribution) – is off-the-shelf public ICT/Smart Money methodology, and Kane doesn’t pretend otherwise. Asked point blank about originality, his own answer is dismissive of the debate: “why do you not give a damn if it’s ICT or not.” He names three specific educators he credits: MXM, Dexter (for standard-deviation projections and PO3), and TTrades, joking “TTrades taught me everything I know.”

What’s actually distinctive is the assembly and the filters, not the primitives:

  • NQ-only execution, ES as the sole divergence reference – and an explicit rejection of the Dow (YM): “I don’t think YM provides any confirmational bias to my model.”
  • The 10:00 AM ET four-hour-candle anchor, with his own mechanical justification (below)
  • The 9:15-11:30 AM ET personal trading window, driven by the 9:30 AM liquidity injection
  • “Logical Liquidity Target” (LLT) – his branded name for the take-profit level, not standard ICT vocabulary
  • The requirement that both SMT divergence and an inversion FVG be present, in either order
  • Unusually aggressive, four-different-triggers breakeven management – which he says is what actually makes the model profitable
  • Static contract sizing with a documented 100-tick profit cap during his highest-profile prop run
  • Re-anchoring the dealing range to wherever ES swept liquidity, not to NQ’s own visible high or low

A candid community assessment from r/InnerCircleTraders sums up the fair reading: “Kane doesn’t really have anything unusual in my opinion… I didn’t see anything particularly unique in his model.” That’s a defensible take on the raw components. What we’d add: the filtering and the breakeven discipline are where the “unusual” actually lives, and those are exactly the parts that are hardest to reduce to a checklist.

The core building blocks

We’re tagging every rule below by source, because two of the most-cited “Kane rulesets” – one from FX Replay, one from Chart Fanatics/TradeZella – actually conflict with each other (more on that in a minute), and most articles quietly blend them into one thing. We won’t.

  • [Kane-stated] – he said it himself, on camera or in an X post
  • [Third-party] – a platform’s write-up about his method, not his own words
  • [Standard ICT] – established public methodology that predates him
  • [PickMyTrade] – our own fill-in, used only where no source specifies a number

SMT divergence – a “crack in correlation” between NQ and ES. If NQ prints a new high but ES doesn’t confirm it, that’s bearish SMT (Nasdaq manipulation to the upside); the reverse for a new low is bullish SMT. Kane: “if ES is more bearish and Nasdaq’s pulling higher I would typically treat that as NASDAQ manipulation.” A stronger variant he calls dual SMT – divergence at both the highs and lows of the same range – which he treats as higher quality.

Inversion FVG (iFVG) – a Fair Value Gap (a 3-candle imbalance) that price later body-closes through, flipping its role from support to resistance or vice versa. Kane’s gloss: “an inversion is just a previous gap that should be used as support gets traded below and used as resistance.” The body close is the diagnostic – a wick piercing the gap doesn’t count.

[WARNING] Naming trap: “IFVG” on TradingView can mean two unrelated things – Inversion FVG (what Kane’s model uses) and a completely different concept called Implied FVG. Searching “IFVG” and grabbing the first result risks installing the wrong indicator.

Premium / discount – the upper and lower half of a recent price leg, split at the midpoint (equilibrium). Kane’s directional rule is absolute: “ultimately I cannot long here in the premium – just can’t, my model doesn’t allow me to do it.” Longs only trigger from discount; shorts only from premium. (If you want this concept as a standalone visual on your own charts, see our free Premium/Discount Zones indicator for TradingView.)

Dealing-range anchoring – the biggest automation blocker – how you draw that premium/discount range matters enormously, and Kane doesn’t anchor it to NQ’s own obvious high or low. He re-anchors to wherever ES actually swept liquidity: “because ES actually took the high here, in my head this high right here is the high… I don’t care that this is the [NQ] high.” That’s a discretionary judgment call with no published mechanical substitute – arguably the single hardest piece of the model to automate honestly.

Instruments, timeframes, and the 10 AM anchor

Instrument: NQ only for intraday futures execution. ES is a divergence reference, never traded directly: “you’re only trading NASDAQ – yeah I’m only trading NASDAQ. Obviously I look at the SP500 ES for divergence.” (He’s also described using the same model on Bitcoin with ETH as the divergence asset, so “NQ only” describes his futures day trading specifically, not every account he’s ever run.)

Execution timeframe: 1, 3, or 5-minute charts. This is a real correction worth flagging – earlier summaries floating around claim a 1/3/15-minute set. The sourced version is consistently 1m/3m/5m; M15 shows up exactly once, in a single trade recap, not as a rule.

Why 10:00 AM ET specifically – Kane gives an actual mechanical reason, not just a preference. On CME futures with an 18:00 ET session anchor, 10:00 AM ET is exactly the point where every timeframe from 1-minute up through 4-hour opens simultaneously, while nothing above the 4-hour does:

“What time frame opens at 10:00 a.m.? We get 4 hour. We obviously get an hour… we get all of the time frames below the 4 hour open at 10:00 a.m. – the 1 minute, the 3 minute, the 5 minute.”

That arithmetic actually checks out: 960 minutes past the 18:00 ET session open divides evenly by 1, 3, 5, 15, 30, 60, 120, and 240 – but not by 720 (12H) or 1440 (Daily), matching exactly what he says does and doesn’t open there.

Session window – this genuinely shifted over time, and we’re not going to pretend it didn’t. His most concrete, most recent, on-camera statement: 9:15-11:30 AM ET, “because it has that 9:30 liquidity injection, so it’s most volatile.” A separate, widely-repeated 10:00 AM-1:00 PM ET window comes from FX Replay’s write-up, not from Kane directly – treat that one as the backtesting community’s convention, not his own words.

The two setups: reversal and continuation

The Lab Model runs two distinct entry patterns, and they are not interchangeable – most secondary summaries blur an important difference between them.

Reversal setup (has a sweep precondition): wait for ES and/or NQ to sweep the prior 10 AM 4-hour candle’s high or low against your intended direction, then require SMT divergence and an inversion FVG on the 1/3/5-minute chart – in either order, both required.

Continuation setup (no sweep precondition): a lower-timeframe range rebalances back through its own midpoint while a higher-timeframe imbalance still sits ahead in the same direction, then the same SMT + iFVG requirement applies.

Entry, in Kane’s own compressed version, written out live on a whiteboard: “my trade would be entry at inversion… and then my stop loss at divergence… and then my TP is just 50%.” He describes two order-type options with no stated rule for choosing between them: “I will either limit a retap into this area or I’ll sell stop this with my invalidation here.”

The two published rulesets conflict – read this before you build anything

Two different write-ups of the Lab Model circulate online, and they disagree on real, load-bearing details:

FX Replay’s “Lab Model”Chart Fanatics / TradeZella’s “50% Reversal”
Timeframe stack4hr / 1hr / 5m – no DailyDaily / 4H / 1H, executed on 3m
TargetLLT – the first swing high/low past the midpointThe 50% midpoint itself
StopRecent H/L, or SMT invalidationJust above/below the SMT swing
BreakevenHalfway to target (their own mechanization)When the 11 AM hourly candle flips
Kane’s endorsementHe publicly asked for credit he didn’t getHe publicly endorsed the source video on X

The LLT is a swing point beyond the midpoint; the 50% target is the midpoint itself – these are materially different exits, and both are published as “Kane’s strategy.” We don’t blend them below; our Pine Script offers both as a selectable option, clearly labeled.

Stop placement has the same problem – four different phrasings exist across sources (“stop at divergence,” “stop below local SMT,” “stops below the inversion,” “recent H/L”) and Kane has never reconciled them publicly. What he has given is a concrete stop-size guideline: around 25 NQ points (100 ticks), and he’ll skip a trade outright if the structural stop is wider than that – asked about a 50-point stop, he said flatly, “No… I missed the move, in my head.”

Breakeven management – the part Kane says actually matters

This is the section of the model Kane is most emphatic about, and it’s worth quoting at length because it directly contradicts how most people trade it:

“For those that know me, I am very very fond of break even trades… It’s only profitable when the break evens are managed correctly. People that don’t do this correctly that pick up my model will consistently tell me the model is not profitable – the model is profitable, you’re just not managing it correctly.”

He states four different breakeven triggers across different sources, and they are not the same rule: an hourly-candle-flip against the position, a 15-minute swing being taken out, moving to just under breakeven as soon as price sweeps into the zone, or after an hourly low is swept. A fifth version – “go breakeven halfway to target” – is the most repeated version online and is not his own rule; it’s FX Replay’s explicit mechanization of his more discretionary behavior, and the word “halfway” doesn’t appear anywhere in his own transcripts.

Position sizing – no percentage-risk rule exists

This is worth stating plainly because it’s the most-asked-about gap: there is no stated risk-per-trade percentage anywhere in Kane’s public output, and he explicitly rejects the idea: “I’m not a percentage risk person.” What he’s actually described is static contract sizing tied to account balance, not risk percentage: “I will always trade with a static size dependent on my balance… It’s not a percentage of balance, it’s normally a round figure.”

The most concrete sizing rule he’s given publicly is from his highest-profile prop-account run: exclusively 2 contracts, with profit capped at 100 ticks or the strategy’s own target, whichever came first – “if my model gave me a take profit that was 200 ticks away, as soon as I got 100 ticks in profit, I would close the trade.” Trade frequency: “one to three [trades] a day, average two. If I get two losses I’m done.”

If you see a specific risk percentage attributed to Kane anywhere (“1% risk,” “0.5R stops”), it isn’t from him – no source, including his own words, contains one.

Is Trader Kane’s strategy legit?

We looked into this honestly rather than either promoting or dismissing it, because both extremes show up in the search results already.

What’s well-corroborated: Kane’s own X post states a single Apex approval of $1,926,240, and Business Insider says it independently verified his payout “through bank statements and payout receipts” – that’s the highest-authority confirmation available. What’s important context, and frequently dropped: this wasn’t one account. By his own account, it was ~40 separate payouts across 20 copy-traded accounts (roughly $93K per account x 20), not a single-account result. His total lifetime prop payouts are quoted anywhere from “$2M+” to “$3.5MM+” depending on the source and date – we’re not going to state one figure as fact given that spread.

One claim that needs correcting: he’s frequently called “the largest payout in prop-firm history.” That was true in 2024 – it’s been surpassed at least twice since, both within days of each other in April 2025 (payouts of $2.46M and $2.55M to other traders from the same firm). “One of the largest on record” or “held the record in 2024” is the accurate framing now.

Worth disclosing directly: Kane runs propfirm.compare, an affiliate comparison site (Trustpilot categorizes it as an “Affiliate Marketing Service”) that includes his own community’s discount code. That’s a real conflict of interest to be transparent about – though, for balance, no source we found documents actual biased ranking on the site, and the firm tied to his own code doesn’t rank first in his own table.

The number worth reading in full, because no other ranking article about him currently uses it – from a May 2026 Business Insider interview, in Kane’s own words:

“It’s not the most rewarding strategy in the world. It loses just as much as it wins, but I take no shame. I’m net red on the year, which I think is okay… I’ve bought probably 500 prop accounts over my lifetime. And at some point, I’ve lost them all. There have been days where I’ve lost $100,000.”

What doesn’t exist anywhere: an independently audited track record of any kind – no MyFxBook, no public TradeZella profile, no broker statement – and no published backtest of the Lab Model, from Kane or anyone else, with a stated win rate, sample size, profit factor, or drawdown. Forum skepticism about the payout exists (an Elite Trader thread literally titled “Larp or legit?”), but we found zero claims of forged screenshots, no fraud complaints, and no regulatory action against him. It’s also worth knowing that Apex payouts like this come from simulated-account fills, paid out in real cash – Kane’s February 2026 move to live capital trading is genuinely new ground, with no published live track record yet.

Our honest read: the payout is well-corroborated by the paying counterparty and by an independent outlet. The trading edge itself is not independently verified by anyone – including Kane, who has never published a backtest of his own model.

What you can (and can’t) actually automate

Kane has addressed this directly, and it’s a genuinely useful thing for him to have said out loud:

“I think a robot could pick up the model, and if I sat down and wrote the logic out correctly it could be quite mechanical. The reality is that I think the discretion is my edge… everybody that I’ve ever taught, the first thing I say is this is going to really suck, because I can’t teach my intuition. And part of my edge is my intuition. But I can teach you the model.”

What’s genuinely codable: the 10:00 AM ET time gate, 4-hour candle high/low sweep detection, SMT pivot comparison between NQ and ES, inversion-FVG body-close detection, and stop/limit order placement at the inverted zone.

What isn’t codable from anything he’s published: a precise definition of “recent” high/low for the stop (no bar lookback, no pivot strength, no tick buffer is ever given); which swing anchors the dealing range (he manually overrides it based on where ES swept); the “A+ setup” grading standard; and the discretionary parts of his breakeven management. We’re not aware of any free public TradingView script that implements the full model end-to-end – every version, including ours, is an assembly of separately codable pieces.

We built it anyway – the PickMyTrade Lab Model Pine Script

Here’s the gap nobody else has closed: no published backtest of the Lab Model exists, from Kane or anyone writing about him. So we built a mechanical version of the parts that are actually codable, tagged every single input by source ([K] Kane-stated, [3P] third-party, [ICT] standard methodology, [PMT] our own fill-in where nothing else exists), and ran it in TradingView’s real Strategy Tester on an NQ1! chart – not a proxy, not a simulation of a simulation.

We’re calling it “Lab Model (Trader Kane style) [PickMyTrade].” To be direct about what it is: it is not an official or endorsed implementation. Kane has never published first-person written rules, so this is our own reconstruction of the mechanical pieces, built the same way the rest of this article is sourced – and it inherits every gap listed in the section above.

What we found running it for real

The first version, run with the literal published defaults (premium/discount enforced, 4H sweep required, SMT-swing stop), was a net loser: -$3,160, profit factor 0.53, 12 trades over a ~103-day NQ1! window. That’s worth stating plainly rather than burying – it matches the honest read above that nobody, including Kane, has published a working backtest.

TradingView Pine Script inputs panel showing correlation symbol, SMT pivot left and right bars, SMT valid-for bars, and session window settings
The correlation/SMT and session inputs panel – SMT pivot lookback, correlation symbol, and the session-window toggle (off by default).
TradingView Pine Script inputs panel showing 4H sweep requirement, dealing range lookback, premium and discount enforcement, and iFVG minimum size entry trigger settings
Setup gates and entry trigger inputs – the 4H sweep requirement, dealing-range lookback, and premium/discount enforcement toggle.

From there we iterated live against the real Strategy Tester report – not a spreadsheet approximation – testing which combination of the genuinely ambiguous inputs (stop placement, premium/discount enforcement, the 4H sweep gate, the iFVG minimum-size filter) turned a documented-but-untested rule set into something that actually held up. The result that survived robustness testing (checked against nearby parameter values, not just the one lucky config):

TradingView Strategy Tester key stats for the PickMyTrade Lab Model Pine Script showing net profit $11,710, profit factor 1.862, 45.71 percent win rate
The live TradingView Strategy Tester result for the tuned configuration: +$11,710 net profit, profit factor 1.862, 45.71% win rate on 35 trades.

+$11,710 net profit, profit factor 1.86, 45.7% win rate on 35 trades, max drawdown $3,165 (5.4%) – using the “Inversion zone edge” stop mode, premium/discount enforcement off, the 4H sweep gate off, and the iFVG minimum-size filter raised to 45% of average range. The whole 30-50% band of that filter tested net-positive, not just the one number, and the result held up when we lengthened the SMT pivot lookback or changed the dealing-range length.

One real fragility we found and are flagging rather than hiding: shortening the SMT swing lookback to 3 bars broke this configuration hard, in both directions, regardless of every other setting. We kept the lookback at Kane’s implied default (5 bars) and documented the finding directly in the script’s tooltip rather than quietly picking a number that happened to work.

TradingView Pine Script inputs panel showing stop placement, stop buffer, max stop points, target mode, and breakeven trigger settings
Stop, target, and trade-management inputs – stop placement mode, max stop width filter, target mode, and breakeven trigger.
TradingView Pine Script inputs panel showing contracts per trade, max trades per day, stop trading after N losses, stop trading after first win, and display toggle settings
Sizing, daily limits, and display inputs – static contract sizing, max trades per day, and the “stop after first win” rule.
TradingView strategy properties panel showing initial capital, default order size, commission, and margin settings for the Lab Model backtest
Strategy properties – initial capital, commission, and the margin_long/margin_short settings that were the fix for the “zero trades” bug below.

We’ll be honest about one bug this process caught, because it’s a genuinely useful lesson for anyone backtesting futures strategies in Pine Script: our first version showed zero trades in the Strategy Tester even though entry labels were plotting correctly on the chart. The cause was a missing margin_long/margin_short setting – without it, Pine’s broker emulator defaults to requiring roughly 100% of notional value as margin, and 2 NQ contracts (~$1.1-1.2M notional) vastly exceeded our $50,000 initial capital, so every strategy.entry() call was being silently rejected while the visual signal still rendered. If your own Strategy Tester ever shows trade labels on the chart but an empty report, check your margin settings first, and this specific failure mode – an order silently rejected rather than throwing an error – is also common outside of backtesting; see our breakdown of what happens when a single bad fill wrecks a live strategy mid-run.

NQ1 futures chart showing the Lab Model strategy long and short entry labels, stop, and target markers plotted on price action
NQ1! chart with the Lab Model’s Long and Short entry labels, stops, and targets plotted live.

The caveat we want front and center, not buried: this was tuned and validated against a single ~103-day NQ1! window, and roughly 30 parameter combinations were tested in the process, which carries real multiple-comparisons risk. Treat this as a promising, mechanically-sensible result – not a proven, durable edge. Validate it on more history and other instruments before trading it with real capital, exactly the same way you should treat every claim in this article that isn’t independently audited.

How to automate it with PickMyTrade

Once you have a Lab Model version you trust – ours, your own, or a hybrid – the model is only as good as your ability to execute it without hesitation. That’s the actual gap between “backtested strategy” and “results,” and it’s the one Kane’s own words gesture at when he talks about discretion being his edge: discretion is hard to teach, but flawless, repeatable execution of a rule set that’s already fired is not.

Our Pine Script fires a machine-readable webhook alert on every signal – symbol, side, setup type, entry, stop, target, and quantity, all in one JSON payload – designed to be caught directly by PickMyTrade and routed to your broker or prop account automatically: Tradovate, Rithmic, Interactive Brokers, TradeStation, TradeLocker, and prop firms including Apex, Topstep, Bulenox, and E8, with bracket orders (stop and target sent together), risk controls, and multi-account copying.

StepToolWhat happens
1. Load the strategyTradingView Pine EditorPaste the script below onto an NQ1! chart
2. BacktestTradingView Strategy TesterVerify the numbers yourself before risking anything
3. AlertTradingViewSet alert frequency to “Once Per Bar Close”
4. ExecutePickMyTradeAuto-places the bracket order on your broker/prop account

Before trusting any backtest – including this one – read up on walk-forward optimization and why in-sample backtests overstate an edge; it’s the same multiple-comparisons risk we flagged above about our own results. And if you want the entry logic to lean on liquidity-sweep or order-block/FVG detection independently of this specific script, our own free indicators cover both: Institutional Order Flow Indicator for TradingView and Order Block AI [PickMyTrade].

You may also like

Frequently asked questions

Is Trader Kane’s strategy legit?

His highest-profile payout – $1,926,240 from Apex – is independently verified by Business Insider through bank statements. What isn’t independently verified is the trading edge itself: no audited track record or published backtest of the Lab Model exists from any source, including Kane. Our own backtest of a mechanical reconstruction (above) was net-positive but should be treated as one data point, not proof.

What is Trader Kane’s “Lab Model” strategy?

A day-trading system for NQ futures combining SMT divergence (a correlation break between NQ and ES), inversion Fair Value Gaps as the entry trigger, and premium/discount as a directional filter – timed around the 10:00 AM ET four-hour candle open. Standard ICT concepts, assembled with Kane-specific filters and unusually aggressive breakeven management.

Can you automate Trader Kane’s strategy?

Partially. The time gate, sweep detection, SMT comparison, and inversion-FVG entry are all mechanical and codable. The dealing-range anchor, setup grading, and discretionary breakeven timing are not – Kane himself says his discretion, not the entry logic, is his actual edge. Our Pine Script codes the mechanical parts and documents every gap explicitly rather than guessing.

Is this the same as “Kane Trading” (kanetrading.com)?

No. Kane Trading is a completely unrelated Fibonacci/ABCD-pattern education site run by a trader named Jim, publishing since 2003. Trader Kane (Kane Simons) is the NQ futures scalper and co-founder of The Lab covered in this article.

What NQ points/ticks does the Lab Model target?

Kane has stated a typical stop around 25 NQ points (100 ticks), and during his highest-profile prop run he capped profit at 100 ticks or the model’s own target, whichever came first. There’s no fixed R-multiple – his own words: “I trade premium/discount… not a fixed R multiple.”

Does Trader Kane use a percentage risk-per-trade rule?

No – he explicitly rejects it: “I’m not a percentage risk person.” He uses static contract sizing tied to account balance instead. Any specific risk percentage attributed to him online is not sourced to anything he’s actually said.

The bottom line

The Lab Model is a well-specified assembly of standard ICT components – SMT divergence, inversion FVGs, premium/discount, Power of Three – sharpened by three genuinely distinctive filters: NQ-only execution with ES as the divergence reference, the 10:00 AM ET four-hour open as the timing anchor, and breakeven management aggressive enough that Kane says it’s the actual difference between the model working and not. The entry logic is mostly codifiable. The range selection, setup grading, and trade management are not – and Kane says so himself. We built the mechanical parts, tagged every assumption, backtested it for real, and are publishing the exact Pine Script and the exact numbers rather than a promise.

Want to run this yourself, or automate whatever you build on top of it? Start your PickMyTrade free trial and connect a TradingView alert to your broker or prop account in minutes.

Educational content, not financial or investment advice. Futures trading carries substantial risk of loss. All performance figures above are simulated Strategy Tester results over a single ~103-day window and are not indicative of future results. Backtest and paper-trade before risking capital.

Full Pine Script – Lab Model (Trader Kane style) [PickMyTrade], v6

Paste this directly into TradingView’s Pine Editor, load it on an NQ1! chart, and click “Add to chart.” Recommended alert setting: “Once Per Bar Close.”

//@version=6
// 
//  Lab Model (Trader Kane style) [PickMyTrade]
//  NQ day-trading strategy: SMT divergence + inversion FVG, traded from
//  premium/discount, anchored to the 10:00 ET four-hour open.
//
//  WHAT THIS IS
//  An honest, mechanical rendering of the parts of Trader Kane's publicly
//  documented "Lab Model" that can actually be coded. It is NOT an official
//  or endorsed implementation, and Kane has never published first-person
//  written rules  -  every ruleset in circulation is a third-party write-up.
//
//  SOURCE TIERS (see the tooltip on each input):
//   [K]   Kane stated it himself on camera or in an X trade post
//   [3P]  third-party reconstruction (FX Replay / Chart Fanatics write-ups)
//   [ICT] standard public ICT/SMC methodology that predates Kane
//   [PMT] PickMyTrade's own choice, because no source specifies it
//
//  WHAT KANE HAS *NOT* PUBLISHED  -  and is therefore [PMT] here:
//    any risk-per-trade percentage. He explicitly rejects percentage risk
//     ("I'm not a percentage risk person"), so the sizing below is static
//     contracts, matching his stated prop-account method  -  not a % rule.
//    a tick buffer or bar-lookback definition for "recent high/low" stops
//    which swing anchors the dealing range. He re-anchors it to where ES
//     swept liquidity, not NQ's own visible high  -  that override is
//     discretionary and is the single biggest automation blocker.
//    the "A+ setup" grading standard
//    a standard-deviation anchor (he credits STDs to Dexter, not himself)
//    a daily loss limit in $ or %, or any news/economic-calendar filter
//
//  THE TWO PUBLISHED RULESETS CONFLICT. FX Replay targets the LLT (the first
//  swing past the midpoint); Chart Fanatics targets the midpoint itself.
//  Both are published as "Kane's strategy". Both are offered below as
//  selectable variants rather than silently blended.
//
//  NON-REPAINTING: SMT pivots are used only after right-bar confirmation;
//  the 4H sweep reference reads the previous CLOSED 4H bar ([1] +
//  lookahead_off); every signal is gated on barstate.isconfirmed; the ES
//  feed is same-timeframe so it aligns 1:1 with the chart's own bars.
//
//  Kane's own view, worth reading before automating any of it:
//   "I think a robot could pick up the model, and if I sat down and wrote
//    the logic out correctly it could be quite mechanical. The reality is
//    that I think the discretion is my edge."
//
//  Educational tool. Not financial advice. Futures carry substantial risk
//  of loss. Backtest results are simulated and are not indicative of
//  future performance.
//  Recommended alert setting: "Once Per Bar Close".
// 
// margin_long/margin_short = 1: without this, Pine's broker emulator defaults to
// requiring ~100% of notional value as margin for futures. 2 NQ contracts is roughly
// $1.1-1.2M notional against a $50,000 initial_capital  -  every strategy.entry() was
// being silently rejected for insufficient margin while the plotshape signal (which
// doesn't check capital) still drew normally. That mismatch  -  visible entry labels,
// zero trades in the Strategy Tester  -  is what exposed this. 1% margin is standard
// practice for point-value futures backtesting, where real margin calls aren't being
// simulated; P&L is still computed correctly via commission_type/point value.
strategy("Lab Model (Trader Kane style) [PickMyTrade]", shorttitle = "Lab Model PMT", overlay = true, initial_capital = 50000, default_qty_type = strategy.fixed, default_qty_value = 2, commission_type = strategy.commission.cash_per_contract, commission_value = 2.50, slippage = 2, pyramiding = 0, calc_on_every_tick = false, margin_long = 1, margin_short = 1, max_boxes_count = 100, max_lines_count = 100, max_labels_count = 300)

// 
// CONSTANTS
// 
var string TZ_NY      = "America/New_York"
var int    MAX_FVG    = 20
var int    MAX_PIVOTS = 12

var string G_CORR  = "Correlation / SMT"
var string G_SESS  = "Session & Timing"
var string G_SETUP = "Setup Gates"
var string G_ENTRY = "Entry Trigger"
var string G_EXIT  = "Stop & Target"
var string G_MGMT  = "Trade Management"
var string G_SIZE  = "Sizing & Daily Limits"
var string G_DISP  = "Display"

// 
// INPUTS
// 
i_corrSym    = input.symbol("CME_MINI:ES1!", "Correlation symbol (SMT reference)", group = G_CORR, tooltip = "[K] Kane trades NQ only and uses ES purely as a divergence reference: 'I'm only trading NASDAQ. Obviously I look at the SP500 ES for divergence.' He explicitly rejects YM: 'I don't think YM provides any confirmational bias to my model.' Load this strategy on an NQ chart and leave ES here.")
i_smtLeft    = input.int(5, "SMT pivot  -  left bars", minval = 1, maxval = 50, group = G_CORR, tooltip = "[PMT] No source states the swing-detection lookback Kane uses, or even whether he reads SMT pivots on the execution timeframe or a higher one. This is a PickMyTrade default, not his number. Larger = fewer, more significant divergences. Live testing found the profitable configuration documented in i_reqPD's tooltip breaks hard if this is lowered to 3  -  keep at or above the 5-bar default.")
i_smtRight   = input.int(5, "SMT pivot  -  right bars", minval = 1, maxval = 50, group = G_CORR, tooltip = "[PMT] Bars required to the right before a swing is confirmed. This is what makes the signal non-repainting  -  and it also means entries arrive this many bars after the actual swing. Lower = faster but noisier. Live testing found the profitable configuration documented in i_reqPD's tooltip breaks hard if this is lowered to 3  -  keep at or above the 5-bar default.")
i_smtMaxAge  = input.int(40, "SMT valid for (bars)", minval = 5, maxval = 300, group = G_CORR, tooltip = "[PMT] How long a confirmed SMT divergence stays eligible to pair with an inversion entry. Kane requires SMT and the iFVG together but states no expiry; without one, a stale divergence could arm a trade hours later.")

i_useSession = input.bool(false, "Restrict to session window", group = G_SESS, tooltip = "[PMT] Off by default. Backtesting the full confluence stack (SMT + 4H sweep + iFVG + premium/discount) against Kane's literal 09:15-11:30 ET window produced 0-1 trades across ~68 days of 5m NQ/ES data  -  too rare to judge. With this OFF (same window, sweep, SMT, and premium/discount logic, just not clocked to those 2h15m) the identical signal fired 22 times over the same window with a positive result (net +$12,980, 31.8% win rate, ~5:1 avg win:loss). Turn this back ON to trade Kane's literal stated window once you have enough live/tick data to judge it properly  -  the gate itself is unchanged, only the default.")
i_session    = input.session("0915-1130", "Session window (ET)", group = G_SESS, tooltip = "[K] 0915-1130 is Kane's own stated window: '9 around 9:15 to around 11:30 EST, because it has that 9:30 liquidity injection, so it's most volatile.' [3P] 1000-1300 is the backtesting community's convention, originating with FX Replay's write-up  -  NOT a number Kane has given. His stated window has also drifted across 2024-2026 interviews.")
i_skipMonFri = input.bool(false, "Skip Mondays and Fridays", group = G_SESS, tooltip = "[K] Ambiguous in the sources. Nov 2024 he excludes Mon/Fri from LONDON only; May 2025 he says 'I don't really trade Mondays and Fridays' with no session qualifier. Off by default because the two statements cannot be reconciled.")
i_flatBySess = input.bool(true, "Flatten at session end", group = G_SESS, tooltip = "[PMT] No source gives Kane a hard flat-by time. He does say 'once that time comes like 11:00 EST, I'm off'  -  but that is about leaving the desk, not a stated exit rule. On by default because an unmanaged overnight futures position is a materially different risk profile.")

i_useSweep   = input.bool(false, "Reversal: require 4H sweep", group = G_SETUP, tooltip = "[3P][PMT] 'Wait for ES and/or NQ to sweep the 10am 4hr candle H/L away from your desired trade direction, followed by SMT and an iFVG.' Note this gate applies to the REVERSAL setup only  -  the continuation setup carries no sweep precondition. Default changed to OFF: live Strategy Tester validation (NQ1!, ~103-day window, 1 symbol) found this gate combined with premium/discount filtering removed most of the trade sample without improving results  -  see i_reqPD tooltip for the full finding.")
i_sweepSrc   = input.string("ES or NQ", "Sweep may occur on", options = ["ES or NQ", "NQ only", "ES only"], group = G_SETUP, tooltip = "[3P] The published rule says the sweep can happen on either instrument, not necessarily the one being traded.")
i_useSetup   = input.string("Both", "Setups enabled", options = ["Both", "Reversal only", "Continuation only"], group = G_SETUP, tooltip = "[3P] Reversal = sweep the prior 4H H/L against your direction, then SMT + iFVG. Continuation = a lower-timeframe range has rebalanced while a higher-timeframe imbalance still sits in the same direction, then SMT + iFVG.")
i_rangeLen   = input.int(60, "Dealing range lookback (bars)", minval = 10, maxval = 500, group = G_SETUP, tooltip = "[PMT] The mechanical stand-in for a genuinely discretionary step. Kane re-anchors the range to where ES swept liquidity rather than NQ's own visible extreme: 'because ES actually took the high here, in my head this high right here is the high.' No published source defines this mechanically  -  this lookback is an approximation and is the weakest link in the whole model.")
i_reqPD      = input.bool(false, "Enforce premium/discount direction", group = G_SETUP, tooltip = "[K][PMT] 'Ultimately I cannot long here in the premium  -  just can't, my model doesn't allow me to do it.' Longs only from discount (below equilibrium), shorts only from premium. Default changed to OFF: live Strategy Tester iteration against real NQ1! data (margin_long/margin_short bug fixed first  -  see strategy() declaration) found the ON default produced a net LOSING baseline (-$3,160, PF 0.53, 12 trades over ~103 days). Turning this off, turning off i_useSweep, setting i_stopMode to 'Inversion zone edge', and raising i_fvgMinDev to 45% turned it net positive: +$11,710, PF 1.86, 35 trades, 45.7% win rate. The whole 30-50% band of i_fvgMinDev is positive (not just 45%), and the result holds up under perturbation of dealing-range length (i_rangeLen 40/90) and a longer SMT lookback (7/7 bars, still +$7,080). Caveat / known fragility: shortening SMT lookback to 3/3 bars breaks this hard regardless of other settings (net -$5,000 to -$7,500)  -  do not set i_smtLeft/i_smtRight below the 5-bar default. Also caveat: this was tuned and validated against a single ~103-day NQ1! window with roughly 30 configurations tested, so multiple-comparisons risk is real  -  treat this as a promising, not proven, edge, and validate on more history / other instruments before trading it live.")

i_entryMode  = input.string("On inversion close", "Entry trigger", options = ["On inversion close", "Limit retap into zone"], group = G_ENTRY, tooltip = "[K] He describes both: 'I will either limit a retap into this area or I'll sell stop this with my invalidation here.' He states NO rule for choosing between them  -  the 'use a stop order if price is moving fast' conditional often quoted is FX Replay's addition, not his.")
i_fvgMinDev  = input.float(45.0, "iFVG minimum size (% of avg range)", minval = 0.0, maxval = 300.0, step = 5.0, group = G_ENTRY, tooltip = "[ICT][PMT] Standard noise filter  -  gaps smaller than this share of the 14-bar average range are ignored. Self-scaling, so it transfers across timeframes. Kane states no minimum gap size anywhere. Default raised 30->45: live Strategy Tester sweep (NQ1!, ~103 days, all other inputs at default) found the whole 30-50% band net positive  -  30%: +$7,120/PF1.26/54tr, 40%: +$5,320/PF1.26/45tr, 45%: +$11,710/PF1.86/35tr, 50%: +$7,870/PF1.76/26tr  -  with 45% the strongest and still solid under dealing-range perturbation (RangeLen 40: +$10,170; RangeLen 90: +$5,940). 60%+ over-filters and turns net negative (16-21 trades, PF<0.5). See i_reqPD tooltip for the full methodology and the one fragility this family has (SMT lookback).")
i_fvgMaxAge  = input.int(60, "FVG valid for (bars)", minval = 5, maxval = 500, group = G_ENTRY, tooltip = "[PMT] How long an unfilled FVG stays tracked as a candidate for inversion.")
i_retapBars  = input.int(12, "Retap limit valid for (bars)", minval = 1, maxval = 100, group = G_ENTRY, tooltip = "[K] 'The only time I'm ever really cancelling my order is if my model tells me that I should be break even if I was in the trade. If it's not, then my limit just sits there.' [PMT] A bar cap is added here because an uncancelled resting limit is not backtestable.")

i_stopMode   = input.string("Inversion zone edge", "Stop placement", options = ["SMT swing", "Inversion zone edge", "Recent swing H/L"], group = G_EXIT, tooltip = "[K][PMT] Four different formulations exist and he has never reconciled them: 'my stop loss at divergence'; 'stop below local SMT' (reversal X post); 'stops below the inversion' (continuation X post); [3P] 'recent H/L or invalidation of SMT'. All three are offered  -  pick one deliberately. Default changed to 'Inversion zone edge': the only one of the three that produced a robust net-positive result in live Strategy Tester iteration (see i_reqPD tooltip for the full methodology/caveats). 'Recent swing H/L' showed even stronger ratios (PF 2.4-2.7) but only on 6-7 trades  -  too small a sample to trust; worth exploring further with more data before switching to it.")
i_stopBuf    = input.float(2.0, "Stop buffer (ticks)", minval = 0.0, maxval = 100.0, step = 1.0, group = G_EXIT, tooltip = "[PMT] No source gives any tick offset beyond the structural level. This is PickMyTrade's, not Kane's.")
i_maxStopPt  = input.float(35.0, "Skip trade if stop wider than (NQ points)", minval = 0.0, maxval = 500.0, step = 5.0, group = G_EXIT, tooltip = "[K] 'Do you typically try to keep the stop loss around the same size  -  yeah, around 25 points, 100 tick.' Asked about a 50-point stop he answered 'No... I missed the move, in my head.' Set 0 to disable the filter.")
i_tgtMode    = input.string("LLT (first swing past 50%)", "Target", options = ["LLT (first swing past 50%)", "Equilibrium (50% of range)", "Fixed points"], group = G_EXIT, tooltip = "THE TWO PUBLISHED RULESETS CONFLICT HERE. [3P] FX Replay: TP is the LLT, 'the first high or low through the premium/discount midline'  -  a swing BEYOND the midpoint. [3P] Chart Fanatics/TradeZella: 'Target: the 50% level of the impulse move.' The LLT and the midpoint are materially different exits. Kane's own words support both framings at different times.")
i_tgtPts     = input.float(25.0, "Fixed target (NQ points)", minval = 1.0, step = 5.0, group = G_EXIT, tooltip = "[K] Only used in Fixed points mode. 25 points = 100 ticks, matching his record-run rule: 'I aimed for 100 ticks or the take profit that my model gave me  -  so if my model gave me a take profit that was 200 ticks away, as soon as I got 100 ticks in profit, I would close the trade.'")
i_capAt100   = input.bool(false, "Cap profit at 100 ticks", group = G_EXIT, tooltip = "[K] The record-run rule above, applied on top of the structural target: whichever comes first. This is the single most concrete exit rule he has stated publicly, and it applied specifically to the first ~50 days of the Apex account where he says 95% of the profit came from.")

i_beMode     = input.string("HTF candle flip", "Breakeven trigger", options = ["Off", "HTF candle flip", "R multiple", "Halfway to target"], group = G_MGMT, tooltip = "[K] 'It's only profitable when the break evens are managed correctly. People that don't do this correctly that pick up my model will consistently tell me the model is not profitable.' He gives FOUR different triggers across sources  -  hourly candle flip, 15m low taken, on the sweep, hourly low swept. 'Halfway to target' is the most-quoted rule in secondary coverage and is the LEAST authentic: it is FX Replay's own explicit mechanization, and the word 'halfway' appears nowhere in his transcripts.")
i_beHtf      = input.timeframe("60", "Breakeven reference timeframe", group = G_MGMT, tooltip = "[K] 'This is the area that I would want to go break even, because this hourly candle just flipped bearish. I don't care that it's 1R, I just care that this hourly candle flipped bearish.'")
i_beR        = input.float(1.0, "Breakeven at R multiple", minval = 0.1, maxval = 5.0, step = 0.1, group = G_MGMT, tooltip = "[PMT] Only used in R multiple mode. Kane explicitly does NOT use an R trigger  -  quoted above.")
i_beOffset   = input.float(1.0, "Breakeven offset (ticks)", minval = -20.0, maxval = 20.0, step = 1.0, group = G_MGMT, tooltip = "[K] 'I will just trail my stop to break just UNDER break even so that I'm not losing any money due to fees.' Positive values sit the stop slightly better than entry.")

i_qty        = input.int(2, "Contracts per trade", minval = 1, maxval = 100, group = G_SIZE, tooltip = "[K] Static sizing, matching his stated prop method: 'I will always trade with a static size dependent on my balance. It's not a percentage of balance, it's normally a round figure.' 2 is his record-run number: 'I pretty much exclusively traded two contracts the entire time.' NOTE: he explicitly rejects percentage risk, so this strategy deliberately offers no % risk model.")
i_maxTrades  = input.int(3, "Max trades per day", minval = 1, maxval = 20, group = G_SIZE, tooltip = "[K] 'How many trades do you typically take a day? One to three, average two.'")
i_maxLoss    = input.int(2, "Stop trading after N losses", minval = 1, maxval = 10, group = G_SIZE, tooltip = "[K] 'If I get two losses I'm done.'")
i_stopOnWin  = input.bool(true, "Stop trading after first win", group = G_SIZE, tooltip = "[K] 'One win, done  -  no matter how much that win is.' This is unusual and materially changes the equity curve, so it is exposed rather than hardcoded.")

i_showFvg    = input.bool(true, "Show FVG / inversion zones", group = G_DISP)
i_showSmt    = input.bool(true, "Show SMT markers",           group = G_DISP)
i_showRange  = input.bool(true, "Show dealing range & 50%",   group = G_DISP)
i_showH4     = input.bool(true, "Show prior 4H high/low",     group = G_DISP)
i_showDash   = input.bool(true, "Show dashboard",             group = G_DISP)

// 
// FUNCTIONS
// 

// JSON-safe number: emits null rather than "NaN" so webhook consumers don't choke.
f_jnum(_v) =>
    na(_v) ? "null" : str.tostring(_v, format.mintick)

// 
// CALCULATIONS  -  correlation and higher-timeframe feeds
// 
// Same-timeframe, different-symbol request: bars align 1:1 with the chart's own,
// so no [1] offset is needed. Every signal is gated on barstate.isconfirmed
// downstream, which is what keeps the realtime bar honest.
[esH, esL, esC] = request.security(i_corrSym, timeframe.period, [high, low, close], lookahead = barmerge.lookahead_off)

// Prior CLOSED 4-hour bar. The [1] offset plus lookahead_off is the house
// non-repainting convention: if the offset were ever dropped the failure mode is
// a visible repaint, not a hidden future leak.
[h4High, h4Low] = request.security(syminfo.tickerid, "240", [high[1], low[1]], lookahead = barmerge.lookahead_off)

// ES needs its OWN 4H reference levels. ES and NQ trade at completely different
// price scales (ES ~6,000 vs NQ ~21,000), so testing an ES price against NQ's 4H
// high would make one side of the sweep permanently true and the other
// permanently false.
[esH4High, esH4Low] = request.security(i_corrSym, "240", [high[1], low[1]], lookahead = barmerge.lookahead_off)

// Breakeven reference candle, same convention. Guarded so a reference timeframe at
// or below the chart's own cannot turn this into a lower-timeframe request, whose
// value would not be reproducible bar-for-bar.
beTf = timeframe.in_seconds(i_beHtf) > timeframe.in_seconds(timeframe.period) ? i_beHtf : timeframe.period
[beOpen, beClose] = request.security(syminfo.tickerid, beTf, [open[1], close[1]], lookahead = barmerge.lookahead_off)

// 
// CALCULATIONS  -  session gating
// 
// Explicit IANA timezone. Session strings without one are read in the EXCHANGE's
// clock, which on CME futures is Chicago  -  the classic "my window is an hour off" bug.
inSession   = not i_useSession or not na(time(timeframe.period, i_session, TZ_NY))
dayOfWeekNy = dayofweek(time, TZ_NY)
dayOk       = not i_skipMonFri or (dayOfWeekNy != dayofweek.monday and dayOfWeekNy != dayofweek.friday)
newDay      = ta.change(time("D")) != 0
sessionEnd  = i_useSession and not inSession and inSession[1]

// 
// CALCULATIONS  -  SMT divergence (NQ vs ES)
// 
ph = ta.pivothigh(high, i_smtLeft, i_smtRight)
pl = ta.pivotlow(low, i_smtLeft, i_smtRight)

var float prevNqPH = na
var float prevEsPH = na
var float prevNqPL = na
var float prevEsPL = na

bool bearSmtRaw = false
bool bullSmtRaw = false

// Bearish SMT: NQ prints a HIGHER high while ES fails to. Kane's own reading:
// "if ES is more bearish and Nasdaq's pulling higher I would typically treat
// that as NASDAQ manipulation."
// Guarded on isconfirmed: ta.pivothigh's rightmost comparison bar is the CURRENT
// bar, whose high is still forming, so a pivot can flicker intrabar. These are var
// state and would not roll back with the bar.
if barstate.isconfirmed and not na(ph)
    float esPH = esH[i_smtRight]
    if not na(prevNqPH) and not na(esPH) and not na(prevEsPH)
        bearSmtRaw := ph > prevNqPH and esPH < prevEsPH
    prevNqPH := ph
    prevEsPH := esPH

// Bullish SMT: NQ prints a LOWER low while ES holds  -  NQ manipulation down.
if barstate.isconfirmed and not na(pl)
    float esPL = esL[i_smtRight]
    if not na(prevNqPL) and not na(esPL) and not na(prevEsPL)
        bullSmtRaw := pl < prevNqPL and esPL > prevEsPL
    prevNqPL := pl
    prevEsPL := esPL

// Latch the most recent divergence plus the swing level that produced it  - 
// that level is what the "stop at divergence" mode uses.
var int   bearSmtBar = na
var float bearSmtLvl = na
var int   bullSmtBar = na
var float bullSmtLvl = na

if bearSmtRaw
    bearSmtBar := bar_index
    bearSmtLvl := ph
if bullSmtRaw
    bullSmtBar := bar_index
    bullSmtLvl := pl

bearSmtActive = not na(bearSmtBar) and bar_index - bearSmtBar <= i_smtMaxAge
bullSmtActive = not na(bullSmtBar) and bar_index - bullSmtBar <= i_smtMaxAge

// 
// CALCULATIONS  -  dealing range, premium / discount
// 
// [PMT] Mechanical stand-in for a discretionary step. Kane overrides the visible
// NQ extreme when ES was the instrument that actually swept the liquidity.
rngHigh    = ta.highest(high, i_rangeLen)
rngLow     = ta.lowest(low, i_rangeLen)
rngEq      = (rngHigh + rngLow) / 2.0
inPremium  = close > rngEq
inDiscount = close < rngEq

// 
// CALCULATIONS  -  FVG tracking and inversion detection
// 
var array<float> fvgTop = array.new<float>()
var array<float> fvgBot = array.new<float>()
var array<int>   fvgDir = array.new<int>()     //  1 = bullish FVG,  -1 = bearish FVG
var array<int>   fvgBar = array.new<int>()

avgRange = ta.sma(high - low, 14)
minGap   = avgRange * i_fvgMinDev / 100.0

// [ICT] Three-candle imbalance. Bullish: candle 3's low sits above candle 1's high.
bullFvgNew = low > high[2] and (low - high[2]) >= minGap
bearFvgNew = high < low[2] and (low[2] - high) >= minGap

if barstate.isconfirmed
    if bullFvgNew
        array.push(fvgTop, low)
        array.push(fvgBot, high[2])
        array.push(fvgDir, 1)
        array.push(fvgBar, bar_index)
    if bearFvgNew
        array.push(fvgTop, low[2])
        array.push(fvgBot, high)
        array.push(fvgDir, -1)
        array.push(fvgBar, bar_index)
    // Bound the pool so the arrays never grow without limit.
    while array.size(fvgTop) > MAX_FVG
        array.shift(fvgTop)
        array.shift(fvgBot)
        array.shift(fvgDir)
        array.shift(fvgBar)

// [ICT] Inversion: an FVG that price BODY-CLOSES through flips polarity. A bullish
// FVG closed below becomes resistance; a bearish FVG closed above becomes support.
// Kane's own gloss: "an inversion is just a previous gap that should be used as
// support gets traded below and used as resistance." The body close is the
// diagnostic  -  a wick piercing is not enough.
bool  bearInvNow = false     // bullish FVG inverted -> resistance -> short candidate
bool  bullInvNow = false     // bearish FVG inverted -> support    -> long  candidate
float bearInvTop = na
float bearInvBot = na
float bullInvTop = na
float bullInvBot = na

if barstate.isconfirmed and array.size(fvgTop) > 0
    for i = array.size(fvgTop) - 1 to 0
        int   dir = array.get(fvgDir, i)
        float top = array.get(fvgTop, i)
        float bot = array.get(fvgBot, i)
        int   age = bar_index - array.get(fvgBar, i)
        if age > i_fvgMaxAge
            array.remove(fvgTop, i)
            array.remove(fvgBot, i)
            array.remove(fvgDir, i)
            array.remove(fvgBar, i)
        else if dir == 1 and close < bot
            // Keep the NEAREST inverted zone, not simply the last one visited.
            // The loop runs newest-to-oldest, so without this test the oldest and
            // furthest zone would win  -  and it is this level that prices the stop
            // and the retap limit.
            if not bearInvNow or bot < bearInvBot
                bearInvTop := top
                bearInvBot := bot
            bearInvNow := true
            array.remove(fvgTop, i)
            array.remove(fvgBot, i)
            array.remove(fvgDir, i)
            array.remove(fvgBar, i)
        else if dir == -1 and close > top
            if not bullInvNow or top > bullInvTop
                bullInvTop := top
                bullInvBot := bot
            bullInvNow := true
            array.remove(fvgTop, i)
            array.remove(fvgBot, i)
            array.remove(fvgDir, i)
            array.remove(fvgBar, i)

// 
// CALCULATIONS  -  4H sweep gate (reversal setup only)
// 
var bool sweptAbove = false
var bool sweptBelow = false

// Reset on a new day AND whenever the 4H reference itself rolls (10:00, 14:00,
// 18:00, 22:00, 02:00, 06:00 ET). Without the second test, an overnight sweep of
// a completely different 4H candle would still be arming the 10:00 setup.
h4Rolled = ta.change(h4High) != 0 or ta.change(h4Low) != 0

if newDay or h4Rolled
    sweptAbove := false
    sweptBelow := false

nqSweepUp = not na(h4High) and high > h4High
nqSweepDn = not na(h4Low) and low < h4Low
esSweepUp = not na(esH4High) and not na(esH) and esH > esH4High
esSweepDn = not na(esH4Low) and not na(esL) and esL < esH4Low

sweepUp = switch i_sweepSrc
    "NQ only" => nqSweepUp
    "ES only" => esSweepUp
    => nqSweepUp or esSweepUp

sweepDn = switch i_sweepSrc
    "NQ only" => nqSweepDn
    "ES only" => esSweepDn
    => nqSweepDn or esSweepDn

if sweepUp
    sweptAbove := true
if sweepDn
    sweptBelow := true

// 
// CALCULATIONS  -  continuation setup
// 
// [3P] "Wait for a LTF range to re-balance, while there is still a HTF imbalance
// to target in the same direction." Rendered mechanically as: price has traded
// back through the local equilibrium (rebalanced) while an unfilled FVG in the
// intended direction still sits ahead of it.
ltfEq        = (ta.highest(high, 20) + ta.lowest(low, 20)) / 2.0
rebalancedUp = close > ltfEq and low <= ltfEq
rebalancedDn = close < ltfEq and high >= ltfEq

// "Still a HTF imbalance to target in the same direction" = an unfilled gap
// sitting AHEAD of price in the direction of the intended trade.
// A long targets an unfilled BEARISH gap above price (a gap-down that has not
// been traded back through); a short targets an unfilled BULLISH gap below.
// Testing the same-polarity gap instead would be unreachable by construction  - 
// a bullish gap only sits above price once price has already closed below it,
// and that close is exactly what removes it from the pool as an inversion.
bool htfImbalUp = false
bool htfImbalDn = false

if array.size(fvgTop) > 0
    for i = 0 to array.size(fvgTop) - 1
        if array.get(fvgDir, i) == -1 and array.get(fvgTop, i) > close
            htfImbalUp := true
        if array.get(fvgDir, i) == 1 and array.get(fvgBot, i) < close
            htfImbalDn := true

// 
// CALCULATIONS  -  swing pivot pool, used for the LLT target
// 
var array<float> pivHi = array.new<float>()
var array<float> pivLo = array.new<float>()

// Guarded on isconfirmed for the same reason the FVG pool is: var arrays do not
// roll back with the realtime bar, so an unconfirmed push would persist.
if barstate.isconfirmed and not na(ph)
    array.push(pivHi, ph)
    if array.size(pivHi) > MAX_PIVOTS
        array.shift(pivHi)
if barstate.isconfirmed and not na(pl)
    array.push(pivLo, pl)
    if array.size(pivLo) > MAX_PIVOTS
        array.shift(pivLo)

// [3P] "LLT is the first high or low through the premium/discount midline."
// Resolved as the nearest confirmed swing sitting on the far side of equilibrium.
float lltShort = na
if array.size(pivLo) > 0
    for i = 0 to array.size(pivLo) - 1
        float v = array.get(pivLo, i)
        if v < rngEq and (na(lltShort) or v > lltShort)
            lltShort := v

float lltLong = na
if array.size(pivHi) > 0
    for i = 0 to array.size(pivHi) - 1
        float v = array.get(pivHi, i)
        if v > rngEq and (na(lltLong) or v < lltLong)
            lltLong := v

// 
// CALCULATIONS  -  daily trade limits
// 
var int tradesToday = 0
var int winsToday   = 0
var int lossToday   = 0
var int lastClosed  = 0

if newDay
    tradesToday := 0
    winsToday   := 0
    lossToday   := 0

if strategy.closedtrades > lastClosed
    for i = lastClosed to strategy.closedtrades - 1
        float p = strategy.closedtrades.profit(i)
        if p > 0
            winsToday := winsToday + 1
        else if p < 0
            lossToday := lossToday + 1
    lastClosed := strategy.closedtrades

// Count a trade when it actually FILLS, not when the order is placed  -  otherwise
// unfilled retap limits would eat the daily budget. Counting opened trades rather
// than a position_size transition also catches a trade that opens and closes
// inside a single bar, which a bar-boundary test would miss.
var int lastOpened = 0
if strategy.opentrades + strategy.closedtrades > lastOpened
    tradesToday := tradesToday + (strategy.opentrades + strategy.closedtrades - lastOpened)
    lastOpened  := strategy.opentrades + strategy.closedtrades

limitsOk = tradesToday < i_maxTrades and lossToday < i_maxLoss and (not i_stopOnWin or winsToday < 1)

// 
// CALCULATIONS  -  setup assembly
// 
allowRev = i_useSetup != "Continuation only"
allowCon = i_useSetup != "Reversal only"

// SMT and iFVG are an AND gate with no sequencing constraint  -  either may come
// first. [3P] "It doesn't matter the order, but both are required."
revShort = allowRev and bearSmtActive and bearInvNow and (not i_useSweep or sweptAbove)
revLong  = allowRev and bullSmtActive and bullInvNow and (not i_useSweep or sweptBelow)

// The continuation setup carries NO sweep precondition  -  that gate is the
// reversal's alone. Most secondary summaries blur this.
conShort = allowCon and bearSmtActive and bearInvNow and rebalancedDn and htfImbalDn
conLong  = allowCon and bullSmtActive and bullInvNow and rebalancedUp and htfImbalUp

pdOkShort = not i_reqPD or inPremium
pdOkLong  = not i_reqPD or inDiscount
baseOk    = inSession and dayOk and limitsOk and barstate.isconfirmed and strategy.position_size == 0

shortSetup = baseOk and pdOkShort and (revShort or conShort)
longSetup  = baseOk and pdOkLong and (revLong or conLong)

// 
// CALCULATIONS  -  stop and target levels
// 
tickBuf  = i_stopBuf * syminfo.mintick
recentHi = ta.highest(high, 10)
recentLo = ta.lowest(low, 10)

shortStopRaw = switch i_stopMode
    "SMT swing"           => bearSmtLvl
    "Inversion zone edge" => bearInvTop
    => recentHi

longStopRaw = switch i_stopMode
    "SMT swing"           => bullSmtLvl
    "Inversion zone edge" => bullInvBot
    => recentLo

shortStop = na(shortStopRaw) ? recentHi + tickBuf : shortStopRaw + tickBuf
longStop  = na(longStopRaw) ? recentLo - tickBuf : longStopRaw - tickBuf

// In retap mode the working order rests at the inverted zone's near edge. Each
// direction resolves its OWN reference  -  a single shared entryRef would price the
// long off the short's zone whenever both setups evaluate on the same bar.
isRetap     = i_entryMode == "Limit retap into zone"
shortEntryRef = isRetap ? nz(bearInvBot, close) : close
longEntryRef  = isRetap ? nz(bullInvTop, close) : close

shortTgtRaw = switch i_tgtMode
    "LLT (first swing past 50%)" => nz(lltShort, rngEq)
    "Equilibrium (50% of range)" => rngEq
    => shortEntryRef - i_tgtPts

longTgtRaw = switch i_tgtMode
    "LLT (first swing past 50%)" => nz(lltLong, rngEq)
    "Equilibrium (50% of range)" => rngEq
    => longEntryRef + i_tgtPts

// [K] The 100-tick cap from the record run, applied as "whichever comes first".
capDist  = 100 * syminfo.mintick
shortTgt = i_capAt100 ? math.max(shortTgtRaw, shortEntryRef - capDist) : shortTgtRaw
longTgt  = i_capAt100 ? math.min(longTgtRaw, longEntryRef + capDist) : longTgtRaw

// SIGNED risk, not math.abs. A structural stop can resolve on the wrong side of
// entry (e.g. an SMT swing that price has already traded through); abs() would let
// that pass the width filter and then submit a stop the market is already beyond,
// which fills instantly at a loss.
shortRiskPt = shortStop - shortEntryRef
longRiskPt  = longEntryRef - longStop

stopPolarityOkShort = shortRiskPt > syminfo.mintick
stopPolarityOkLong  = longRiskPt > syminfo.mintick

// [K] He skips a trade whose structural stop is too wide, rather than sizing down.
stopOkShort = stopPolarityOkShort and (i_maxStopPt <= 0 or shortRiskPt <= i_maxStopPt)
stopOkLong  = stopPolarityOkLong and (i_maxStopPt <= 0 or longRiskPt <= i_maxStopPt)

// Reject any setup whose target resolves on the wrong side of entry  -  a structural
// target can sit behind price when the range has already been travelled.
tgtOkShort = shortTgt < shortEntryRef - syminfo.mintick
tgtOkLong  = longTgt > longEntryRef + syminfo.mintick

fireShortRaw = shortSetup and stopOkShort and tgtOkShort
fireLongRaw  = longSetup and stopOkLong and tgtOkLong

// With the premium/discount gate disabled both directions can qualify on one bar.
// Take neither rather than silently letting evaluation order pick a side.
bothFired = fireShortRaw and fireLongRaw
fireShort = fireShortRaw and not bothFired
fireLong  = fireLongRaw and not bothFired

// 
// STRATEGY CALLS
// 
var float actStop  = na
var float actTgt   = na
var bool  beDone   = false
var int   orderBar = na

if fireShort
    if isRetap
        strategy.entry("Short", strategy.short, qty = i_qty, limit = shortEntryRef, comment = "Lab Short")
    else
        strategy.entry("Short", strategy.short, qty = i_qty, comment = "Lab Short")
    actStop  := shortStop
    actTgt   := shortTgt
    beDone   := false
    orderBar := bar_index

if fireLong
    if isRetap
        strategy.entry("Long", strategy.long, qty = i_qty, limit = longEntryRef, comment = "Lab Long")
    else
        strategy.entry("Long", strategy.long, qty = i_qty, comment = "Lab Long")
    actStop  := longStop
    actTgt   := longTgt
    beDone   := false
    orderBar := bar_index

// Cancel a resting retap limit once it goes stale. This fires an alert too  -  the
// entry payload above told the broker to place a working order, so the cancel has
// to reach it as well or the backtest and the live account diverge.
if isRetap and strategy.position_size == 0 and not na(orderBar) and bar_index - orderBar > i_retapBars
    strategy.cancel("Short")
    strategy.cancel("Long")
    orderBar := na
    alert('{"src":"Lab Model [PickMyTrade]","sym":"' + syminfo.tickerid + '","tf":"' + timeframe.period + '","action":"cancel_working_orders","reason":"retap_expired"}', alert.freq_once_per_bar_close)

//  Breakeven management 
// [K] "It's only profitable when the break evens are managed correctly."
inLong  = strategy.position_size > 0
inShort = strategy.position_size < 0
entryPx = strategy.position_avg_price

// A FLIP is a change of state, not the state itself. "beClose < beOpen" is merely
// "the last closed hourly bar was red"  -  true for a whole hour, so it would fire
// breakeven on the first confirmed bar of almost every short and collapse the trade
// to a scratch before the structural stop was ever tested.
htfBear     = not na(beOpen) and not na(beClose) and beClose < beOpen
htfBull     = not na(beOpen) and not na(beClose) and beClose > beOpen
htfFlipBear = htfBear and not htfBear[1]
htfFlipBull = htfBull and not htfBull[1]

// Breakeven may only ever IMPROVE the stop, so it also requires the trade to be in
// profit by at least the offset. Otherwise moving the stop to entry would place it
// on the wrong side of the market and force an immediate exit.
beOffPx      = i_beOffset * syminfo.mintick
inProfitLong = not na(entryPx) and close > entryPx + beOffPx
inProfitShort= not na(entryPx) and close < entryPx - beOffPx

beTrigLong = switch i_beMode
    "HTF candle flip"   => htfFlipBull
    "R multiple"        => not na(actStop) and close >= entryPx + (entryPx - actStop) * i_beR
    "Halfway to target" => not na(actTgt) and close >= entryPx + (actTgt - entryPx) * 0.5
    => false

beTrigShort = switch i_beMode
    "HTF candle flip"   => htfFlipBear
    "R multiple"        => not na(actStop) and close <= entryPx - (actStop - entryPx) * i_beR
    "Halfway to target" => not na(actTgt) and close <= entryPx - (entryPx - actTgt) * 0.5
    => false

if inLong and not beDone and beTrigLong and inProfitLong and barstate.isconfirmed
    actStop := entryPx + beOffPx
    beDone  := true

if inShort and not beDone and beTrigShort and inProfitShort and barstate.isconfirmed
    actStop := entryPx - beOffPx
    beDone  := true

// Submit the bracket unconditionally rather than gating on an already-open
// position. strategy.exit is a no-op when its from_entry has no position, and
// gating on position_size would leave the entire fill bar unprotected  -  the
// backtest would run that bar with no stop while a live account already has one.
if not na(actStop) and not na(actTgt)
    strategy.exit("X-L", from_entry = "Long", stop = actStop, limit = actTgt, comment_loss = "Stop", comment_profit = "Target")
    strategy.exit("X-S", from_entry = "Short", stop = actStop, limit = actTgt, comment_loss = "Stop", comment_profit = "Target")

// [PMT] Session-end flatten  -  see the input tooltip for why this is ours, not his.
if i_flatBySess and sessionEnd and strategy.position_size != 0
    strategy.close_all(comment = "Session end")

if strategy.position_size == 0 and strategy.position_size[1] != 0
    actStop  := na
    actTgt   := na
    beDone   := false
    orderBar := na

// 
// VISUALS
// 
plot(i_showRange ? rngHigh : na, "Range High", color = color.new(color.gray, 40), style = plot.style_linebr)
plot(i_showRange ? rngLow : na, "Range Low", color = color.new(color.gray, 40), style = plot.style_linebr)
plot(i_showRange ? rngEq : na, "Equilibrium (50%)", color = color.new(color.orange, 20), style = plot.style_linebr, linewidth = 2)
plot(i_showH4 and not na(h4High) ? h4High : na, "Prior 4H High", color = color.new(color.blue, 30), style = plot.style_linebr)
plot(i_showH4 and not na(h4Low) ? h4Low : na, "Prior 4H Low", color = color.new(color.blue, 30), style = plot.style_linebr)
plot(strategy.position_size != 0 ? actStop : na, "Active Stop", color = color.new(color.red, 0), style = plot.style_linebr, linewidth = 2)
plot(strategy.position_size != 0 ? actTgt : na, "Active Target", color = color.new(color.green, 0), style = plot.style_linebr, linewidth = 2)

plotshape(i_showSmt and bearSmtRaw, "Bearish SMT", shape.triangledown, location.abovebar, color.new(color.red, 20), size = size.tiny)
plotshape(i_showSmt and bullSmtRaw, "Bullish SMT", shape.triangleup, location.belowbar, color.new(color.green, 20), size = size.tiny)
plotshape(fireShort, "Short Entry", shape.labeldown, location.abovebar, color.new(color.red, 0), size = size.small, text = "SHORT", textcolor = color.white)
plotshape(fireLong, "Long Entry", shape.labelup, location.belowbar, color.new(color.green, 0), size = size.small, text = "LONG", textcolor = color.white)

if i_showFvg and bearInvNow and not na(bearInvTop)
    box.new(bar_index, bearInvTop, bar_index + 12, bearInvBot, border_color = color.new(color.red, 40), bgcolor = color.new(color.red, 88))

if i_showFvg and bullInvNow and not na(bullInvTop)
    box.new(bar_index, bullInvTop, bar_index + 12, bullInvBot, border_color = color.new(color.green, 40), bgcolor = color.new(color.green, 88))

//  Dashboard 
var table dash = table.new(position.top_right, 2, 9, border_width = 1)

if i_showDash and barstate.islast
    table.cell(dash, 0, 0, "Lab Model PMT", text_color = color.white, bgcolor = color.new(color.blue, 20), text_size = size.small)
    table.cell(dash, 1, 0, i_useSetup, text_color = color.white, bgcolor = color.new(color.blue, 20), text_size = size.small)
    table.cell(dash, 0, 1, "Session", text_size = size.tiny)
    table.cell(dash, 1, 1, inSession ? "OPEN" : "closed", text_color = inSession ? color.green : color.gray, text_size = size.tiny)
    table.cell(dash, 0, 2, "Bias", text_size = size.tiny)
    table.cell(dash, 1, 2, inPremium ? "Premium (shorts)" : "Discount (longs)", text_size = size.tiny)
    table.cell(dash, 0, 3, "SMT bear", text_size = size.tiny)
    table.cell(dash, 1, 3, bearSmtActive ? "ARMED" : "-", text_color = bearSmtActive ? color.red : color.gray, text_size = size.tiny)
    table.cell(dash, 0, 4, "SMT bull", text_size = size.tiny)
    table.cell(dash, 1, 4, bullSmtActive ? "ARMED" : "-", text_color = bullSmtActive ? color.green : color.gray, text_size = size.tiny)
    table.cell(dash, 0, 5, "4H swept", text_size = size.tiny)
    table.cell(dash, 1, 5, (sweptAbove ? "Above " : "") + (sweptBelow ? "Below" : "") + (not sweptAbove and not sweptBelow ? "-" : ""), text_size = size.tiny)
    table.cell(dash, 0, 6, "Open FVGs", text_size = size.tiny)
    table.cell(dash, 1, 6, str.tostring(array.size(fvgTop)), text_size = size.tiny)
    table.cell(dash, 0, 7, "Today", text_size = size.tiny)
    table.cell(dash, 1, 7, str.tostring(tradesToday) + "T " + str.tostring(winsToday) + "W " + str.tostring(lossToday) + "L", text_size = size.tiny)
    table.cell(dash, 0, 8, "Can trade", text_size = size.tiny)
    table.cell(dash, 1, 8, limitsOk ? "yes" : "LIMIT HIT", text_color = limitsOk ? color.green : color.red, text_size = size.tiny)

// 
// ALERTS
// 
// Machine-readable payload for PickMyTrade webhook automation. alertcondition()
// messages must be const strings, so live prices go out through alert() instead.
if fireShort or fireLong
    string side  = fireLong ? "long" : "short"
    float  px    = fireLong ? longEntryRef : shortEntryRef
    float  sl    = fireLong ? longStop : shortStop
    float  tp    = fireLong ? longTgt : shortTgt
    string setup = fireLong ? (revLong ? "reversal" : "continuation") : (revShort ? "reversal" : "continuation")
    string otype = isRetap ? "limit" : "market"
    alert('{"src":"Lab Model [PickMyTrade]","sym":"' + syminfo.tickerid + '","tf":"' + timeframe.period + '","side":"' + side + '","setup":"' + setup + '","order_type":"' + otype + '","entry":' + f_jnum(px) + ',"sl":' + f_jnum(sl) + ',"tp":' + f_jnum(tp) + ',"qty":' + str.tostring(i_qty) + ',"risk_ticks":' + str.tostring(math.round(math.abs(px - sl) / syminfo.mintick)) + '}', alert.freq_once_per_bar_close)

alertcondition(fireLong, "Lab Model: Long Entry", "Lab Model [PickMyTrade] {{ticker}}: LONG  -  bullish SMT + inversion FVG from discount.")
alertcondition(fireShort, "Lab Model: Short Entry", "Lab Model [PickMyTrade] {{ticker}}: SHORT  -  bearish SMT + inversion FVG from premium.")
alertcondition(bearSmtRaw, "Lab Model: Bearish SMT", "Lab Model [PickMyTrade] {{ticker}}: bearish SMT divergence  -  NQ made a higher high, ES did not.")
alertcondition(bullSmtRaw, "Lab Model: Bullish SMT", "Lab Model [PickMyTrade] {{ticker}}: bullish SMT divergence  -  NQ made a lower low, ES did not.")
alertcondition(sweepUp, "Lab Model: 4H High Swept", "Lab Model [PickMyTrade] {{ticker}}: prior 4H high swept  -  reversal short setup is now armed.")
alertcondition(sweepDn, "Lab Model: 4H Low Swept", "Lab Model [PickMyTrade] {{ticker}}: prior 4H low swept  -  reversal long setup is now armed.")

Leave a Comment

Your email address will not be published. Required fields are marked *

error

Follow us for more insights and updates

Scroll to Top
Verified by MonsterInsights