Skip to content

Help #482

Description

@zachariahgakere-cell

help improve this strategy,
//@Version=6
indicator("GGbest Backtest Lite v1.2 (Fixed Trade vars) — EMA+FVG + In-chart Backtester", overlay=true, max_boxes_count=250, max_lines_count=250)

// ========== USER INPUTS ==========
ema_fast_len = input.int(50, "EMA Fast")
ema_slow_len = input.int(200, "EMA Slow")
use_fvg = input.bool(true, "Use FVG")
use_atr_filter = input.bool(false, "Use ATR acceptance")
atr_len = input.int(14, "ATR length")
atr_min = input.float(0.0, "Min ATR (0=off)")
atr_max = input.float(0.0, "Max ATR (0=off)")

rsi_len = input.int(14, "RSI length")

// Session (your trading hours) — default 21:00-23:00 EAT
use_session = input.bool(true, "Restrict to session")
sess = input.session("2100-2300", "Session (24h)")

need_confs = input.int(3, "Min confluence (1..4)", minval=1, maxval=4)

// Backtest settings
sl_pips = input.int(7, "Stop Loss (pips)", minval=1, maxval=100) // you said 5-10 pips — change as needed
lookback_for_tp = input.int(50, "Lookback bars to find nearest swing (TP)", minval=5, maxval=500)
max_bars_holding = input.int(200, "Max bars to hold trade", minval=1, maxval=2000)

// Money / sizing
start_balance = input.float(100.0, "Start Balance (USD)")
pip_size = input.float(0.0001, "Pip size (price units). e.g. EURUSD=0.0001, XAUUSD=0.01, CL=0.01")
pip_value_usd = input.float(0.1, "USD per pip (per trade) — change to reflect lot size")

// Display
show_stats = input.bool(true, "Show backtest stats on chart")
show_trade_markers = input.bool(true, "Show entry/exit markers")

// ========== INDICATORS & BASIC SCANNER ==========
ema_fast = ta.ema(close, ema_fast_len)
ema_slow = ta.ema(close, ema_slow_len)
atr_val = ta.atr(atr_len)
rsi = ta.rsi(close, rsi_len)

in_session = not use_session or (time(timeframe.period, sess) != 0)
atr_ok = not use_atr_filter or ((atr_min == 0 or atr_val >= atr_min) and (atr_max == 0 or atr_val <= atr_max))

// FVG detection (3-bar rule)
var box[] fvg_bull_boxes = array.new_box()
var box[] fvg_bear_boxes = array.new_box()
max_boxes = 200
bool bull_fvg = false
bool bear_fvg = false
if use_fvg and bar_index >= 2
if (high[2] < low[1]) and (low > high[2])
bull_fvg := true
array.push(fvg_bull_boxes, box.new(bar_index-2, high[2], bar_index, low[1], bgcolor=color.new(color.green,85)))
else
bull_fvg := false
if (low[2] > high[1]) and (high < low[2])
bear_fvg := true
array.push(fvg_bear_boxes, box.new(bar_index-2, high[1], bar_index, low[2], bgcolor=color.new(color.red,85)))
else
bear_fvg := false

while array.size(fvg_bull_boxes) > max_boxes
box.delete(array.shift(fvg_bull_boxes))
while array.size(fvg_bear_boxes) > max_boxes
box.delete(array.shift(fvg_bear_boxes))

// Trend / RSI confluence
bull_trend = ema_fast > ema_slow
bear_trend = ema_fast < ema_slow
rsi_bull_ok = rsi > 50
rsi_bear_ok = rsi < 50

long_confs = (bull_trend ? 1 : 0) + ((bull_fvg) ? 1 : 0) + (atr_ok ? 1 : 0) + (rsi_bull_ok ? 1 : 0)
short_confs = (bear_trend ? 1 : 0) + ((bear_fvg) ? 1 : 0) + (atr_ok ? 1 : 0) + (rsi_bear_ok ? 1 : 0)

long_signal = in_session and (long_confs >= need_confs)
short_signal = in_session and (short_confs >= need_confs)

// ======= Backtester internal state (flat vars replacing custom type) =======
var float active_entry_price = na
var int active_entry_bar = 0
var string active_side = ""
var float active_sl_price = na
var float active_tp_price = na
var int active_exit_bar = 0
var float active_exit_price = na
var string active_exit_reason = ""
var bool in_trade = false

// stats
var int trades_count = 0
var int wins = 0
var int losses = 0
var float cum_pips = 0.0
var float balance = start_balance

// helper: convert pips to price units
f_pips_to_price(pips) =>
pips * pip_size

// helper: price -> pips difference (for buys positive if price increased)
f_price_to_pips(delta_price) =>
delta_price / pip_size

// helper: find nearest swing high above entry price within lookback_for_tp (TP for long)
find_tp_for_long(entry_price) =>
float tp = na
float hh = ta.highest(high, lookback_for_tp)
if hh > entry_price
for i = 1 to lookback_for_tp
if high[i] > entry_price
tp := high[i]
break
tp

find_tp_for_short(entry_price) =>
float tp = na
float ll = ta.lowest(low, lookback_for_tp)
if ll < entry_price
for i = 1 to lookback_for_tp
if low[i] < entry_price
tp := low[i]
break
tp

