Live & automated

Your signal, wired straight to MT5.

The exact same Open Legacy engine, running unattended — from a TradingView alert, through a webhook, into PineConnector, onto your broker. This page is everything needed to get that chain running for real.

Start here

Quick Start

Six steps from nothing to a running bot. Everything below is detail you can come back to.

Open Legacy Auto-Trade

TradingView sends the signal, PineConnector passes it to MT5, a VPS keeps it running.

1
Get the three pieces. A PineConnector licence, MT5 with its EA installed, and a Windows VPS to keep both running. See Three Pieces.
2
Add Open Legacy Bot to the chart. It places the trades, sends your phone alerts and shows the Scoreboard — one script, one set of settings.
3
Fill in the bridge. Licence ID, your exact MT5 symbol name (often ends .s), lot size, and TP/SL in pips. Start with wide TP/SL and narrow them once trades come through.
4
Pick a tested setup. Entry Method, Sensitivity, session and TP/SL per pair are in Recommended Settings. The Properties tab is already set for you — leave it alone.
5
Create the two alerts on the bot: Alert 1 sends orders to PineConnector (Condition "alert() function calls only", webhook on, notifications off), Alert 2 sends your phone notifications (Condition "Order fills only", push on, webhook off). Set the schedule to cover your whole trading window.
6
Test on demo first. Watch one full session before trusting it with real money, and check the first trade actually lands in MT5.
Before you automateThis places real orders. Read this first.▼

Everything below has been tested and works — but "works" means the pipeline fires correctly, not that any settings, sizing, or schedule are the right ones for you specifically.

Not financial advice. Everything on this page — the routine example, lot sizing, TP/SL discussion — is illustrative, not a recommendation. Test on a demo account first. Automated does not mean unsupervised; check on it regularly.
The connection, at a glanceThree Pieces, Wired Together▼

This whole system is three separate services talking to each other. Understanding the chain makes every setup step below make sense.

1. TradingView
This script
Fires an alert the instant a signal happens, sent as a webhook.
2. PineConnector
The bridge
Details
Receives that webhook and translates it into a real MT5 order. Needs your License ID (from PineConnector's Connections page) pasted into the strategy, and PineConnector's own webhook URL pasted into each TradingView alert.
3. A VPS
Where MT5 actually runs
Details
A reliable Windows VPS (ForexVPS is a solid choice) keeps MT5 and the PineConnector EA running 24/7 — no need for your own laptop to stay on. Confirm DLL imports are allowed on whichever VPS you pick; PineConnector's EA needs that to run at all.
One-time only. Set these three up once, and you're done — after that, everything (lot size, TP/SL, strategy, sensitivity) is adjustable straight from your phone without ever touching the VPS again. Restart the VPS once a week, Saturday morning, to keep MT5 stable.
DAY TO DAYCheat Sheet▼

Once the bot is running, this is the whole routine. Everything else on this page is setup you've already done.

Daily

Under a minute, twice a day.

1
Confirm MT5 is alive on the VPS. Platform open, EA on the chart, smiley face showing. A silent VPS reboot is the most common reason a bot stops trading.
2
Check the first trade of the session actually landed in MT5 — not just a label on the chart. Chart label without an MT5 fill means something broke between the two.
3
Pause before unscheduled news. Toggle Alert 1 off, back on afterwards. Scheduled events belong in News Blackouts instead.

Weekly

Sunday or Monday morning.

1
Restart the VPS — Saturday morning is ideal, so everything is back up well before the market reopens.
2
Load next week's blackout dates into the three News slots, and untick any that have passed.
3
Read the Scoreboard (bottom-right of the bot's chart). Its Outcome panel shows how many trades genuinely hit TP or SL versus closed early as a partial profit or loss. Its weekly TP / SL tally shows whether signal quality is holding up before it shows in your balance.
When something's wrong
Check first
Alert fired, no MT5 trade
Symbol name + suffix
Order rejected outright
TP / SL too tight
BUY/SELL on chart, nothing in MT5
Alert 1 Condition + webhook
Nothing fires at all
Alert schedule vs session hours
Nothing fires on a new pair
Session days + Timezone
Backtest looks too good
Commission set to zero
TWO PROVEN STRATEGIESEntry Methods▼

Two well-known strategies, picked because they were the only ones that held up when tested on three months of real 1-minute data across gold, indices and BTC — with real spreads. One dropdown picks the strategy; one Sensitivity dial sets how picky it is.

Strategy
What it waits for
FVG Pullback
Buys the dip into an imbalance
A fair value gap is left when price moves so fast that a candle never overlaps the one two candles back. The bot waits for price to pull back into that gap, then enters when a candle closes back out in the gap's direction. Tiny gaps are ignored — the gap must be a meaningful size versus normal volatility.
EMA Trend
Catches a new trend leg
Enters on the first candle where a fast EMA is above a slow EMA, price is above the fast EMA, and RSI is above 50 (all reversed for sells). It fires once at the start of each leg, not on every candle of the trend.
Sensitivity
What changes
Aggressive
Most trades
Every valid setup, with faster settings. Works on any timeframe. Expect more losers in the mix — it trades more, not smarter.
Balanced
About half as many
Only setups that agree with the 50 EMA trend on your chart.
Conservative
Fewest, best quality
Also needs the trend on a timeframe 4x your chart to agree, and real trend strength (ADX 20+). On gold this lifted the win rate from 51% to 65%.
What was cut, and why. UT Bot, Grid, Squeeze Breakout and Omega, plus Session Breakout and Liquidity Sweep, were all tested on the same data and lost money on most pairs, so they're gone. Layers and the correlation filter are gone too — one clean position at a time.
Copy & pasteGet the Scripts▼

One script does it all. Open Legacy Bot places real trades through PineConnector, sends a readable phone notification for every trade it opens and closes, and draws the TP / SL Scoreboard in the bottom-right corner — two strategies, one Sensitivity dial, five tidy settings groups.

Open Legacy Bot Trades · Phone alerts · Scoreboard
//@version=6
// Properties are pre-set so you never need to open that tab: one position at
// a time, no margin limits, and a large internal balance so no entry is ever
// rejected. Internal sizing is bookkeeping only — your real size is Lot Size.
strategy("Open Legacy Bot", overlay = true,
     initial_capital = 1000000, default_qty_type = strategy.fixed, default_qty_value = 1,
     pyramiding = 1, margin_long = 0, margin_short = 0,
     calc_on_every_tick = false, process_orders_on_close = true, max_labels_count = 500)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 🤖 OPEN LEGACY BOT
//  Two proven strategies, one sensitivity dial, your sessions and news
//  blackouts, PineConnector orders, phone alerts and a TP/SL scoreboard.
//
//  ALERTS (two, on this script):
//   Alert 1 — Condition "alert() function calls only", Webhook ON with your
//             PineConnector URL, push OFF. (Message box is not used.)
//   Alert 2 — Condition "Order fills only", message
//             {{ticker}} {{strategy.order.comment}}
//             push ON, webhook OFF.  Reads e.g. "XAUUSD BUY", "XAUUSD TP".
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// ── 1. STRATEGY ───────────────────────────────────────────────────────────
G1 = "1. Strategy"
entryMethod = input.string("FVG Pullback", "Entry Method", options = ["FVG Pullback", "EMA Trend"], group = G1,
     tooltip = "FVG Pullback — price leaves a fair value gap (a candle so fast it leaves an untouched gap), then the bot enters when price pulls back into the gap and turns.\n\nEMA Trend — enters at the start of a new trend leg: fast EMA above slow EMA, price above the fast EMA and RSI above 50 (the reverse for sells).")
sensitivity = input.string("Balanced", "Sensitivity", options = ["Aggressive", "Balanced", "Conservative"], group = G1,
     tooltip = "Aggressive — the most trades. Every valid setup, works on any timeframe.\n\nBalanced — only setups that agree with the 50 EMA trend. Roughly half the trades of Aggressive.\n\nConservative — also needs the trend on a timeframe 4x your chart to agree, and real trend strength (ADX 20+). The fewest trades, the highest quality.")
tfLock = input.string("Off", "Only Trade On Timeframe", options = ["Off", "1", "3", "5", "15", "30", "60"], group = G1,
     tooltip = "Off — trades on whatever timeframe the chart (and alert) uses. Pick a value to stop it trading on any other timeframe by mistake.")

// ── 2. SESSIONS ───────────────────────────────────────────────────────────
G2 = "2. Sessions"
s1Time = input.session("0700-1200", "Session 1", group = G2, inline = "s1")
s1Days = input.string("Mon-Fri", "", options = ["Mon-Fri", "Tue-Fri", "Mon-Thu", "Tue-Thu", "Mon-Wed", "Wed-Fri", "Sun-Fri", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat-Sun", "Sat", "Sun", "Every Day", "Off"], group = G2, inline = "s1",
     tooltip = "📆 Pick the days each session trades. Off switches the session off.")
s2Time = input.session("1400-1800", "Session 2", group = G2, inline = "s2")
s2Days = input.string("Off", "", options = ["Mon-Fri", "Tue-Fri", "Mon-Thu", "Tue-Thu", "Mon-Wed", "Wed-Fri", "Sun-Fri", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat-Sun", "Sat", "Sun", "Every Day", "Off"], group = G2, inline = "s2")
sessionTz = input.string("Europe/Dublin", "Timezone", group = G2,
     tooltip = "Your timezone, IANA format — e.g. Europe/Dublin, Europe/London, America/New_York.")
closeAtSessionEnd = input.bool(true, "Close Trades At Session End", group = G2)

// ── 3. NEWS BLACKOUTS ─────────────────────────────────────────────────────
G3 = "3. News Blackouts"
newsSide = input.int(30, "Minutes Either Side", minval = 1, group = G3,
     tooltip = "No new trades from this many minutes before each event until this many minutes after. Any open trade is closed as the window starts.")
n1On   = input.bool(false, "News 1", group = G3, inline = "n1")
n1Date = input.time(timestamp("2026-10-13T12:00:00"), "", group = G3, inline = "n1")
n1Hour = input.int(13, "at", minval = 0, maxval = 23, group = G3, inline = "n1")
n1Min  = input.int(30, ":", minval = 0, maxval = 59, group = G3, inline = "n1")
n2On   = input.bool(false, "News 2", group = G3, inline = "n2")
n2Date = input.time(timestamp("2026-10-14T12:00:00"), "", group = G3, inline = "n2")
n2Hour = input.int(13, "at", minval = 0, maxval = 23, group = G3, inline = "n2")
n2Min  = input.int(30, ":", minval = 0, maxval = 59, group = G3, inline = "n2")
n3On   = input.bool(false, "News 3", group = G3, inline = "n3")
n3Date = input.time(timestamp("2026-10-15T12:00:00"), "", group = G3, inline = "n3")
n3Hour = input.int(13, "at", minval = 0, maxval = 23, group = G3, inline = "n3")
n3Min  = input.int(30, ":", minval = 0, maxval = 59, group = G3, inline = "n3")

// ── 4. BROKER (PINECONNECTOR) ─────────────────────────────────────────────
G4 = "4. Broker (PineConnector)"
licenseId = input.string("YourLicenseID", "License ID", group = G4)
mt5Symbol = input.string("", "MT5 Symbol", group = G4,
     tooltip = "Exactly as in MT5 Market Watch, e.g. XAUUSD.s. Leave blank to use the chart's symbol.")
lotSize   = input.float(0.01, "Lot Size", minval = 0.01, step = 0.01, group = G4)
slPips    = input.float(100, "Stop Loss (pips)", minval = 0.1, step = 0.1, group = G4)
tpPips    = input.float(200, "Take Profit (pips)", minval = 0.1, step = 0.1, group = G4,
     tooltip = "Keep Take Profit larger than Stop Loss.")
pipPoints = input.int(10, "Points Per Pip", minval = 1, group = G4,
     tooltip = "10 for gold, indices, BTC and 5-decimal FX. Only affects the chart and backtest — PineConnector applies the pips above itself.")
pauseEA   = input.bool(true, "Pause EA Outside Sessions", group = G4,
     tooltip = "Sends eaon when a session starts and eaoff after it ends.")

// ── 5. DISPLAY ────────────────────────────────────────────────────────────
G5 = "5. Display"
sbShow      = input.bool(true, "📊 Show Scoreboard", group = G5)
sbPeriod    = input.string("This Week", "📅 Show Results For", options = ["This Week", "Last Week", "2 Weeks Ago", "3 Weeks Ago", "4 Weeks Ago", "Last 2 Weeks", "Last 4 Weeks", "Last 8 Weeks", "Last 12 Weeks", "All Time"], group = G5,
     tooltip = "📅 One week: This Week, Last Week, 2-4 Weeks Ago.\n📈 Several weeks added up: Last 2/4/8/12 Weeks (this week included).\n♾️ All Time: every trade on the chart, best for backtesting.")
showMarks   = input.bool(true, "Show TP/SL Lines & Session Markers", group = G5)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ENGINE
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
bool tfOk = tfLock == "Off" or timeframe.period == tfLock
int  tfMin = timeframe.in_seconds() / 60

// Sensitivity presets (literal values keep lengths "simple" for ta.* calls)
fvgLife  = sensitivity == "Aggressive" ? 12  : sensitivity == "Balanced" ? 24  : 40
fvgMin   = sensitivity == "Aggressive" ? 0.10 : sensitivity == "Balanced" ? 0.25 : 0.40
emaFastL = sensitivity == "Aggressive" ? 8   : sensitivity == "Balanced" ? 13  : 21
emaSlowL = sensitivity == "Aggressive" ? 21  : sensitivity == "Balanced" ? 34  : 55

atrV  = ta.atr(14)
ema50 = ta.ema(close, 50)
[diP, diM, adxV] = ta.dmi(14, 14)

// Higher-timeframe trend (4x chart), previous completed candle — never repaints.
htfTf  = timeframe.from_seconds(timeframe.in_seconds() * 4)
htfC   = request.security(syminfo.tickerid, htfTf, close[1], lookahead = barmerge.lookahead_on)
htfE   = request.security(syminfo.tickerid, htfTf, ta.ema(close, 50)[1], lookahead = barmerge.lookahead_on)
bool htfUp = not na(htfE) and htfC > htfE
bool htfDn = not na(htfE) and htfC < htfE

// ── FVG Pullback ──
var float bullTop = na
var float bullBot = na
var int   bullBar = na
var float bearTop = na
var float bearBot = na
var int   bearBar = na
bool fvgBuy  = not na(bullBar) and bar_index - bullBar > 0 and bar_index - bullBar <= fvgLife and low <= bullTop and close > open and close > bullBot
bool fvgSell = not na(bearBar) and bar_index - bearBar > 0 and bar_index - bearBar <= fvgLife and high >= bearBot and close < open and close < bearTop
if fvgBuy
    bullBar := na
if fvgSell
    bearBar := na
if low > high[2] and (low - high[2]) >= fvgMin * atrV
    bullBot := high[2]
    bullTop := low
    bullBar := bar_index
if high < low[2] and (low[2] - high) >= fvgMin * atrV
    bearTop := low[2]
    bearBot := high
    bearBar := bar_index

// ── EMA Trend ──
emaF = ta.ema(close, emaFastL)
emaS = ta.ema(close, emaSlowL)
rsiV = ta.rsi(close, 14)
bool trUp = emaF > emaS and close > emaF and rsiV > 50
bool trDn = emaF < emaS and close < emaF and rsiV < 50
bool trendBuy  = trUp and not trUp[1]
bool trendSell = trDn and not trDn[1]

// ── Sensitivity filter ──
bool rawBuy  = entryMethod == "FVG Pullback" ? fvgBuy  : trendBuy
bool rawSell = entryMethod == "FVG Pullback" ? fvgSell : trendSell
bool qBuy  = sensitivity == "Aggressive" or (close > ema50 and (sensitivity == "Balanced" or (htfUp and adxV >= 20)))
bool qSell = sensitivity == "Aggressive" or (close < ema50 and (sensitivity == "Balanced" or (htfDn and adxV >= 20)))
bool sigBuy  = rawBuy and qBuy
bool sigSell = rawSell and qSell and not sigBuy

// Scoreboard period -> weeks to add up (0 = all) and weeks back
int sbSpan = switch sbPeriod
    "Last 2 Weeks"  => 2
    "Last 4 Weeks"  => 4
    "Last 8 Weeks"  => 8
    "Last 12 Weeks" => 12
    "All Time"      => 0
    => 1
int sbWeeksBack = switch sbPeriod
    "Last Week"   => 1
    "2 Weeks Ago" => 2
    "3 Weeks Ago" => 3
    "4 Weeks Ago" => 4
    => 0

// ── Sessions ──
f_days(string opt) =>
    switch opt
        "Mon-Fri"   => "23456"
        "Tue-Fri"   => "3456"
        "Mon-Thu"   => "2345"
        "Tue-Thu"   => "345"
        "Mon-Wed"   => "234"
        "Wed-Fri"   => "456"
        "Sun-Fri"   => "123456"
        "Mon"       => "2"
        "Tue"       => "3"
        "Wed"       => "4"
        "Thu"       => "5"
        "Fri"       => "6"
        "Sat-Sun"   => "17"
        "Sat"       => "7"
        "Sun"       => "1"
        "Every Day" => "1234567"
        => ""

f_hm(string s, int p) =>
    int(str.tonumber(str.substring(s, p, p + 2)))

f_mins(int t) =>
    hour(t, sessionTz) * 60 + minute(t, sessionTz)

// true when time t falls inside the window on one of its days
f_inSess(int t, string sess, string days) =>
    int st = f_hm(sess, 0) * 60 + f_hm(sess, 2)
    int en = f_hm(sess, 5) * 60 + f_hm(sess, 7)
    int m  = f_mins(t)
    bool dayOk = str.length(days) > 0 and str.contains(days, str.tostring(dayofweek(t, sessionTz)))
    dayOk and (en > st ? (m >= st and m < en) : (m >= st or m < en))

// entries need the candle to CLOSE inside the window with a candle of room left
f_room(string sess) =>
    int en = f_hm(sess, 5) * 60 + f_hm(sess, 7)
    int st = f_hm(sess, 0) * 60 + f_hm(sess, 2)
    int m  = f_mins(time_close)
    int left = en > m ? en - m : 1440 - m + en
    bool inside = en > st ? (m > st and m < en) : (m > st or m < en)
    not closeAtSessionEnd or (inside and left > tfMin)

string d1 = f_days(s1Days)
string d2 = f_days(s2Days)
bool inWin1 = f_inSess(time, s1Time, d1)
bool inWin2 = f_inSess(time, s2Time, d2)
bool inSession = inWin1 or inWin2
bool sessionStarted = inSession and not inSession[1]
bool sessionEnded   = not inSession and inSession[1]
// true on the LAST candle of a session (its close lands on the session end),
// so trades close exactly at the end time, not one candle later.
bool sessionEndsNow = (inWin1 and not f_inSess(time_close, s1Time, d1)) or (inWin2 and not f_inSess(time_close, s2Time, d2))
bool entryWindow = (inWin1 and f_room(s1Time)) or (inWin2 and f_room(s2Time))

// ── News blackouts ──
f_newsTs(int pick, int hh, int mm) =>
    timestamp(sessionTz, year(pick, sessionTz), month(pick, sessionTz), dayofmonth(pick, sessionTz), hh, mm)
int sideMs = newsSide * 60 * 1000
f_inNews(int t, bool on, int ts) =>
    on and t >= ts - sideMs and t < ts + sideMs
bool inBlackout = f_inNews(time_close, n1On, f_newsTs(n1Date, n1Hour, n1Min)) or f_inNews(time_close, n2On, f_newsTs(n2Date, n2Hour, n2Min)) or f_inNews(time_close, n3On, f_newsTs(n3Date, n3Hour, n3Min))
bool blackoutStarted = inBlackout and not inBlackout[1]

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ORDERS — PineConnector commands go out through alert() (Alert 1).
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
string sym = mt5Symbol == "" ? syminfo.ticker : mt5Symbol
f_order(string side) =>
    licenseId + "," + side + "," + sym + ",vol_lots=" + str.tostring(lotSize, "#.##") +
     ",sl_pips=" + str.tostring(slPips, "#.##") + ",tp_pips=" + str.tostring(tpPips, "#.##")
f_close(string side) =>
    licenseId + "," + side + "," + sym

float tpDist = tpPips * pipPoints * syminfo.mintick
float slDist = slPips * pipPoints * syminfo.mintick
int   tpTicks = math.round(tpPips * pipPoints)
int   slTicks = math.round(slPips * pipPoints)

// 1. Session end / blackout: flatten (MT5 and the chart)
if (sessionEndsNow and closeAtSessionEnd) or blackoutStarted
    alert(f_close("closelong"), alert.freq_all)
    alert(f_close("closeshort"), alert.freq_all)
    strategy.close_all(comment = blackoutStarted ? "Blackout" : "Session End")

// 2. Entries — one position at a time; an opposite signal reverses it.
bool canTrade = tfOk and entryWindow and not inBlackout and barstate.isconfirmed
if canTrade and sigBuy and strategy.position_size <= 0
    if strategy.position_size < 0
        alert(f_close("closeshort"), alert.freq_all)
    alert(f_order("buy"), alert.freq_all)
    strategy.entry("Long", strategy.long, comment = "BUY")
if canTrade and sigSell and strategy.position_size >= 0
    if strategy.position_size > 0
        alert(f_close("closelong"), alert.freq_all)
    alert(f_order("sell"), alert.freq_all)
    strategy.entry("Short", strategy.short, comment = "SELL")

// 3. TP / SL — the same distances MT5 holds, so the chart and the broker agree.
strategy.exit("XL", from_entry = "Long",  profit = tpTicks, loss = slTicks, comment_profit = "TP", comment_loss = "SL")
strategy.exit("XS", from_entry = "Short", profit = tpTicks, loss = slTicks, comment_profit = "TP", comment_loss = "SL")

// 4. Pause / resume the EA with your sessions (eaoff the candle after the close)
if pauseEA and sessionStarted
    alert(licenseId + ",eaon,eaon", alert.freq_all)
if pauseEA and sessionEnded
    alert(licenseId + ",eaoff,eaoff", alert.freq_all)

// ── Chart ──
float posTp = strategy.position_size > 0 ? strategy.position_avg_price + tpDist : strategy.position_size < 0 ? strategy.position_avg_price - tpDist : na
float posSl = strategy.position_size > 0 ? strategy.position_avg_price - slDist : strategy.position_size < 0 ? strategy.position_avg_price + slDist : na
plot(showMarks ? posTp : na, "TP", color.new(color.green, 0), 2, plot.style_linebr)
plot(showMarks ? posSl : na, "SL", color.new(color.red, 0), 2, plot.style_linebr)
bgcolor(showMarks and inSession ? color.new(color.blue, 94) : na, title = "Session")

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 📊 SCOREBOARD — reads the bot's own closed trades, so it always matches.
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Per week: 14 slots (7 days x 2 sessions) x 4 outcomes
// (0 TP hit, 1 Partial Profit, 2 Partial Loss, 3 SL hit).
var array<int> sbOut = array.new_int(56, 0)
var int sbWeek = 0
var int sbSeen = 0

f_dayIdx(int t) =>
    int d = dayofweek(t, sessionTz)
    d == dayofweek.sunday ? 6 : d - 2

wkNow = weekofyear(time, sessionTz)
if wkNow != wkNow[1] and bar_index > 0
    sbWeek += 1
    for i = 0 to 55
        array.push(sbOut, 0)

while sbSeen < strategy.closedtrades
    int   et   = strategy.closedtrades.entry_time(sbSeen)
    string xc  = strategy.closedtrades.exit_comment(sbSeen)
    float pnl  = strategy.closedtrades.profit(sbSeen)
    int   kind = xc == "TP" ? 0 : xc == "SL" ? 3 : pnl > 0 ? 1 : 2
    int   sIdx = f_inSess(et, s1Time, d1) ? 0 : 1
    int   slot = (sbWeek * 14 + f_dayIdx(et) * 2 + sIdx) * 4 + kind
    array.set(sbOut, slot, array.get(sbOut, slot) + 1)
    sbSeen += 1

var table sb = table.new(position.bottom_right, 5, 16, border_width = 1, frame_width = 2,
     frame_color = color.new(#2A3350, 0), border_color = color.new(#1C2334, 0))

// Strategies only run when a candle closes, so on a live chart the newest
// candle isn't "last" until it closes. Drawing on the last historical candle
// too makes the board appear the moment the chart loads.
if (barstate.islastconfirmedhistory or barstate.isrealtime) and sbShow
    bg    = color.new(#0E1320, 0)
    alt   = color.new(#121829, 0)
    head  = color.new(#18203A, 0)
    sub   = color.new(#141B2E, 0)
    green = color.rgb(47, 212, 128)
    red   = color.rgb(255, 93, 108)
    teal  = color.rgb(46, 196, 182)
    amber = color.rgb(245, 185, 66)
    dim   = color.rgb(118, 126, 146)
    ts    = size.small
    bool s2On = d2 != ""

    int lastWk  = sbWeek - sbWeeksBack
    int firstWk = sbSpan == 0 ? 0 : math.max(0, lastWk - sbSpan + 1)
    bool single = sbSpan == 1
    string hdr  = sbSpan == 0 ? "📅 All" : single ? (sbWeeksBack == 0 ? "📅 This Wk" : sbWeeksBack == 1 ? "📅 Last Wk" : "📅 " + str.tostring(sbWeeksBack) + " Wks Ago") : "📅 " + str.tostring(sbSpan) + " Wks"

    // totals per day/session, and per outcome
    array<int> win  = array.new_int(14, 0)
    array<int> loss = array.new_int(14, 0)
    array<int> o1   = array.new_int(4, 0)
    array<int> o2   = array.new_int(4, 0)
    if lastWk >= 0
        for w = firstWk to lastWk
            for cell = 0 to 13
                for k = 0 to 3
                    int v = array.get(sbOut, (w * 14 + cell) * 4 + k)
                    if v > 0
                        if k <= 1
                            array.set(win, cell, array.get(win, cell) + v)
                        else
                            array.set(loss, cell, array.get(loss, cell) + v)
                        if cell % 2 == 0
                            array.set(o1, k, array.get(o1, k) + v)
                        else
                            array.set(o2, k, array.get(o2, k) + v)

    table.cell(sb, 0, 0, hdr, text_color = color.rgb(214, 220, 234), bgcolor = head, text_size = ts)
    table.cell(sb, 1, 0, "✅ S1", text_color = green, bgcolor = head, text_size = ts)
    table.cell(sb, 2, 0, "❌ S1", text_color = red,   bgcolor = head, text_size = ts)
    table.cell(sb, 3, 0, "✅ S2", text_color = green, bgcolor = head, text_size = ts)
    table.cell(sb, 4, 0, "❌ S2", text_color = red,   bgcolor = head, text_size = ts)

    names = array.from("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
    int today = f_dayIdx(time)
    int t1 = 0
    int l1 = 0
    int t2 = 0
    int l2 = 0
    for d = 0 to 6
        bool isToday = d == today and sbWeeksBack == 0 and single
        color rbg = isToday ? color.new(#1A2440, 0) : (d % 2 == 0 ? bg : alt)
        table.cell(sb, 0, d + 1, (isToday ? "▸ " : "") + array.get(names, d),
             text_color = isToday ? color.rgb(77, 232, 255) : color.rgb(205, 212, 228), bgcolor = rbg, text_size = ts)
        for s = 0 to 1
            int wv = array.get(win, d * 2 + s)
            int lv = array.get(loss, d * 2 + s)
            bool off = s == 1 and not s2On
            table.cell(sb, s * 2 + 1, d + 1, off ? "·" : wv == 0 ? "-" : str.tostring(wv), text_color = off or wv == 0 ? dim : green, bgcolor = rbg, text_size = ts)
            table.cell(sb, s * 2 + 2, d + 1, off ? "·" : lv == 0 ? "-" : str.tostring(lv), text_color = off or lv == 0 ? dim : red, bgcolor = rbg, text_size = ts)
            if s == 0
                t1 += wv
                l1 += lv
            else
                t2 += wv
                l2 += lv

    int totW = t1 + t2
    int totN = totW + l1 + l2
    float winPct = totN > 0 ? totW * 100.0 / totN : 0.0
    color totCol = totN == 0 ? dim : winPct > 50 ? green : winPct < 50 ? red : amber
    table.cell(sb, 0, 8, totN == 0 ? (single ? "🏁 Week" : "🏁 Total") : "🏁 " + str.tostring(winPct, "#") + "%", text_color = totCol, bgcolor = head, text_size = ts)
    table.cell(sb, 1, 8, str.tostring(t1), text_color = green, bgcolor = head, text_size = ts)
    table.cell(sb, 2, 8, str.tostring(l1), text_color = red,   bgcolor = head, text_size = ts)
    table.cell(sb, 3, 8, s2On ? str.tostring(t2) : "·", text_color = s2On ? green : dim, bgcolor = head, text_size = ts)
    table.cell(sb, 4, 8, s2On ? str.tostring(l2) : "·", text_color = s2On ? red   : dim, bgcolor = head, text_size = ts)

    // Outcome breakdown
    table.cell(sb, 0, 9, "📊 Outcome", text_color = color.rgb(214, 220, 234), bgcolor = head, text_size = ts, text_halign = text.align_left)
    table.cell(sb, 1, 9, "S1",    text_color = dim, bgcolor = head, text_size = ts)
    table.cell(sb, 2, 9, "S2",    text_color = dim, bgcolor = head, text_size = ts)
    table.cell(sb, 3, 9, "All",   text_color = dim, bgcolor = head, text_size = ts)
    table.cell(sb, 4, 9, "Share", text_color = dim, bgcolor = head, text_size = ts)
    oNames = array.from("🎯 TP Hit", "◐ Partial +", "◐ Partial −", "🛑 SL Hit")
    oCols  = array.from(green, teal, amber, red)
    for k = 0 to 3
        int v1 = array.get(o1, k)
        int v2 = array.get(o2, k)
        int vt = v1 + v2
        float pct = totN > 0 ? vt * 100.0 / totN : 0.0
        int nb = int(math.round(pct / 20.0))
        string meter = ""
        for q = 1 to 5
            meter += q <= nb ? "▰" : "▱"
        color cc  = array.get(oCols, k)
        color rbg = k % 2 == 0 ? bg : alt
        table.cell(sb, 0, 10 + k, array.get(oNames, k), text_color = cc, bgcolor = rbg, text_size = ts, text_halign = text.align_left)
        table.cell(sb, 1, 10 + k, v1 == 0 ? "-" : str.tostring(v1), text_color = v1 == 0 ? dim : cc, bgcolor = rbg, text_size = ts)
        table.cell(sb, 2, 10 + k, not s2On ? "·" : v2 == 0 ? "-" : str.tostring(v2), text_color = not s2On or v2 == 0 ? dim : cc, bgcolor = rbg, text_size = ts)
        table.cell(sb, 3, 10 + k, str.tostring(vt), text_color = vt == 0 ? dim : cc, bgcolor = sub, text_size = ts)
        table.cell(sb, 4, 10 + k, totN == 0 ? "▱▱▱▱▱" : meter + " " + str.tostring(pct, "#") + "%", text_color = totN == 0 ? dim : cc, bgcolor = sub, text_size = ts, text_halign = text.align_left)

    // spacer so the TradingView footer never covers the last row
    for col = 0 to 4
        table.cell(sb, col, 14, " ", bgcolor = color.new(color.black, 100), text_size = ts)

if showMarks and sessionStarted
    label.new(bar_index, high, "Session Start", style = label.style_label_down, color = color.new(color.gray, 35), textcolor = color.white, size = size.tiny)
if showMarks and sessionEndsNow
    label.new(bar_index, high, "Session End", style = label.style_label_down, color = color.new(color.gray, 35), textcolor = color.white, size = size.tiny)
One-time, per laptopOne-Time Setup▼

Do this once on your VPS or laptop, per broker account. Everything after this (lot size, TP/SL pips, sensitivity) is adjustable straight from your phone without touching this setup again.

1

Get your PineConnector License ID

From PineConnector's Connections page. This is what goes into the bot's "License ID" field.

2

Install the PineConnector EA on MT5

Place the .ex5 file in MQL5\Experts\Advisors\ and the .dll one level up in MQL5\Experts\. In MT5 Options → Experts, enable "Allow algorithmic trading" and add PineConnector's webhook domain under "Allow WebRequest for listed URL."

3

Properties — nothing to do

The bot sets its own Properties: one position at a time, no margin limits, and a large internal balance so no entry is ever rejected. That balance is bookkeeping only — your real trade size is the Lot Size setting. Leave the Properties tab as it is.

4

Confirm your MT5 symbol name

Brokers often suffix symbols (e.g. XAUUSD.a). Check Market Watch → Symbols on your MT5, and if it differs from the plain ticker, type the exact name into the bot's MT5 Symbol field.

How signals actually confirmOnly Confirmed Candles Fire a Trade▼

Same principle as the manual indicator, built directly into the code rather than left to habit: the bot will never open a trade, or send a phone alert, off a candle that's still forming.

Every entry explicitly requires barstate.isconfirmed. A crossover can flicker true then false again on a still-forming candle before it closes; that's a fakeout, not a real signal. The bot only ever places a real order, and so only ever sends a phone alert, once the candle genuinely closes and that condition holds.
What this doesn't cover: session-end and blackout closes also happen on a candle close, not to the exact second — on a 15m chart a 12:00 session end closes the trade at the 12:15 close.
Per pairSetting Up Alerts▼

Two alerts per pair, both on Open Legacy Bot. Each has exactly one job. A TradingView alert sends the same message to every channel you tick, and PineConnector needs its exact syntax with nothing extra — so the order alert and the readable phone alert have to stay separate. Set both alerts' Interval to the exact timeframe you trade (e.g. 5m), and if you use Only Trade On Timeframe, set it to the same.

Script
Trigger
Bot — Alert 1 (orders)
alert() function calls only
Details
Carries every command to MT5 — entries, reversal closes, session-end and blackout closes, and EA pause/resume. The script writes each command itself, so the Message box isn't used. Webhook on (your PineConnector URL), every other notification channel off.
Bot — Alert 2 (phone)
Order fills only
Details
Your readable trade notification. Message: {{ticker}} {{strategy.order.comment}} — it arrives as e.g. XAUUSD BUY when a trade opens and XAUUSD TP or XAUUSD SL when it closes. It only fires when the bot genuinely fills a trade, so it can never alert on one that was blocked. Session-end and blackout closes arrive too, as XAUUSD Session End / XAUUSD Blackout. Push notification on, webhook off — never put your PineConnector URL on this one.
The alert's Interval decides the timeframe, not your chart. Each alert runs the bot on whatever timeframe its Interval box says. If Alert 1 is on 3m and Alert 2 on 5m, you'll get phone alerts for 5m trades that never reach MT5. Create both alerts with the chart already on your trading timeframe so the Interval fills in correctly, and check they match.
Pick the right Condition for each alert. Alert 1 must be "alert() function calls only" so every command reaches MT5 — and nothing else. Alert 2 must be "Order fills only". If Alert 2 includes alert() function calls, it also sends the bot's raw broker commands to your phone (e.g. 123456,closelong,NAS100.s), because those always use the script's own text and ignore your message box. Seeing those on your phone? Change Alert 2's Condition, and delete any old alerts still running from previous versions.
Trading more than one pair? Each pair needs its own chart with the bot on it, and its own full set of two alerts. Nothing is shared between pairs.
Running two sessions? Still use one alert, not two. It's tempting to make a "morning" alert and an "afternoon" alert, but both would be listening to the same script — so every single order would fire both of them, sending PineConnector two identical commands and opening double the position you intended. Instead: keep one alert, and set its notification schedule to span the whole outer range (e.g. 07:00–18:00). The script's own Session Hours handle the midday gap — nothing trades between your two windows regardless of the alert being awake.

Avoiding a News Release

Pine has no live news feed, so the script can't detect a release on its own — but you can tell it when they happen. News Blackouts (below) let you enter up to three dates and times in advance. For anything unscheduled, pausing the alert manually still works:

Open TradingView's Alerts panel, find the bot's Alert 1 — "alert() function calls only" — and toggle it off shortly before the release. Toggle it back on once it's passed. That's it; disabling an alert stops it from firing at all, webhook included, not just its notifications.
Only Alert 1 needs this — leave Alert 2 running the whole time. It never reaches PineConnector or your broker, so pausing it accomplishes nothing; only Alert 1 controls whether a real trade can fire.

Running This on a Prop Firm Account

Prop firms like FTMO run on a specific rule set — daily loss limits, news restrictions, holding rules that vary by account type. Here's exactly what this bot currently handles on its own, and what's on you to manage manually. Figures below reference FTMO as one example; confirm your own firm's current terms directly rather than relying on this as a source of truth.

Firm requirement
Bot status
News restriction window
News Blackouts
Details
No live news feed exists in Pine, so you enter the release times yourself in News Blackouts — set the window to cover your firm's restriction (FTMO's is 2 minutes either side of a restricted release), or pause Alert 1 manually for one-offs. Worth knowing: this only stops new entries. If a resting SL or TP order happens to execute during that same window, the bot has no way to prevent that — it's a real order already sitting on the broker's server, outside Pine's control entirely.
Daily / max loss limit
Manual
Details
The strategy has no built-in loss cap of its own. Many firms count floating (unrealized) loss toward the limit, not just closed trades. The bot only ever holds one position per pair, but several pairs running at once add up. Check that directly against your firm's limit rather than assuming the bot will stop itself.
Holding rules by account type
Partial
Details
These vary by phase — Challenge, Verification, and Swing accounts commonly allow overnight and weekend holding; standard Funded accounts commonly don't. The bot's session-end close genuinely closes everything once your configured Session Hours end — but it only helps if your window is actually set to end before your account type's cutoff. Confirm which rule applies to your specific account phase.
Market access windows
Manual
Details
Beyond your own Session Hours, firms and brokers can add their own restrictions — symbol maintenance windows, broker rollover blackout periods. The bot only knows about the session window you've configured; it has no awareness of a broker's separate maintenance schedule.
Consistent lot sizing
Manual, supported by design
Details
Lot Size is a fixed value you set once — the bot won't vary it trade to trade on its own, which naturally supports this requirement as long as you don't change it mid-account. Sudden manual changes to Lot Size are on you to avoid, not something the script prevents.
Timezone safety, specifically for Ireland/UTC+1 setups: Session Timezone defaulting to Europe/Dublin means the hours you set follow whichever offset is currently active — UTC+1 in summer, UTC+0 in winter — since Ireland's clocks shift with the rest of the EU. Your firm's economic calendar and your MT5 server time won't necessarily shift on the same schedule. Cross-check both directly against your own Session Hours around a DST changeover, rather than assuming the offset stayed the same.

Rules and figures vary by firm and change over time — check your own firm's current terms directly rather than relying on this table as a source of truth.

Set a few dates, forget about themNews Blackouts▼

Up to three dates and times the bot stays out of — typically scheduled news. Around each one it takes no new trades, and it closes any open position as the window starts so you go into the release flat.

Setting
What it does
Minutes Either Side
30
How long each event blocks, before and after. 30 blocks from 30 minutes before to 30 minutes after, and closes any open trade at the start of that window.
News 1–3
One row each
Details
• Tick the row to turn it on.
• Date — pick the date only. The calendar also shows a time, but that time is ignored.
• at and : — the news hour and minute, in your Timezone. These are the times that count.
Each row is one specific date — it doesn't repeat.
Example: USD news at 13:30 on three days. Tick News 1–3, pick those dates, set each to 13 : 30. Each of those days blocks 13:00–14:00, with any open trade closed at 13:00. When they've passed, swap in the next three.
Good to know. Timing lands on your chart's candle closes, so on a 15m chart the window starts at the nearest 15-minute close. It can't stop a TP or SL already sitting on your broker's server from triggering during a blackout. After setting a date, check it once with Bar Replay.
A live, running setupBuilding Your Trading Routine▼

One way to structure a day. Each pair runs on its own chart with its own two alerts, and only trades in the hours where that market genuinely has volume.

Time (Dublin)
Pair
07:00–12:00
XAUUSD — London's prime hours
14:00–18:00
SP500 — US open and the first half of the session
18:00–20:30
NAS100 — the deeper US afternoon
Sat–Sun 12:00–20:00
BTCUSD — weekend only
Swap in your own pairs and hours — the point is deliberate windows, not this exact split. Re-test a pair before changing its session.
Worth real thoughtLot Sizing & Targets▼

Lot Size, Stop Loss (pips) and Take Profit (pips) live under ④ Broker (PineConnector), editable from your phone at any time — but "editable easily" isn't the same as "safe to guess."

Size for the stop loss. The bot holds one position per pair at your Lot Size, so your worst single loss is the Stop Loss distance times your lot. Work that out before going live — e.g. on gold a $16.50 stop at 0.10 lots (10 oz) is about $165 per losing trade. Pick the lot from what you're genuinely willing to lose on one stop, not what feels exciting.
Pips are broker-specific. PineConnector works out "1 pip" from your broker's decimal places for each symbol. Before trusting a setup, open one demo trade and check the SL and TP in MT5 sit at the price distances shown in Recommended Settings. If they're 10x off, adjust the pips to match.