// On each new bar: handle entry logic or manage active trade
if not in_trade
// enter long
if long_signal
float entry = close
float sl_price = entry - f_pips_to_price(sl_pips)
float tp_price = find_tp_for_long(entry)
// fallback TP: if no swing high found, set TP at entry + 1.5*ATR
if na(tp_price)
tp_price := entry + 1.5 * atr_val
// open trade (assign to flat vars)
active_entry_price := entry
active_entry_bar := bar_index
active_side := "long"
active_sl_price := sl_price
active_tp_price := tp_price
active_exit_bar := na
active_exit_price := na
active_exit_reason := ""
in_trade := true
trades_count += 1
if show_trade_markers
label.new(bar_index, low, "ENTER LONG\nSL " + str.tostring(sl_pips) + "pips\nTP->" + str.tostring(tp_price), style=label.style_label_up, color=color.new(color.green, 10), textcolor=color.white)
// enter short
if short_signal
float entry = close
float sl_price = entry + f_pips_to_price(sl_pips)
float tp_price = find_tp_for_short(entry)
if na(tp_price)
tp_price := entry - 1.5 * atr_val
active_entry_price := entry
active_entry_bar := bar_index
active_side := "short"
active_sl_price := sl_price
active_tp_price := tp_price
active_exit_bar := na
active_exit_price := na
active_exit_reason := ""
in_trade := true
trades_count += 1
if show_trade_markers
label.new(bar_index, high, "ENTER SHORT\nSL " + str.tostring(sl_pips) + "pips\nTP->" + str.tostring(tp_price), style=label.style_label_down, color=color.new(color.red, 10), textcolor=color.white)
else
// trade is active — check for hits within current bar using high/low
string side = active_side
float curHigh = high
float curLow = low
bool hitTP = false
bool hitSL = false
// For long: TP hit if high >= tp_price; SL hit if low <= sl_price
if side == "long"
if curHigh >= active_tp_price
hitTP := true
else if curLow <= active_sl_price
hitSL := true
else
// short
if curLow <= active_tp_price
hitTP := true
else if curHigh >= active_sl_price
hitSL := true

// Also close if max holding exceeded
bool timed_out = (bar_index - active_entry_bar) >= max_bars_holding ? true : false

// Opposite signal closure (optional): if opposite signal appears we can close at bar open (conservative)
bool opp_close = false
if side == "long" and short_signal
    opp_close := true
if side == "short" and long_signal
    opp_close := true

if hitTP or hitSL or timed_out or opp_close
    float exit_price = na
    string exit_reason = ""
    if hitTP
        exit_price := active_tp_price
        exit_reason := "TP"
    else if hitSL
        exit_price := active_sl_price
        exit_reason := "SL"
    else if opp_close
        exit_price := open  // close at next bar open
        exit_reason := "opp_signal"
    else
        exit_price := close
        exit_reason := "timeout"

    // compute pips gained
    float pips_gained = side == "long" ? f_price_to_pips(exit_price - active_entry_price) : f_price_to_pips(active_entry_price - exit_price)
    cum_pips += pips_gained
    // USD profit
    float usd_profit = pips_gained * pip_value_usd
    balance += usd_profit

    // win/loss accounting
    if pips_gained > 0
        wins += 1
    else
        losses += 1

    // mark exit on chart
    if show_trade_markers
        label.new(bar_index, exit_price, "EXIT " + exit_reason + "\nPips:" + str.tostring(pips_gained, format.mintick) + "\nBal:" + str.tostring(math.round(balance,2)), style=label.style_label_left, color=color.new(color.gray, 10), textcolor=color.white)

    // reset
    in_trade := false
    active_entry_price := na
    active_entry_bar := 0
    active_side := ""
    active_sl_price := na
    active_tp_price := na
    active_exit_bar := na
    active_exit_price := na
    active_exit_reason := ""

// ========== Stats label on chart ==========
var label stats_label = na
if barstate.islast
if not na(stats_label)
label.delete(stats_label)
win_rate = trades_count > 0 ? math.round(100 * wins / trades_count) : 0
stats_txt = "GGbest Backtest Lite\nTF: " + timeframe.period + "\nTrades: " + str.tostring(trades_count) + " Wins: " + str.tostring(wins) + " Losses: " + str.tostring(losses) + "\nWin%: " + str.tostring(win_rate) + "% CumPips: " + str.tostring(math.round(cum_pips,1)) + "\nBalance: $" + str.tostring(math.round(balance,2))
if show_stats
stats_label := label.new(bar_index, high, stats_txt, style=label.style_label_right, color=color.new(color.blue,85), textcolor=color.white, yloc=yloc.abovebar)

// ========== Plots for trend and FVG ==========
plot(ema_fast, color=color.blue)
plot(ema_slow, color=color.orange)
// use bull_fvg and bear_fvg booleans for plotshape; ensure they exist
plotshape(bull_fvg, title="BullFVG", location=location.belowbar, style=shape.circle, size=size.tiny, color=color.green)
plotshape(bear_fvg, title="BearFVG", location=location.abovebar, style=shape.circle, size=size.tiny, color=color.red)

// static alerts to notify of signals (constant message)
alertcondition(long_signal, title="GGbest Long Signal (alert)", message="GGbest: Long signal - check chart")
alertcondition(short_signal, title="GGbest Short Signal (alert)", message="GGbest: Short signal - check chart")

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions