Indicator · Pine Script v5 · On the chart

Portfolio Backtester

by f-cksociety· code credited to pAulseperformance

The script is a Pine Script v5 indicator that emulates strategy backtesting directly on the price chart.

View on GitHub

GitHub stars11on the repository
Pine versionv5runs as is
Lines of code1,828
Settings40inputs
LicenceMPL-2.0
Last update4 years ago20 Sep 2022

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

1,828 lines of Pine Script v5 from pbe.pine, published under MPL-2.0. Keep the author's credit and licence lines when you reuse it.

pbe.pine
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © pAulseperformance

// 1142: Updated to version 5
// 1143: Set to overlay true
// 1145: Fixed Fees bug. Fixed Entry A and B reversal switch.
// 1158: Added labels for entry/exits.

// Portfolio

// Net Profit (NP) - Shows profitability.

// Number of Trades (#T) - Shows # of trades taken during backtesting period

// Average Trade Length (ATL) - Shows avg # of days in a trade

// Maximum Drawdown (MD) - Max peak-to-valley equity drawdown during backtesting period. This number defines the minimum amount of capital required to trade the system. Typically, this shouldn’t be lower than 34% and we will want to allow for at least 50% beyond this number.

// Maximum Loss (ML) - Shows largest loss experienced on a per-trade basis. Normally, don’t want to exceed more than 1-2 % of equity. 

// Maximum Drawdown Duration (MDD) - The longest duration of a drawdown in equity prior to a new equity peak. This number is important to help us psychologically understand how long we can expect to wait for a new peak in account equity. 

// Maximum Consecutive Losses (MCL) - The max consecutive losses endured throughout the backtesting period. Another important metric for trader psychology, this will help you understand how many losses you should be prepared to handle.

// Profit to Maximum Drawdown (P:MD) - A ratio for the average profit to the maximum drawdown. The higher the ratio is, the better. Large profits and small losses contribute to a good PMD.  This metric allows us to examine the profit with respect to risk.

// Profit Loss Ratio (P:L) - Average profit over the average loss. Typically this number should be higher in trend following systems. Mean reversion systems show lower values, but compensate with a better win %.

// Percent Winners (% W) - The percentage of winning trades. Trend systems will usually have lower win percentages, since statistically the market is only trending roughly 30% of the time. Mean reversion systems typically should have a high % W.

// Time Percentage (Time %) - The amount of time that the system has an open position. The more time you are in the market, the more you are exposed to the market's risk, not to mention you could be using that money for something else right? 

// Return on Investment (ROI) - Your Net Profit over your initial investment, represented as a percentage. You want this number to be positive and high.

// Open Profit (OP) - If the strategy has any open positions, the floating value will be represented here.

// Trading Days (TD) - An important metric showing how many days the strategy was active. This is good to know and will be valuable in understanding how long you will need to run this strategy in order to achieve results.


//@version=5
indicator('Portfolio Backtester', overlay=true, precision=6, max_lines_count=500, max_labels_count=500)




// ——————————————— Inputs and states
// ————— A. Option list constants.
// Trade Direction
TD1 = 'Both'
TD2 = 'Longs Only'
TD3 = 'Shorts Only'
// Strategies
EN0 = '0. None'
EN1 = '1. Entry Strategy A'
EN2 = '2. Entry Strategy B'

// Entry Stops
ES0 = '0. None'
ES2 = '2. ATR multiple'
ES3 = '3. Fixed %'
ES4 = '4. Fixed Value'

// In Trade Stops
IS0 = '0. None'
IS1 = '1. Trailing at X multiple'
IS2 = '2. Trailing at %'
IS3 = '3. Trailing at Fixed Value'
IS4 = '4. Move To Break Even'
IS5 = '5. ATR multiple'
IS8 = '8. Last Lo/Hi'
// In Trade Stop Kick In
IK1 = '1. Stop >= Entry Stop'
IK2 = '2. Stop >= Entry +/- X multiple'
IK3 = '3. Stop >= Entry +/- %'
IK4 = '4. Stop >= Entry +/- Fixed Value'
IK5 = '5. Price >= Entry +/- %'
// Pyramiding
PY1 = 'Off'
PY3 = 'On'
PT1 = '1. Every X Multiple'
PT2 = '2. Every % Increment'
PT3 = '3. Every Price Increment'
PT4 = '4. On same entry'
PT5 = '5. On different entry'
PT6 = '6. At every opportunity'
// Slippage
SL1 = '1. None'
SL2 = '2. Percentage'
SL3 = '3. Fixed Value'
// Fees
FE1 = '1. None'
FE2 = '2. Percentage'
FE3 = '3. Fixed Value'
// Position Sizing
PS1 = '1. Proportional to Stop -> Variable'
PS2 = '2. % of Equity -> Variable'
PS3 = '3. % of Capital -> Fixed'


// ————— C. Entries
TradeDirection      = input.string(TD1, 'Trade Direction', options=[TD1, TD2, TD3], group='Entries')
GenerateLongs       = TradeDirection != TD3
GenerateShorts      = TradeDirection != TD2
EntryA              = input.string(EN1, 'Entry Strategy A', options=[EN0, EN1, EN2], group='Entries', inline='EA')
EntryAReverse       = input.bool(false, 'Reverse Signals', tooltip='Long Signals will be interpreted as Short Signals and vise versa', inline='EA', group='Entries')
EntryB              = input.string(EN0, 'Entry Strategy B', options=[EN0, EN1, EN2], group='Entries', inline='EB')
EntryBReverse       = input.bool(false, 'Reverse Signals', tooltip='Long Signals will be interpreted as Short Signals and vise versa', inline='EB', group='Entries')


// Split the random entries in 2 so we can generate from 2 different seeds.
EntryType1 = EntryA == EN1 or EntryB == EN1
EntryType2 = EntryA == EN2 or EntryB == EN2


// ————— E. Entry stops
EntryStopType       = input.string(ES0, 'Entry Stop Selection', options=[ES0, ES2, ES3], group='Entry Stops')
EntryStopType0      = EntryStopType == ES0
EntryStopType2      = EntryStopType == ES2
EntryStopType3      = EntryStopType == ES3
EntryStopType2Mult  = input.float(1.5, '  2. ATR', minval=0.05, step=0.1, group='Entry Stops', inline='ES2')
EntryStopType2Len   = input.int(14, '', minval=2, group='Entry Stops', inline='ES2', tooltip='ATR Multiple, ATR Length')
EntryStopType3Pct   = input.float(3.0, '  3. Fixed %', minval=0.0001, step=0.1, group='Entry Stops')


// ————— D. Filters
FilterType1         = input.bool(false, '1. Random', group='Filters')
FilterType2         = input.bool(false, '2. MACD', inline='FT2', group='Filters', tooltip='Long Filter: MACD > 0\nShort Filter: MACD < 0')
FilterType2Len1     = input.int(12, '', inline='FT2', group='Filters')
FilterType2Len2     = input.int(26, '', inline='FT2', group='Filters')
FilterType2Len3     = input.int(9, '', inline='FT2', group='Filters', tooltip='Fast Length, Slow Length, Signal Line')
FilterType3         = input.bool(false, '3. Bar direction=Trade direction', group='Filters', tooltip='Long filter: close>open\nShort Filter: close<open')
FilterType4         = input.bool(false, '4. Rising Volume', group='Filters', tooltip='Long Filter: Volume Increasing\nShort Filter: Volume Decreasing')
FilterType5         = input.bool(false, '5. Rising/Falling MA ↴           ', group='Filters', tooltip='Long Filter: SMA is Rising\nShort Filter: SMA Falling')
FilterType5Len      = input.int(50, '  ↳ Length', minval=2, group='Filters')
FilterType5Bars     = input.int(1, '  ↳ For n Bars', minval=1, group='Filters')
FilterType6         = input.bool(false, '6. Maximum % Stop allowed on entry', group='Filters', inline='Filter Max % Stop')
FilterType6MaxRisk  = input.float(6.0, '  ', minval=0.0, step=0.5, group='Filters', inline='Filter Max % Stop')
FilterType7         = input.bool(false, '7. Maximum close change (%) allowed on entry', group='Filters', inline='FT7')
FilterType7IncPct   = input.float(10.0, '', minval=0.0, step=0.5, group='Filters', inline='FT7')
FilterType8         = input.bool(false, '8. Minimum close change (%) allowed on entry', group='Filters', inline='FT8')
FilterType8IncPct   = input.float(1.0, '', minval=0.0, step=0.5, group='Filters', inline='FT8')
FilterType9         = input.bool(false, '9. RSI OB/OS ↴           ', group='Filters')
FilterType9Len      = input.int(20, '  ↳ Length', minval=2, group='Filters')
FilterType9OS       = input.int(25, '  ↳ Oversold Level', minval=0, maxval=100, group='Filters')
FilterType9OB       = input.int(75, '  ↳ Overbought Level', minval=0, maxval=100, group='Filters')
FilterType10        = input.bool(false, '10. MA', group='Filters', inline='FT10')
FilterType10MAType  = input.string('SMA', '', options=['SMA', 'EMA', 'WMA', 'RMA', 'VWMA', 'SWMA'], group='Filters', inline='FT10')
FilterType10Source  = input.source(close, '', group='Filters', inline='FT10')
FilterType10Len     = input.int(200, '', minval=1, group='Filters', inline='FT10', tooltip='MA Type, MA Source, MA Length\nLong = close > MA\nShort = close < MA')
// FilterType10 = input(true, "10. Chandelier", group="Filters")
// FilterType10Len = input(20, "... Length", minval=2, group="Filters")
// FilterType10Mult = input(3, "... ATR Multiple", minval=0, step=0.25, group="Filters")
// FilterType11 = input(false, "11. MA Squize", group="Filters")
// FilterType11Len1 = input(5, "... Fast MA", minval=2, group="Filters")
// FilterType11Len2 = input(21, "... Slow MA", minval=2,group="Filters")


// // ————— F. In-trade stops
InTradeStopType         = input.string(IS0, 'In-Trade Stop Selection', options=[IS0, IS1, IS2, IS3, IS4, IS5, IS8], group='In Trade Stops')
InTradeStopDisabled     = InTradeStopType == IS0
InTradeStopType1        = InTradeStopType == IS1
InTradeStopType2        = InTradeStopType == IS2
InTradeStopType3        = InTradeStopType == IS3
InTradeStopType4        = InTradeStopType == IS4
InTradeStopType5        = InTradeStopType == IS5
InTradeStopType8        = InTradeStopType == IS8
InTradeStopType1Mult    = input.float(1.0, '  1. X (Entry Stop\'s Amplitude) Multiple', minval=0.001, step=0.1, group='In Trade Stops', inline='If you have an Entry Stop this will be a multiple of the gap between the Entry and the Entry Stop')
InTradeStopType2Pct     = input.float(2.0, '  2. %', minval=0.001, step=0.1, group='In Trade Stops')
InTradeStopType3Val     = input.float(0.001, '  3. Value', minval=0.00000001, step=0.0001, group='In Trade Stops')
InTradeStopType567Len   = input.int(20, '  5. ATR', minval=2, group='In Trade Stops', inline='5. In Trade Stop')
InTradeStopType567Mult  = input.float(3.0, '', minval=0.001, step=0.5, group='In Trade Stops', inline='5. In Trade Stop', tooltip='ATR Multiple, ATR Length')

InTradeKickType         = input.string(IK1, 'In-Trade Stop Kicks In When...', options=[IK1, IK2, IK3, IK4, IK5], group='In Trade Stop Kick In')
InTradeKickType1        = InTradeKickType == IK1
InTradeKickType2        = InTradeKickType == IK2
InTradeKickType3        = InTradeKickType == IK3
InTradeKickType4        = InTradeKickType == IK4
InTradeKickType5        = InTradeKickType == IK5
InTradeKickType2Mult    = input.float(2.0, '  2. X Multiple (+ is in trade direction, - is against)', step=0.05, group='In Trade Stop Kick In', tooltip='Make sure you have an Entry Stop in place for this to be effective.')
InTradeKickType3Pct     = input.float(3.0, '  3. %  (+/-)', step=0.1, group='In Trade Stop Kick In')
InTradeKickType4Val     = input.float(0.001, '  4. Value  (+/-)', step=0.0001, group='In Trade Stop Kick In')
InTradeKickType5Pct     = input.float(2.0, '  5. %  (+/-)', step=0.1, group='In Trade Stop Kick In') / 100

// ————— G. Pyramiding rules
Pyramiding              = input.string(PY1, 'Pyramiding', options=[PY1, PY3], group='Pyramiding', inline='Pyramiding Amount')
PyramidingOff           = Pyramiding == PY1
PyramidingOn            = Pyramiding == PY3
PyramidingMaxCnt        = input.int(1, 'Max # of pyramiding entries', minval=1, group='Pyramiding', inline='Pyramiding Amount')
PyramidingType          = input.string(PT6, 'Pyramiding is allowed...', options=[PT1, PT2, PT3, PT4, PT5, PT6], group='Pyramiding', inline='Pyramiding Type')
PyramidingFilterNeeded  = input.bool(false, 'Filter must allow entry', group='Pyramiding', inline='Pyramiding Type')
PyramidingPosMult       = input.float(1.0, 'Position Size Multiple of Original Entry Position', minval=0.00000001, step=0.1, group='Pyramiding')
Pyramiding1MultX        = input.float(1.0, '  1. Multiple of X (stop)', minval=0.00000001, group='Pyramiding')
Pyramiding2Pct          = input.float(2.0, '  2. % Increment', minval=0.00000001, group='Pyramiding')
Pyramiding3Val          = input.float(0.00001, '  3. Price Increment', minval=0.00000001, group='Pyramiding')
PyramidingType1         = not PyramidingOff and PyramidingType == PT1
PyramidingType2         = not PyramidingOff and PyramidingType == PT2
PyramidingType3         = not PyramidingOff and PyramidingType == PT3
PyramidingType4         = not PyramidingOff and PyramidingType == PT4
PyramidingType5         = not PyramidingOff and PyramidingType == PT5
PyramidingType6         = not PyramidingOff and PyramidingType == PT6

// ————— H. Hard Exits
ExitType0               = input.bool(true, '0. Opposite Signal          ', group='Hard Exits', inline='Exit Type 0', tooltip='Trade will exit when opposite signal is detected.')
i_rvrsePositionExit0    = input.bool(true, 'Reverse Position', group='Hard Exits', inline='Exit Type 0', tooltip='Trade will be exited and a new trade in the opposite direction will be opened on the next bar')
ExitType1               = input.bool(false, '1. SROC Falling/Rising X ↴', group='Hard Exits', inline='Exit Type 1', tooltip='Trade will exit when the SROC changes from F/R to nothing.')
Exit1Length             = input.int(7, '', minval=1, inline='Exit Type 1.0', group='Hard Exits')
Exit1Smoothing          = input.int(10, '', minval=1, inline='Exit Type 1.0', group='Hard Exits', tooltip='ROC Length, ROC Smoothing Length')
Exit1Source             = input.source(close, '', inline='Exit Type 1.1', group='Hard Exits')
Exit1Fall               = input.int(8, '', inline='Exit Type 1.1', group='Hard Exits')
Exit1Rise               = input.int(8, '', inline='Exit Type 1.1', group='Hard Exits', tooltip='ROC Source, # of Bars to be rising, # of bars to be falling')


ExitType4               = input.bool(false, '4. RSI Crosses ↴           ', group='Hard Exits', inline='Exit Type 4')
i_rvrsePositionExit4    = input.bool(false, 'Reverse Position', group='Hard Exits', inline='Exit Type 4', tooltip='Trade will be exited and a new trade in the opposite direction will be opened on the next bar')
ExitType4Len            = input.int(14, '  ↳ Length', minval=2, group='Hard Exits')
ExitType4LongLvl        = input.int(70, '  ↳ Exit Long Crossunder Level', minval=0, maxval=100, group='Hard Exits')
ExitType4ShortLvl       = input.int(30, '  ↳ Exit Short Crossover Level', minval=0, maxval=100, group='Hard Exits')
ExitType6               = input.bool(false, '6. When Take Profit Level (multiple of X) is reached', group='Hard Exits', inline='ET6')
ExitType6Mult           = input.float(2.0, '', minval=0.0, group='Hard Exits', inline='ET6', tooltip='X Multiple is your stop gap.')
ExitType7               = input.bool(false, '7. When % change is reached', group='Hard Exits', inline='ET7')
ExitType7Pct            = input.float(2.0, '', minval=0.0, group='Hard Exits', inline='ET7') / 100
ExitType8               = input.bool(false, '8. When % gain (equity) is reached', group='Hard Exits', inline='ET8')
ExitType8Pct            = input.float(2.0, '', minval=0.0, group='Hard Exits', inline='ET8') / 100

// ————— I. Slippage
SlippageType        = input.string(SL3, 'Slippage', options=[SL1, SL2, SL3], group='Slippage')
SlippageType2InPct  = input.float(0.25, '  2. % Slippage on Entry', minval=0.0, step=0.1, group='Slippage')
SlippageType2OutPct = input.float(0.25, '  2. % Slippage on Exit', minval=0.0, step=0.1, group='Slippage')
SlippageType3InVal  = input.float(0, '  3. Fixed Slippage on Entry (Ticks)', minval=0.0, step=0.0001, group='Slippage', confirm=true)
SlippageType3OutVal = input.float(0, '  3. Fixed Slippage on Exit (Ticks)', minval=0.0, step=0.0001, group='Slippage', confirm=true)
SlippageType2       = SlippageType == SL2
SlippageType3       = SlippageType == SL3

// ————— J. Fees
FeesType        = input.string(FE2, 'Fees', options=[FE1, FE2, FE3], group='Fees')
FeesType2InPct  = input.float(0.1, '  2. % of Position on Entry (Curr)', minval=0.0, step=0.1, group='Fees', confirm=true)
FeesType2OutPct = input.float(0.1, '  2. % of Position on Exit (Curr)', minval=0.0, step=0.1, group='Fees', confirm=true)
FeesType3InVal  = input.float(10.0, '  3. Fixed Amount on Entry (Curr)', minval=0.0, group='Fees')
FeesType3OutVal = input.float(10.0, '  3. Fixed Amount on Exit (Curr)', minval=0.0, group='Fees')
FeesType2       = FeesType == FE2
FeesType3       = FeesType == FE3


// ————— M. Date range filtering
DateFilter      = input.bool(false, 'Date Range Filtering', group='Date Filter')
FromDate        = input.time(title='  From Date', defval=timestamp('19 Feb 2021 00:00 +0000'), group='Date Filter')
ToDate          = input.time(title='  To Date', defval=timestamp('13 Mar 2021 00:00 +0000'), group='Date Filter')
TradeDateIsAllowed() =>
    DateFilter ? time >= FromDate and time <= ToDate : true


// ————— O. Position sizing
PosType         = input.string(PS2, 'Position Sizing', options=[PS1, PS2, PS3], group='Position Sizing')
PosType1        = PosType == PS1
PosType2        = PosType == PS2
PosType3        = PosType == PS3
PosTypeCapital  = input.float(100000.0, title='Initial Capital (In Units of Currency)', minval=0.0, step=10000, group='Position Sizing', confirm=true)
PosType1Risk    = input.float(1.0, title='  1. % of Equity Risked Per Trade', minval=0.0, maxval=100.0, step=0.1, group='Position Sizing', tooltip=' (Stop % * Position)')
PosType1Cap     = input.float(50.0, title='  1. Cap on position sizes (% of Equity)', minval=0.0, maxval=100.0, step=10.0, group='Position Sizing')
PosType2Pct     = input.float(1.0, title='  2. % of Equity', minval=0.0, maxval=100.0, step=1.0, group='Position Sizing', confirm=true)
PosType3Pct     = input.float(10.0, title='  3. % of Initial Capital', minval=0.0, maxval=100.0, step=5.0, group='Position Sizing')

// ————— P. Risk Tolerance
i_RiskMaxDrawdown   = input.float(30.0, 'Max % Of Equity Drawdown Tolerated', confirm=true, tooltip='If DD exceeds this amount, the strategy will continue to run, but an alert will be generated. This also affects the table colors.', group='Risk Tolerance')
i_RiskMaxDrawdownCB = input.float(50.0, 'Max % Of Equity Drawdown Circuit Breaker', confirm=true, tooltip='If DD exceeds this amount, the strategy will stop.', group='Risk Tolerance')




// ————— Q. Functions
f_roc() =>
    // Rate of Change Function
    100 * math.log(Exit1Source / Exit1Source[Exit1Length])

f_getMA(_maType, _maSource, _maLength) =>
    if _maType == 'SMA'
        ta.sma(_maSource, _maLength)
    else if _maType == 'EMA'
        ta.ema(_maSource, _maLength)
    else if _maType == 'WMA'
        ta.wma(_maSource, _maLength)
    else if _maType == 'VWMA'
        ta.vwma(_maSource, _maLength)
    else if _maType == 'RMA'
        ta.rma(_maSource, _maLength)
    else if _maType == 'SWMA'
        ta.swma(_maSource)





// ——————————————— Strategies.
f_RSICross() =>
    rsi   = ta.rsi(close, 14)
    
    Long  = ta.crossover(rsi, 30)
    Short = ta.crossunder(rsi, 70)

    Signal = Long ? 1 : Short ? -1 : 0
    Signal



// Customize these two for your own entry strategies.
// You can add in a function or customize the function itself.
f_entryStrategyA() =>
    f_RSICross()
    
f_entryStrategyB() =>
    Long  = false
    Short = false
    Signal = Long ? 1 : Short ? -1 : 0
    Signal


f_strategies(_strategy) =>
    // Function to aggregate strategies and convert binary signals to bool
    // [bool, bool]

    // Signal = 0
    Signal = if EntryType1 and _strategy == EN1
        f_entryStrategyA()
    else if EntryType2 and _strategy == EN2
        f_entryStrategyB()
    else
        0

    Long = Signal == 1
    Short = Signal == -1
    [Long, Short]

// ENTRY Functions
f_getEntry(InTrade, FilterLongOK, FilterShortOK) =>

    // ————— Entry 1: Entry Strategy A
    [Entry1Long, Entry1Short] = f_strategies(EN1)

    // ————— Entry 2: Entry Strategy B
    [Entry2Long, Entry2Short] = f_strategies(EN2)

    // ———————————————————— Get entry number (positive for longs, negative for shorts).
    // Distinguish between A and B selections.
    EntryNumberA = Entry1Long and EntryA == EN1 ? 1 : 
                   Entry2Long and EntryA == EN2 ? 2 : 
                   Entry1Short and EntryA == EN1 ? -1 : 
                   Entry2Short and EntryA == EN2 ? -2 : 0

    EntryNumberB = Entry1Long and EntryB == EN1 ? 1 :
                   Entry2Long and EntryB == EN2 ? 2 :
                   Entry1Short and EntryB == EN1 ? -1 : 
                   Entry2Short and EntryB == EN2 ? -2 :  0


    // Entry A numbers are as is, entry B numbers get 100 added/subtracted.
    EntryNumber = EntryNumberA != 0 ? EntryNumberA : 
                  EntryNumberB != 0 ? EntryNumberB + (EntryNumberB > 0 ? 100 : -100) : 0
    EntryNumber := EntryAReverse and EntryNumberA != 0 ? -EntryNumber :  
                   EntryBReverse and EntryNumberB != 0 ? -EntryNumber : EntryNumber
    EntryNumber

// HELPER Functions
OneZero(_bool) =>
    _bool ? 1 : 0


// ————— Converts current chart timeframe into a float minutes value.
f_resInMinutes() =>
    _resInMinutes = timeframe.multiplier * (timeframe.isseconds ? 1. / 60 : timeframe.isminutes ? 1. : timeframe.isdaily ? 60. * 24 : timeframe.isweekly ? 60. * 24 * 7 : timeframe.ismonthly ? 60. * 24 * 30.4375 : na)
    _resInMinutes

f_barsToDays(_bars) =>
    _minutes = f_resInMinutes() * _bars
    _days = _minutes / 60 / 24
    _days

f_timeToString(_t) =>
    str.tostring(year(_t), '0000') + '.' + str.tostring(month(_t), '00') + '.' + str.tostring(dayofmonth(_t), '00') + ' ' + str.tostring(hour(_t), '00') + ':' + str.tostring(minute(_t), '00') + ':' + str.tostring(second(_t), '00')


// DRAWDOWN Functions
f_getMaxDD(InTradeEquity, _initialCapital) =>
    // ————— Max drawdown (measured at every in-trade bar).
    // This max drawdown calc needs to maintain separate max and min equity points.
    // We are re-evaluating the max and min at each in-trade bar.
    // All-time in-trade equity high and min. Min resets when new high encountered.

    // Different than strategy tester which is based off trade-by-trade max Cumulative Profit - min cumulative profit.
    // More accurate this way becuase we see drawdown during trades.
    // Still based on Close-to-close and not high/low

    // ——————————————— Drawdown.
    // ————— Max drawdown (using InTradeEquity measured at every bar but only changes during in-trade bars).
    EquityMax = 0.0
    EquityMax := math.max(InTradeEquity, nz(EquityMax[1]))

    // Reset min when new high encountered.
    EquityMin = 0.0
    EquityMin := ta.change(EquityMax) ? EquityMax : math.min(InTradeEquity, nz(EquityMin[1]))

    // Max drawdown in equity. Percent
    Drawdown = (EquityMin - EquityMax) / EquityMax
    MaxDrawdown = 0.0
    MaxDrawdown := math.min(Drawdown, nz(MaxDrawdown[1]))
    // label.new(bar_index, high, tostring(Drawdown))
    // Drawdown Peak to Trough Equity
    MaxPTDrawdown = 0.0
    MaxPTDrawdown := math.min((EquityMin - EquityMax) * _initialCapital, nz(MaxPTDrawdown))

    // Maximum Drawdown Duration (MDD) - The longest duration of a drawdown in equity prior to a new equity peak. 
    // This number is important to help us psychologically understand how long we can expect to wait for a new peak in account equity. 
    var int MaxDDDur = 0
    var int MaxDDDurCount = 0
    MaxDDDurCount := EquityMax > 0 and TradeDateIsAllowed() ? ta.change(EquityMax) ? 0 : MaxDDDurCount + 1 : 0  // Warning has no effect on accuracy
    MaxDDDur := math.max(MaxDDDurCount, MaxDDDur)


    // Maximum Drawdown - Max peak-to-valley equity drawdown during backtesting period. This number defines the minimum amount of capital required to trade the system. Typically, this shouldn’t be lower than 34% and we will want to allow for at least 50% beyond this number.
    // MaxDDPct  = round(MaxDrawdown*100,2)   // Percent
    // MaxDDPT   = round(MaxPTDrawdown, 2)    // Absolute


    [MaxDrawdown * 100, MaxPTDrawdown, f_barsToDays(MaxDDDur)]


f_getMCL(Equity, Trades, All_Losing) =>
    // Maximum Consecutive Losses (MCL) - The max consecutive losses endured throughout the backtesting period. Another important metric for trader psychology, this will help you understand how many losses you should be prepared to handle in order to achieve netprofit.
    var int ConsecutiveLossesCount = 0
    ConsecutiveLossesCount := ta.change(Trades) ? ta.change(All_Losing) ? ConsecutiveLossesCount + 1 : 0 : ConsecutiveLossesCount  // Warning has no effect on accuracy.
    var int MaxConsecutiveLosses = 0
    MaxConsecutiveLosses := math.max(ConsecutiveLossesCount, MaxConsecutiveLosses)
    MaxConsecutiveLosses


// Aggregate All data 
f_getData(_initialCapital) =>
    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // —————————————————————————————————————— 1. Variable Initializations —————————————————————————————————————————————————————
    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // We declare global and trade vars and propagate values for those that need it.
    // Vars holding global stats for all trades are divided in 4 groups: Combined (First and pyramided entries), First entries, Pyramided entries and PEA (Post-Exit Analysis).
    // They start with All_, First_, Pyr_ or PEA_ respectively.
    // So-called local vars hold data relating to one trade at a time. They use the same prefix, without an underscore.

    // We deal with 4 different units in the code:
    //  1.  Quote currency (quote), i.e. the price on the chart.
    //  2.  X (X): the stop's amplitude,
    //  3.  Currency (curr): the currency of the Initial Capital.
    //  4.  % of Initial Capital (equ): a % representing a fraction of Initial Capital.

    // ———————————————————— A. Global data.
    // ————— Global stats (1st and pyramided entries)
    // All entries.
    All_Entries = 0
    All_Entries := nz(All_Entries[1])
    // All fees paid (equ).
    All_FeesEqu = 0.0
    All_FeesEqu := nz(All_FeesEqu[1])
    // Sum and avg of PLX (P&L expressed as mult of X).
    All_PLXTot = 0.0
    All_PLXTot := nz(All_PLXTot[1])
    All_PLXAvg = 0.0
    All_PLXAvg := nz(All_PLXAvg[1])
    // Total of all Entry position sizes (equ).
    All_PositionInTot = 0.0
    All_PositionInTot := nz(All_PositionInTot[1])
    // Profit factor.
    All_ProfitFactor = 0.0
    All_ProfitFactor := nz(All_ProfitFactor[1])
    // All slippage paid (equ).
    All_SlipEqu = 0.0
    All_SlipEqu := nz(All_SlipEqu[1])
    // Sum and avg of trade lengths.
    All_TradeLengthsTot = 0.0
    All_TradeLengthsTot := nz(All_TradeLengthsTot[1])
    All_TradeLengthsAvg = 0.0
    All_TradeLengthsAvg := nz(All_TradeLengthsAvg[1])
    // All currency volume traded (both entries and exits counted).
    All_VolumeTraded = 0.0
    All_VolumeTraded := nz(All_VolumeTraded[1])
    // Sum of all winning/losing trades.
    All_Winning = 0
    All_Winning := nz(All_Winning[1])
    All_Losing = 0
    All_Losing := nz(All_Losing[1])
    // Sum of winning/losing trade lengths.
    All_WinsTL = 0.0
    All_WinsTL := nz(All_WinsTL[1])
    All_LoseTL = 0.0
    All_LoseTL := nz(All_LoseTL[1])
    // P&L total (X) of all winning/losing trades.
    All_WinsX = 0.0
    All_WinsX := nz(All_WinsX[1])
    All_LoseX = 0.0
    All_LoseX := nz(All_LoseX[1])
    // P&L total (equ) of all winning/losing trades.
    All_WinsEqu = 0.0
    All_WinsEqu := nz(All_WinsEqu[1])
    All_LoseEqu = 0.0
    All_LoseEqu := nz(All_LoseEqu[1])
    // Average Winning Trade, and Average Losing Trade
    All_AvgWin = 0.0
    All_AvgWin := nz(All_AvgWin[1])
    All_AvgLos = 0.0
    All_AvgLos := nz(All_AvgLos[1])
    // Sum and avg of all stops (quote).
    All_XTot = 0.0
    All_XTot := nz(All_XTot[1])
    All_XAvg = 0.0
    All_XAvg := nz(All_XAvg[1])
    // Sum and avg of all stops (equ).
    All_XEquTot = 0.0
    All_XEquTot := nz(All_XEquTot[1])
    All_XEquAvg = 0.0
    All_XEquAvg := nz(All_XEquAvg[1])
    // Sum and avg of all stops as percentage of fill.
    All_XPctTot = 0.0
    All_XPctTot := nz(All_XPctTot[1])
    All_XPctAvg = 0.0
    All_XPctAvg := nz(All_XPctAvg[1])

    // ————— First entry stats.
    First_Entries = 0
    First_Entries := nz(First_Entries[1])
    First_FeesEqu = 0.0
    First_FeesEqu := nz(First_FeesEqu[1])
    First_PLXTot = 0.0
    First_PLXTot := nz(First_PLXTot[1])
    First_PLXAvg = 0.0
    First_PLXAvg := nz(First_PLXAvg[1])
    First_PositionInTot = 0.0
    First_PositionInTot := nz(First_PositionInTot[1])
    First_ProfitFactor = 0.0
    First_ProfitFactor := nz(First_ProfitFactor[1])
    First_SlipEqu = 0.0
    First_SlipEqu := nz(First_SlipEqu[1])
    First_TradeLengthsTot = 0.0
    First_TradeLengthsTot := nz(First_TradeLengthsTot[1])
    First_TradeLengthsAvg = 0.0
    First_TradeLengthsAvg := nz(First_TradeLengthsAvg[1])
    First_VolumeTraded = 0.0
    First_VolumeTraded := nz(First_VolumeTraded[1])
    First_Winning = 0
    First_Winning := nz(First_Winning[1])
    First_Losing = 0
    First_Losing := nz(First_Losing[1])
    First_WinsTL = 0
    First_WinsTL := nz(First_WinsTL[1])
    First_LoseTL = 0
    First_LoseTL := nz(First_LoseTL[1])
    // First_WinsX = 0.0, First_WinsX := nz(First_WinsX[1])
    // First_LoseX = 0.0, First_LoseX := nz(First_LoseX[1])
    First_WinsEqu = 0.0
    First_WinsEqu := nz(First_WinsEqu[1])
    First_LoseEqu = 0.0
    First_LoseEqu := nz(First_LoseEqu[1])
    First_XTot = 0.0
    First_XTot := nz(First_XTot[1])
    First_XAvg = 0.0
    First_XAvg := nz(First_XAvg[1])
    First_XEquTot = 0.0
    First_XEquTot := nz(First_XEquTot[1])
    First_XEquAvg = 0.0
    First_XEquAvg := nz(First_XEquAvg[1])
    First_XPctTot = 0.0
    First_XPctTot := nz(First_XPctTot[1])
    First_XPctAvg = 0.0
    First_XPctAvg := nz(First_XPctAvg[1])

    // ————— Pyramiding
    // All pyramid entries.
    Pyr_Entries = 0
    Pyr_Entries := nz(Pyr_Entries[1])
    Pyr_PLXTot = 0.0
    Pyr_PLXTot := nz(Pyr_PLXTot[1])
    Pyr_PLXAvg = 0.0
    Pyr_PLXAvg := nz(Pyr_PLXAvg[1])
    Pyr_ProfitFactor = 0.0
    Pyr_ProfitFactor := nz(Pyr_ProfitFactor[1])
    Pyr_VolumeTraded = 0.0
    Pyr_VolumeTraded := nz(Pyr_VolumeTraded[1])
    Pyr_Winning = 0
    Pyr_Winning := nz(Pyr_Winning[1])
    Pyr_Losing = 0
    Pyr_Losing := nz(Pyr_Losing[1])
    Pyr_WinsEqu = 0.0
    Pyr_WinsEqu := nz(Pyr_WinsEqu[1])
    Pyr_LoseEqu = 0.0
    Pyr_LoseEqu := nz(Pyr_LoseEqu[1])



    // ————— Risk/Equity management.
    // Starting Equity in the script is represented as value 1.0. It is converted at display time in units of currency
    // corresponding to whatever the user thinks he's using in the "Initial Capital" field.
    // Equity = Initial Capital + P&L. If equity reaches 0.0, ruin occurs and no more trades are entered.
    Equity = float(na)
    Equity := nz(Equity[1], 1.0)
    // Current ROI.
    ReturnOnEquity = 0.0
    ReturnOnEquity := nz(ReturnOnEquity[1])
    // True if ruin occurs.
    Ruin = false
    Ruin := Ruin[1]

    // ————— Risk Tolerance.
    // True if i_RiskMaxDrawdownCB is exceeded
    CircuitBreaker = false
    CircuitBreaker := CircuitBreaker[1]

    // ————— Close to close max drawdown (only measured from trade close to trade close).
    // Max and min close to close equity points, and end of trade max drawdown reached. Reset min when new high encountered.
    CToCEquityMax = 0.0
    CToCEquityMin = 0.0
    CToCDrawdown = 0.0
    // Max close to close drawdown in equity.
    CToCMaxDrawdown = 0.0
    CToCMaxDrawdown := nz(CToCMaxDrawdown[1])


    // Equity update in-trade to provide full equity max drawdown instead of close to close drawdown.
    InTradeEquity = 0.0
    InTradeEquity := nz(InTradeEquity[1])
    InTradeStartEquity = 0.0
    InTradeStartEquity := nz(InTradeStartEquity[1])


    // ———————————————————— B. Local data.
    // "Local" refers to data that is maintained while a trade is open or a PEA is ongoing.
    // ————— Trade management.
    // Entry level (open of bar following trigger).
    EntryOrder = 0.0
    EntryOrder := EntryOrder[1]
    // Entry price after slippage.
    EntryFill = 0.0
    EntryFill := EntryFill[1]
    // Stop level at Entry.
    EntryStop = 0.0
    EntryStop := EntryStop[1]
    // Entry stop amplitude in quote, from fill to stop (so includes slippage).
    entryStopGap = 0.0
    entryStopGap := entryStopGap[1]
    // Entry stop as % of entry fill.
    EntryXPct = 0.0
    EntryXPct := EntryXPct[1]
    // Entry stop (equ).
    EntryXEqu = 0.0
    EntryXEqu := EntryXEqu[1]
    // Exit level (order).
    ExitOrder = 0.0
    ExitOrder := ExitOrder[1]
    // Exit fill after slippage.
    ExitFill = 0.0
    ExitFill := ExitFill[1]
    // Fees paid on entry (equ).
    FeesPaidIn = 0.0
    FeesPaidIn := FeesPaidIn[1]
    // Fees paid on exit (equ).
    FeesPaidOut = 0.0
    FeesPaidOut := FeesPaidOut[1]
    // Holds last issued entry signal: + for longs, - for shorts.
    LastEntryNumber = 0
    LastEntryNumber := LastEntryNumber[1]
    // Last entry's price level, whether it was a normal or pyramided entry.
    LastEntryPrice = 0.0
    LastEntryPrice := LastEntryPrice[1]
    // Position size at entry (equ).
    PositionIn = 0.0
    PositionIn := PositionIn[1]
    // Position size at exit (equ).
    PositionOut = 0.0
    PositionOut := PositionOut[1]
    // Slippage incurred on entry (quote).
    SlipPaidIn = 0.0
    SlipPaidIn := SlipPaidIn[1]
    // Slippage incurred on exit (quote).
    SlipPaidOut = 0.0
    SlipPaidOut := SlipPaidOut[1]
    // Total trade slippage (equ).
    SlippageEqu = 0.0
    SlippageEqu := SlippageEqu[1]
    // Target level for hard exits.
    TakeProfitLevel = 0.0
    TakeProfitLevel := TakeProfitLevel[1]
    // Trade length in bars.
    TradeLength = 0
    TradeLength := TradeLength[1]
    // Max in-trade drawdown.
    TradeMaxDrawdown = 0.0
    TradeMaxDrawdown := TradeMaxDrawdown[1]

    EntryNumber = 0  // Current first entry number throughout trade.
    // EntryNumberA = 0        // Current first entry number from selecton A.
    // EntryNumberB = 0        // Current first entry number from selecton B.
    InTradeStop = 0.0  // Last valid in-trade stop (we always use previous bar's stop for comparison to price, so must be used indexed).
    MaxReached = 0.0  // Max high/low reached since beginning of trade.
    MinReached = 0.0  // Min high/low reached since beginning of trade.
    TradeDrawdown = 0.0  // Discrete drawdown at each bar in trade's history, to be used to determine trade max drawdown.
    TradePLX = 0.0  // Trade PL (X), after slippage.
    // TradePLXMax = 0.0       // Highest PL (X) reached during trade.
    // TradePLXMin = 0.0       // Lowest PL (X) reached during trade.
    // TradePLPct = 0.0       // Trade PL (%) after slippage.
    TradePLEqu = 0.0  // Net trade PL (equ), i.e. including slippage AND fees.

    // ————— Pyramiding.
    PyrEntries = 0
    PyrEntries := nz(PyrEntries[1])
    PyrEntryBarTot = 0
    PyrEntryBarTot := nz(PyrEntryBarTot[1])
    PyrEntryBarAvg = 0.0
    PyrEntryBarAvg := nz(PyrEntryBarAvg[1])
    PyrEntryFill = 0.0
    PyrEntryFillTot = 0.0
    PyrEntryFillTot := nz(PyrEntryFillTot[1])
    PyrEntryFillAvg = 0.0
    PyrEntryFillAvg := nz(PyrEntryFillAvg[1])
    PyrEntryOrder = 0.0
    PyrEntryX = 0.0
    PyrEntryX := nz(PyrEntryX[1])
    PyrEntryXTot = 0.0
    PyrEntryXTot := nz(PyrEntryXTot[1])
    PyrEntryXAvg = 0.0
    PyrEntryXAvg := nz(PyrEntryXAvg[1])
    PyrEntryXPctTot = 0.0
    PyrEntryXPctTot := nz(PyrEntryXPctTot[1])
    PyrEntryXPctAvg = 0.0
    PyrEntryXPctAvg := nz(PyrEntryXPctAvg[1])
    PyrFeesEquTot = 0.0
    PyrFeesEquTot := nz(PyrFeesEquTot[1])
    PyrFeesPaidIn = 0.0
    PyrFeesPaidOut = 0.0
    PyrEntryXPct = 0.0
    PyrPositionIn = 0.0
    PyrPositionOut = 0.0
    PyrPositionInTot = 0.0
    PyrPositionInTot := nz(PyrPositionInTot[1])
    PyrPositionInAvg = 0.0
    PyrPositionInAvg := nz(PyrPositionInAvg[1])
    PyrPositionInQtyTot = 0.0
    PyrPositionInQtyTot := nz(PyrPositionInQtyTot[1])
    PyrSlipPaidEqu = 0.0
    PyrSlipPaidIn = 0.0
    PyrSlipPaidOut = 0.0
    PyrSlipEquTot = 0.0
    PyrSlipEquTot := nz(PyrSlipEquTot[1])
    PyrTradePLX = 0.0
    PyrTradePLPct = 0.0
    PyrTradePLEqu = 0.0
    PyrTradeLengthsTot = 0
    PyrTradeLengthsTot := nz(PyrTradeLengthsTot[1])
    PyrTradeLengthsAvg = 0.0
    PyrTradeLengthsAvg := nz(PyrTradeLengthsAvg[1])


    // ———————————————————— C. State and misc vars.
    // These variables get updated after triggers and during trades.
    // They do not require propagation because they are derived from the state of other propagated information or previous states.
    EntryTrigger = false  // True when an entry trigger is detected.
    ExitTrigger = false  // True when an exit trigger is detected
    PyramidEntry = false  // True when a long/short pyramid entry occurs.
    InTrade = false  // True when in InLong or InShort is true.
    ExitCondition = false  // True when stop breached, take profit or target level reached.
    // ————— Longs
    LongEntryTrigger = false  // Becomes true on the bar when entry order is to be issued; false otherwise.
    InLong = false  // True from bar after entry trigger to bar of exit trigger, inclusively.
    // ————— Shorts
    ShortEntryTrigger = false  // Becomes true on the bar when entry order is to be issued; false otherwise.
    InShort = false  // True from bar after entry trigger to bar of exit trigger, inclusively.


    // Not sure where to put this. But needs to be propagated
    // ------ Stops
    StoppedCondition = false

    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // ——————————————————————————————————————————— 3. Entry Stops —————————————————————————————————————————————————————————————
    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // ————— OHLC rounded to mintick (used to standardize values across test as mintick sometimes varies during dataset).
    // Although situated in this module, are used all over the script.
    Ropen = math.round_to_mintick(open)
    Rhigh = math.round_to_mintick(high)
    Rlow = math.round_to_mintick(low)
    Rclose = math.round_to_mintick(close)
    // ————— Lowest/highest of open/close
    MinOC = math.round_to_mintick(math.min(open, close))
    MaxOC = math.round_to_mintick(math.max(open, close))
    // ————— Entry Stops init.
    EntryStopLong = MinOC
    EntryStopShort = MaxOC
    // Entry Stops are pre-calculated every bar so they are always at hand upon entering.
    // They cannot depend on information like a trade entry level as they must be calculated prior to entry.
    // // The entry stop wil be taken over by the In-trade stop when its kick-in rules allow for it.

    // ————— Entry Stop 2: ATR*Multiple
    EntryStop2_Atr = EntryStopType2 ? nz(ta.atr(EntryStopType2Len)) : 0
    EntryStop2_AtrGap = EntryStop2_Atr * EntryStopType2Mult
    EntryStop2Long = MinOC - EntryStop2_AtrGap
    EntryStop2Short = MaxOC + EntryStop2_AtrGap
    // ————— Entry Stop 3: Fixed Percent
    EntryStop3Long = MinOC * (1.0 - EntryStopType3Pct / 100)
    EntryStop3Short = MaxOC * (1.0 + EntryStopType3Pct / 100)

    // ———————————————————— Select Entry Stop chosen by user.
    EntryStopLong_ = EntryStopType2 ? EntryStop2Long : EntryStopType3 ? EntryStop3Long : 0.0
    EntryStopShort_ = EntryStopType2 ? EntryStop2Short : EntryStopType3 ? EntryStop3Short : 9e99
    // Round stop to tick size.
    EntryStopLong := math.round_to_mintick(EntryStopLong_)
    EntryStopShort := math.round_to_mintick(EntryStopShort_)



    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // ————————————————————————————————————————————— 4. Filters ———————————————————————————————————————————————————————————————
    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // ** If adding a new filter, be sure to include in Entry for Filter States

    // ————— Filter 1: Random
    Filter1Long = FilterType1 ? math.round(math.random(0, 10, 1)) == 1 : true
    Filter1Short = FilterType1 ? math.round(math.random(0, 10, 1)) == 1 : true
    // ————— Filter 2: MACD Hist
    _macdLine = FilterType2 ? nz(f_getMA('EMA', close, FilterType2Len1) - f_getMA('EMA', close, FilterType2Len2)) : 0.
    _macdSignal = FilterType2 ? nz(f_getMA('EMA', _macdLine, FilterType2Len3)) : 0.
    _macdHist = FilterType2 ? _macdLine - _macdSignal : 0.
    Filter2Long = FilterType2 ? _macdHist > 0 : true
    Filter2Short = FilterType2 ? _macdHist < 0 : true

    // ————— Filter 3: Bar direction
    Filter3Long = FilterType3 ? close > open : true
    Filter3Short = FilterType3 ? close < open : true
    // ————— Filter 4: Rising volume
    Filter4Long = FilterType4 ? ta.rising(volume, 1) : true
    Filter4Short = Filter4Long
    // ————— Filter 5: Rising/Falling MA
    Filter5Ma = FilterType5 ? nz(f_getMA('SMA', close, FilterType5Len)) : 0
    Filter5Long = FilterType5 ? ta.rising(Filter5Ma, FilterType5Bars) : true
    Filter5Short = FilterType5 ? ta.falling(Filter5Ma, FilterType5Bars) : true
    // ————— Filter 6: Maximum risk allowed at entry
    Filter6Long = FilterType6 ? close - EntryStopLong < close * FilterType6MaxRisk / 100 : true
    Filter6Short = FilterType6 ? EntryStopShort - close < close * FilterType6MaxRisk / 100 : true
    // ————— Filter 7: Maximum delta with previous close allowed at entry
    Filter7Long = FilterType7 ? close < close[1] * (1 + FilterType7IncPct / 100) : true
    Filter7Short = FilterType7 ? close > close[1] * (1 - FilterType7IncPct / 100) : true
    // ————— Filter 8: Minimum delta with previous close allowed at entry
    Filter8Long = FilterType8 ? close > close[1] * (1 + FilterType8IncPct / 100) : true
    Filter8Short = FilterType8 ? close < close[1] * (1 - FilterType8IncPct / 100) : true

    // ————— Filter 9: RSI OS/OB
    Rsi = FilterType9 ? ta.rsi(close, FilterType9Len) : 0
    RsiOS = Rsi < FilterType9OS
    RsiOB = Rsi > FilterType9OB
    Filter9Long = FilterType9 ? not RsiOB : true
    Filter9Short = FilterType9 ? not RsiOS : true
    // ————— Filter 10: Moving Average
    Filter10Ma = FilterType10 ? f_getMA(FilterType10MAType, FilterType10Source, FilterType10Len) : 0.
    Filter10Long = FilterType10 ? close > Filter10Ma : true
    Filter10Short = FilterType10 ? close < Filter10Ma : true
    // ————— Filter 10: Chandelier
    // Filter10Long = FilterType10? C2Bull : true
    // Filter10Short = FilterType10? C2Bear : true
    // ————— Filter 11: MA Squize
    // Filter11Long = FilterType11? MASqzBull : true
    // Filter11Short = FilterType11? MASqzBear : true


    // ———————————————————— Assemble filters
    FilterLongOK = Filter1Long and Filter2Long and Filter3Long and Filter4Long and Filter5Long and Filter6Long and Filter7Long and Filter8Long and Filter9Long and Filter10Long
    FilterShortOK = Filter1Short and Filter2Short and Filter3Short and Filter4Short and Filter5Short and Filter6Short and Filter7Short and Filter8Short and Filter9Short and Filter10Short


    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // ————————————————————————————————————————————— 5. Entries ———————————————————————————————————————————————————————————————
    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

    EntryNumber := f_getEntry(InTrade, FilterLongOK, FilterShortOK)

    // ————— Whipsaw Waiting Period. Waits x bars after signal is generated. If an opposite signal is generated the waiting period is reset.
    WhipSawWait = input.bool(false, 'WhipSaw Wait', group='Filters', inline='WSW', tooltip='Waits x bars after entry signal is generated. If an opposite signal is generated the waiting period is reset.')
    WhipSawLen = input.int(3, '', group='Filters', inline='WSW')

    if WhipSawWait
        var _wait = false
        var _waiting = 0
        barsAgoLong = nz(ta.barssince(EntryNumber > 0))
        barsAgoShort = nz(ta.barssince(EntryNumber < 0))
        // Reset wait period if opposite signal happens within waiting period.
        if ta.cross(barsAgoLong, barsAgoShort)
            _waiting := 0
            _waiting

        var SavedEntryNumber = 0
        if EntryNumber != 0 and WhipSawWait
            _wait := true
            SavedEntryNumber := EntryNumber
            SavedEntryNumber

        if _wait
            _waiting := _waiting + 1
            _waiting

        if _wait and _waiting <= WhipSawLen
            EntryNumber := 0
            EntryNumber
        else if _wait and _waiting > WhipSawLen
            EntryNumber := SavedEntryNumber
            _waiting := 0
            _wait := false
            _wait





    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // ————————————————————————————————— 6. Trade Entry and In-trade Processing ———————————————————————————————————————————————
    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // Here we:
    //  A. Process 1st entries.
    //  B. Process pyramided entries.
    //  C. Update in-trade numbers for display purposes.

    // ———————————————————— Trade state management
    // ————— Determine if we have entered a trade and propagate state until we exit.
    InLong := LongEntryTrigger[1] or InLong[1] and not ExitCondition[1]
    InShort := ShortEntryTrigger[1] or InShort[1] and not ExitCondition[1]
    // ————— Merge states for ease of logical testing.
    InTrade := InLong or InShort
    FirstEntry1stBar = EntryTrigger[1] and not PyramidEntry[1]
    PyramidEntry1stBar = EntryTrigger[1] and PyramidEntry[1]


    // ———————————————————— A. Process 1st entries.
    // ————— If a trade's first entry (as opposed to pyramiding entries) must be entered, process it.
    // While this code appears earlier in the script than the entry trigger detection that comes in module 10,
    // it is only executed at the bar following the one where the trigger is detected.
    if FirstEntry1stBar
        // ————— Handle trade-opening tasks.
        // Suppose market entry order is given at open.
        EntryOrder := Ropen
        // Begin counting trade length.
        TradeLength := 1
        // Reset max and min point reached during trade.
        MaxReached := InLong ? Rhigh : Rlow
        MinReached := InLong ? Rlow : Rhigh
        // Save last issued signal number.
        LastEntryNumber := EntryNumber[1]

        // Calculate what slippage should be before hi/lo constraints.
        SlipPaidIn := SlippageType2 ? math.round_to_mintick(EntryOrder * SlippageType2InPct / 100) : SlippageType3 ? math.round_to_mintick(SlippageType3InVal * syminfo.mintick) : 0.0
        // Entry fill price including tentative slippage.
        EntryFill := math.max(0.0, EntryOrder + (InLong ? SlipPaidIn : -SlipPaidIn))
        // Cap fill to bar's hi/lo (can't slip more/less that hi/lo).
        EntryFill := InLong ? math.min(Rhigh, EntryFill) : math.max(Rlow, EntryFill)
        // Calculate actual slippage.
        SlipPaidIn := math.abs(EntryFill - EntryOrder)
        // Save fill for pyramiding rules validations.
        LastEntryPrice := EntryFill
        // Initialized upon entry and constant throughout trade, InTradeStop will usually take over in further trade management.
        EntryStop := InLong ? EntryStopLong : EntryStopShort


        // ————— Calculate X upon entry.
        // This amplitude in quote currency is the fundamental unit of risk for the trade, which we call X.
        // We also keep a version of it expressed as a % of the entry fill, and another in equ. Notice that entry slippage affects the size of X.
        entryStopGap := math.abs(EntryFill - EntryStop)
        EntryXPct := entryStopGap / EntryFill

        // PositionIn size is determined according to user selection: Type 1 for relative pos size and fixed risk. Type 2 for fixed % of Equity. Type 3 for fixed % of initial capital.
        PositionIn := math.min(Equity, PosType1 ? math.min(Equity * PosType1Cap / 100, Equity * PosType1Risk / 100 / EntryXPct) : PosType2 ? Equity * PosType2Pct / 100 : PosType3Pct / 100)
        // % Fees are calculated on the PositionIn size. Fixed fees are in equ. Either way they end up being expressed in equ and will only later be deducted from trade's PL and Equity.
        FeesPaidIn := FeesType2 ? PositionIn * FeesType2InPct / 100 : 0.0
        // X (equ).
        EntryXEqu := PositionIn * EntryXPct
        // Convert slippage to equ units for display during trade.
        SlippageEqu := SlipPaidIn / EntryOrder * PositionIn

        ExitOrder := 0.0
        ExitFill := 0.0
        TakeProfitLevel := na
        TradePLX := 0.0
        // TradePLPct := 0.0
        TradePLEqu := 0.0
        // TradePLXMax := 0.0
        // TradePLXMin := 0.0
        SlipPaidOut := 0.0
        FeesPaidOut := 0.0
        TradeMaxDrawdown := 0.0

        // Going to use this temp value to watch for a new maximum equity drawdown during the trade,
        // leaving the real Equity unchanged until the end of the trade.
        InTradeStartEquity := Equity
        InTradeEquity := Equity
        InTradeEquity
    else
        // ———————————————————— B. Process pyramided entries.
        if PyramidEntry1stBar
            // Count of pyramids in current trade.
            PyrEntries := PyrEntries + 1
            // Cumulative count of TLs for all pyramided entries. Adds 1 per bar per active entry.
            PyrTradeLengthsTot := PyrTradeLengthsTot + PyrEntries
            PyrTradeLengthsAvg := PyrTradeLengthsTot / PyrEntries
            // Suppose market entry order is given at open.
            PyrEntryOrder := Ropen
            // Add slippage to entry.
            PyrSlipPaidIn := SlippageType2 ? math.round_to_mintick(PyrEntryOrder * SlippageType2InPct / 100) : SlippageType3 ? math.round_to_mintick(SlippageType3InVal * syminfo.mintick) : 0.0
            // Entry fill price including slippage.
            PyrEntryFill := math.max(0.0, PyrEntryOrder + (InLong ? PyrSlipPaidIn : -PyrSlipPaidIn))
            // Cap fill to hi/lo.
            PyrEntryFill := InLong ? math.min(Rhigh, PyrEntryFill) : math.max(Rlow, PyrEntryFill)
            // Update X. Need to use previous bar's in-trade stop as current one isn't calculated yet.
            PyrEntryX := math.abs(PyrEntryFill - InTradeStop[1])
            // Update X%. 
            PyrEntryXPct := PyrEntryX / PyrEntryFill
            // PositionIn size is determined according to user selection: Type 1 for relative pos size and fixed risk. Type 2 for fixed % of Equity. Type 3 for fixed % of initial capital.
            PyrPositionIn := PyramidingPosMult * math.min(Equity, PosType1 ? math.min(Equity * PosType1Cap / 100, Equity * PosType1Risk / 100 / EntryXPct) : PosType2 ? Equity * PosType2Pct / 100 : PosType3Pct / 100)
            // Also keep position size in units of asset.
            PyrPositionInQty = PyrPositionIn * _initialCapital / PyrEntryFill
            // % Fees are calculated on the PositionIn size. Fixed fees are in equ. Either way they end up being expressed in equ and will only later be deducted from trade's PL and Equity.
            PyrFeesPaidIn := FeesType2 ? PyrPositionIn * FeesType2InPct / 100 : 0.0
            // X (equ).
            // PyrEntryXEqu := PyrPositionIn*PyrEntryXPct
            // Convert slippage in equ.
            PyrSlipPaidEqu := PyrSlipPaidIn / PyrEntryOrder * PyrPositionIn

            // ————— Build totals and avgs for all pyramided entries in current trade.
            // X (quote)
            PyrEntryXTot := PyrEntryXTot + PyrEntryX
            PyrEntryXAvg := PyrEntryXTot / PyrEntries
            // X %
            PyrEntryXPctTot := PyrEntryXPctTot + PyrEntryXPct
            PyrEntryXPctAvg := PyrEntryXPctTot / PyrEntries
            // Entry positions.
            PyrPositionInTot := PyrPositionInTot + PyrPositionIn
            PyrPositionInAvg := PyrPositionInTot / PyrEntries
            PyrPositionInQtyTot := PyrPositionInQtyTot + PyrPositionInQty
            // Average fill.
            PyrEntryFillTot := PyrEntryFillTot + PyrEntryFill
            PyrEntryFillAvg := PyrEntryFillTot / PyrEntries
            // 1st entry's TL at pyr entry point. Use +1 because it hasn't been incremented yet!
            PyrEntryBarTot := PyrEntryBarTot + TradeLength + 1
            PyrEntryBarAvg := PyrEntryBarTot / PyrEntries
            // Fees & Slippage.
            PyrFeesEquTot := PyrFeesEquTot + PyrFeesPaidIn
            PyrSlipEquTot := PyrSlipEquTot + PyrSlipPaidEqu
            // Save last entry price to then calculate subsequent pyramiding rules.
            LastEntryPrice := PyrEntryFill
            LastEntryPrice


        // ———————————————————— C. Update in-trade info. Also update some numbers for display purposes.
        if InTrade
            // ————— Update essential in-trade info.
            // Increase trade length.
            TradeLength := TradeLength + 1
            // Update Max point reached during trade (used both for trailing stop reference point and in-trade max drawdown calcs).
            MaxReached := InLong ? math.max(nz(MaxReached[1], Rhigh), Rhigh) : InShort ? math.min(nz(MaxReached[1], Rlow), Rlow) : na

            // ————— Update in-trade max drawdown info (has nothing to do with max drawdown calcs later in this section).
            // Since we only use the Min point to calculate in-trade max drawdown, reset it when a new max is found.
            MinReached := ta.change(MaxReached) ? MaxReached : InLong ? math.min(nz(MinReached[1], Rlow), Rlow) : InShort ? math.max(nz(MinReached[1], Rhigh), Rhigh) : na
            // Record current drawdown to build history of all drawdowns during trade.
            TradeDrawdown := math.min(0.0, (MaxReached - MinReached) * (InLong[1] ? -1 : 1) / MaxReached)
            // Find largest drawdown during trade.
            TradeMaxDrawdown := math.min(TradeMaxDrawdown, TradeDrawdown)

            // ————— Simulate a complete exit from close here to display provisional trade info.
            // In this "display only mode" during trade, fees paid in or out are ignored. They will only be included in results at real exit.
            ExitOrder := StoppedCondition[1] ? EntryStop : Ropen
            // Calculate tentative slippage from Exit order level.
            SlipPaidOut := SlippageType2 ? math.round_to_mintick(ExitOrder * SlippageType2OutPct / 100) : SlippageType3 ? math.round_to_mintick(SlippageType3OutVal * syminfo.mintick) : 0.0
            // Calculate Exit Fill including slipppage, protecting against negative values.
            ExitFill := math.max(0.0, ExitOrder + (InLong[1] ? -SlipPaidOut : SlipPaidOut))
            // Fill cannot be outside hi/lo.
            ExitFill := InLong[1] ? math.max(Rlow, ExitFill) : math.min(Rhigh, ExitFill)
            // Calculate slippage that actually occured.
            SlipPaidOut := math.abs(ExitFill - ExitOrder)
            // Net P&L including slippage and fees.
            // TradePLX := (ExitFill-EntryFill)*(InLong[1]?1:-1)/entryStopGap
            TradePLX := (ExitFill - EntryFill) * (InLong[1] ? 1 : -1)

            // Highest/LOwest PL (mult of X) reached during trade.
            // TradePLXMax := max(TradePLX, TradePLXMax[1])
            // TradePLXMin := min(TradePLX, TradePLXMin[1])
            // Trade P&L in %.
            // TradePLPct := TradePLX*EntryXPct


            // Trade P&L in equity units.
            // TradePLEqu := PositionIn*TradePLX*EntryXPct
            TradePLEqu := PositionIn * TradePLX / EntryFill


            // Add initial position size to P&L to get exit position before fees.
            PositionOut := TradePLEqu + PositionIn

            // Convert slippage into equity units.
            SlippageEqu := SlipPaidIn / EntryOrder * PositionIn + SlipPaidOut / ExitOrder * PositionOut
            // Calculate a pseudo equity value given current trade result.
            InTradeEquity := InTradeStartEquity + PositionOut - PositionIn

            // ————— Process active pyramided entries info.
            if PyrEntries > 0
                // ————— Update active pyramided entries informations.
                // Add 1 in length for each pyr entry, but don't add if pyr entry made on this bar, as total already updated.
                PyrTradeLengthsTot := PyrTradeLengthsTot + (PyramidEntry1stBar ? 0 : PyrEntries)
                PyrTradeLengthsAvg := PyrTradeLengthsTot / PyrEntries
                // Update following numbers for display purposes only, without worrying about fees.
                PyrPositionOut := PyrPositionInQtyTot * ExitFill / _initialCapital
                PyrTradePLEqu := (PyrPositionOut - PyrPositionInTot) * (InLong[1] ? 1 : -1)
                PyrTradePLPct := PyrTradePLEqu / PyrPositionInTot
                PyrTradePLX := PyrTradePLPct / PyrEntryXPctAvg
                PyrTradePLX

    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // ———————————————————————————————————————— 7. Pyramiding Rules ———————————————————————————————————————————————————————————
    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // Here we evaluate the rules determining if pyramiding is allowed.
    // ————— Pyramiding 1: Once after X multiple.
    Pyramiding1Long = PyramidingType1 and Rclose > LastEntryPrice + entryStopGap * Pyramiding1MultX
    Pyramiding1Short = PyramidingType1 and Rclose < LastEntryPrice - entryStopGap * Pyramiding1MultX
    // ————— Pyramiding 2: Every % increment.
    Pyramiding2Long = PyramidingType2 and Rclose > LastEntryPrice * (1 + Pyramiding2Pct / 100)
    Pyramiding2Short = PyramidingType2 and Rclose < LastEntryPrice * (1 - Pyramiding2Pct / 100)
    // ————— Pyramiding 3: Every price increment.
    Pyramiding3Long = PyramidingType3 and Rclose > LastEntryPrice + Pyramiding3Val
    Pyramiding3Short = PyramidingType3 and Rclose < LastEntryPrice - Pyramiding3Val
    // ————— Pyramiding 4: Entry number=previous entry number.
    Pyramiding4Long = PyramidingType4 and EntryNumber == LastEntryNumber
    Pyramiding4Short = Pyramiding4Long
    // ————— Pyramiding 5: Entry number<>previous entry number.
    Pyramiding5Long = PyramidingType5 and EntryNumber != LastEntryNumber
    Pyramiding5Short = Pyramiding5Long
    // ————— Pyramiding 6: All opportunities.
    Pyramiding6Long = PyramidingType6
    Pyramiding6Short = PyramidingType6

    // ———————————————————— Assemble Pyramiding rules.
    // In addition to the above rules allowing a pyramided entry, we need to be in a trade and the number of max pyramided entries must not have been reached.
    PyramidLongOK = InLong[1] and InLong and PyrEntries < PyramidingMaxCnt and (PyramidingFilterNeeded ? FilterLongOK : true) and (Pyramiding1Long or Pyramiding2Long or Pyramiding3Long or Pyramiding4Long or Pyramiding5Long or Pyramiding6Long)
    PyramidShortOK = InShort[1] and InShort and PyrEntries < PyramidingMaxCnt and (PyramidingFilterNeeded ? FilterShortOK : true) and (Pyramiding1Short or Pyramiding2Short or Pyramiding3Short or Pyramiding4Short or Pyramiding5Short or Pyramiding6Short)



    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // ——————————————————————————————————————————— 8. In-trade stops ——————————————————————————————————————————————————————————
    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // We work in 2 steps: first we calculate the stop for each strat,
    // then we evaluate the kick in conditions that will determine if the stop we have calculated will be used.
    // In all cases a new stop will only replace a previous one if it closer to price, so this logic does not
    // currently allow implementing stop strats where the stop moves against price. They won't cause errors; the
    // stop will just never go further away from price than the previous one.

    // ———————————————————— In-trade stops
    // ————— In-Trade Stop 1: Trailing stop using multiple of X% as max deviation
    InTradeStop1Long = MaxReached - entryStopGap * InTradeStopType1Mult
    InTradeStop1Short = MaxReached + entryStopGap * InTradeStopType1Mult
    // ————— In-Trade Stop 2: Trailing stop using fixed percentage as max deviation
    InTradeStop2Long = MaxReached * (1 - InTradeStopType2Pct / 100)
    InTradeStop2Short = MaxReached * (1 + InTradeStopType2Pct / 100)
    // ————— In-Trade Stop 3: Trailing stop using fixed value as max deviation
    InTradeStop3Long = MaxReached - InTradeStopType3Val
    InTradeStop3Short = MaxReached + InTradeStopType3Val

    // ————— In-Trade Stop 4: Move to Break Even. 
    InTradeStop4Long = EntryFill
    InTradeStop4Short = EntryFill


    // ————— In-Trade Stop 5: ATR stop
    // Calculated using average of 2 preceeding MinOC/MaxOC and ATR gap.
    InTradeStop5Atr = InTradeStopType5 ? ta.atr(InTradeStopType567Len) : na
    InTradeStop5Long = InTradeStopType5 ? math.avg(MinOC[1], MinOC[2]) - InTradeStop5Atr * InTradeStopType567Mult : na
    InTradeStop5Short = InTradeStopType5 ? math.avg(MaxOC[1], MaxOC[2]) + InTradeStop5Atr * InTradeStopType567Mult : na

    // ————— In-Trade Stop 8: Last Lo/Hi
    InTradeStop8Long = low[1]
    InTradeStop8Short = high[1]

    // ————— Select potential new stop.
    InTradeStopLong_ = 0.0
    InTradeStopShort_ = 0.0
    InTradeStopLong_ := InTradeStopType1 ? InTradeStop1Long : InTradeStopType2 ? InTradeStop2Long : InTradeStopType3 ? InTradeStop3Long : InTradeStopType4 ? InTradeStop4Long : InTradeStopType5 ? InTradeStop5Long : InTradeStopType8 ? InTradeStop8Long : 0.0
    InTradeStopShort_ := InTradeStopType1 ? InTradeStop1Short : InTradeStopType2 ? InTradeStop2Short : InTradeStopType3 ? InTradeStop3Short : InTradeStopType4 ? InTradeStop4Short : InTradeStopType5 ? InTradeStop5Short : InTradeStopType8 ? InTradeStop8Short : 0.0

    // Make sure stop is not on wrong side of the trade.
    InTradeStopLong_ := InLong[1] and InTradeStopLong_ > Rhigh ? nz(InTradeStop[1]) : math.round_to_mintick(InTradeStopLong_)
    InTradeStopShort_ := InShort[1] and InTradeStopShort_ < Rlow ? nz(InTradeStop[1]) : math.round_to_mintick(InTradeStopShort_)


    // —————————— Kick In Conditions
    PreviousStop = nz(InTradeStop[1])

    // Determine if we can move stop or leave the entry stop behind and start using the in-trade stop.
    // ————— Kisk In 1: When the stop passes the entry stop.
    InTradeKick1Long = InTradeStopLong_ > EntryStop
    InTradeKick1Short = InTradeStopShort_ < EntryStop

    // ————— Kisk In 2: When the stop passes Entry +/- X Multiple.
    InTradeKick2Long_ = EntryFill + entryStopGap * InTradeKickType2Mult
    InTradeKick2Short_ = EntryFill - entryStopGap * InTradeKickType2Mult
    InTradeKick2Long = InTradeStopLong_ > math.max(math.round_to_mintick(InTradeKick2Long_), EntryStop)
    InTradeKick2Short = InTradeStopShort_ < math.min(math.round_to_mintick(InTradeKick2Short_), EntryStop)

    // ————— Kisk In 3: When the stop passes Entry +/- %.
    InTradeKick3Long_ = EntryFill * (1.0 + InTradeKickType3Pct / 100)
    InTradeKick3Short_ = EntryFill * (1.0 - InTradeKickType3Pct / 100)
    InTradeKick3Long = InTradeStopLong_ > math.max(math.round_to_mintick(InTradeKick3Long_), EntryStop)
    InTradeKick3Short = InTradeStopShort_ < math.min(math.round_to_mintick(InTradeKick3Short_), EntryStop)

    // ————— Kisk In 4: When the stop passes Entry +/- Value.
    InTradeKick4Long_ = EntryFill + InTradeKickType4Val
    InTradeKick4Short_ = EntryFill - InTradeKickType4Val
    InTradeKick4Long = InTradeStopLong_ > math.max(math.round_to_mintick(InTradeKick4Long_), EntryStop)
    InTradeKick4Short = InTradeStopShort_ < math.min(math.round_to_mintick(InTradeKick4Short_), EntryStop)

    // ————— Kisk In 5: When the price passes Entry +/- %
    InTradeKick5Long_ = EntryFill + InTradeKickType5Pct * EntryFill
    InTradeKick5Short_ = EntryFill - InTradeKickType5Pct * EntryFill
    InTradeKick5Long = MaxReached > math.max(math.round_to_mintick(InTradeKick5Long_), EntryStop)
    InTradeKick5Short = MaxReached < math.min(math.round_to_mintick(InTradeKick5Short_), EntryStop)


    // ————— Select Kick In
    InTradeKickLong = InTradeKickType1 ? InTradeKick1Long : InTradeKickType2 ? InTradeKick2Long : InTradeKickType3 ? InTradeKick3Long : InTradeKickType4 ? InTradeKick4Long : InTradeKickType5 ? InTradeKick5Long : false
    InTradeKickShort = InTradeKickType1 ? InTradeKick1Short : InTradeKickType2 ? InTradeKick2Short : InTradeKickType3 ? InTradeKick3Short : InTradeKickType4 ? InTradeKick4Short : InTradeKickType5 ? InTradeKick5Short : false

    // ———————————————————— Assemble In-Trade stop.
    // Our potential new stop value is determined. Only use it as new one if it moved in trade direction compared to previous one.
    // ————— Set In-trade stop for check on next bar.
    // Exceptionally, on entry bar of a trade, init to EntryStop.
    InTradeStop_ = FirstEntry1stBar ? EntryStop : InLong ? InTradeKickLong ? math.max(InTradeStopLong_, nz(InTradeStop[1])) : nz(InTradeStop[1]) : InTradeKickShort ? math.min(InTradeStopShort_, nz(InTradeStop[1])) : nz(InTradeStop[1])
    InTradeStop := math.max(0.0, InTradeStop_)



    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // ———————————————————————————————————————————————— 9. Exits ——————————————————————————————————————————————————————————————
    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    //

    // ----- Exit 0: Opposite Signal
    Exit0Long   = ExitType0 and EntryNumber < 0
    Exit0Short  = ExitType0 and EntryNumber > 0


    // Exit 1: SROC Falling / Rising X 
    sroc        = ta.ema(f_roc(), Exit1Smoothing)
    srocFalling = ta.falling(sroc, Exit1Fall)
    srocRising  = ta.rising(sroc, Exit1Rise)
    Exit1Long   = ExitType1 and srocRising[1] and not srocRising
    Exit1Short  = ExitType1 and srocFalling[1] and not srocFalling


    // ————— Exit 4: RSI Crosses
    Exit4Rsi    = ta.rsi(close, ExitType4Len)
    Exit4Long   = ExitType4 and ta.crossunder(Exit4Rsi, ExitType4LongLvl)
    Exit4Short  = ExitType4 and ta.crossover(Exit4Rsi, ExitType4ShortLvl)

    // ————— Exit 6: When the stop passes Entry +/- X Multiple.
    Exit6Long_           = EntryFill + entryStopGap * ExitType6Mult
    Exit6Short_          = EntryFill - entryStopGap * ExitType6Mult
    Exit6TakeProfitLevel = InLong ? math.round_to_mintick(Exit6Long_) : InShort ? math.round_to_mintick(Exit6Short_) : 0.0
    Exit6Long            = ExitType6 and math.round_to_mintick(close) > Exit6TakeProfitLevel
    Exit6Short           = ExitType6 and math.round_to_mintick(close) < Exit6TakeProfitLevel

    // ————— Exit 7: When % gain is x amount (change)
    Exit7Long_           = EntryFill + ExitType7Pct * EntryFill
    Exit7Short_          = EntryFill - ExitType7Pct * EntryFill
    Exit7TakeProfitLevel = InLong ? math.round_to_mintick(Exit7Long_) : InShort ? math.round_to_mintick(Exit7Short_) : 0.0
    Exit7Long            = ExitType7 and math.round_to_mintick(close) > Exit7TakeProfitLevel  // Use close here because hard exits are calculated on close.
    Exit7Short           = ExitType7 and math.round_to_mintick(close) < Exit7TakeProfitLevel  // So it's really a true TP Level.

    // ————— Exit 8: When % gain is x amount (equity)
    Exit8LongShort = ExitType8 and InTradeEquity >= Equity + Equity * ExitType8Pct


    // ———————————————————— Assemble exits
    ExitLong  = Exit0Long  or Exit1Long  or Exit4Long  or Exit6Long  or Exit7Long  or Exit8LongShort
    ExitShort = Exit0Short or Exit1Short or Exit4Short or Exit6Short or Exit7Short or Exit8LongShort

    // ———————————————————— Assemble reversals
    reverse0Exit = i_rvrsePositionExit0 and ExitType0
    reverse4Exit = i_rvrsePositionExit4 and ExitType4

    reversePosition = reverse0Exit or reverse4Exit



    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // ——————————————————————————————————————— 10. Trade Entry/Exit Trigger Detection —————————————————————————————————————————
    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    //  Detect Exit and then Entry trigger conditions for execution on next bar.

    // We start with exit triggers because if one is generated, we don't want to enter on the same bar.
    // ————— Exit triggers
    // Make an exception on Entry bar; since no previous trade stop existed, test current one.
    StoppedCondition := not InTradeStopDisabled and (InLong and Rlow < InTradeStop[LongEntryTrigger[1] ? 0 : 1] or InShort and Rhigh > InTradeStop[ShortEntryTrigger[1] ? 0 : 1]) or InLong and Rlow <= EntryStop or InShort and Rhigh >= EntryStop
    ExitTradeCondition = InLong and ExitLong or InShort and ExitShort
    ExitCondition := StoppedCondition or ExitTradeCondition
    LongExitTrigger = InLong and ExitCondition
    ShortExitTrigger = InShort and ExitCondition


    // ————— Entry triggers
    // If a signal has triggered, this is its number (+ for long, - for short)
    LongEntry = EntryNumber > 0
    ShortEntry = EntryNumber < 0
    // Identify Pyramiding entries so they can be treated distincly.
    PyramidLongEntry = LongEntry and PyramidLongOK and not ExitCondition
    PyramidShortEntry = ShortEntry and PyramidShortOK and not ExitCondition
    // Confirm entries with filter and pyramiding.
    LongEntryConfirmed = LongEntry and FilterLongOK and not InTrade or PyramidLongEntry and PyramidingOn
    ShortEntryConfirmed = not LongEntryConfirmed and (ShortEntry and FilterShortOK and not InTrade or PyramidShortEntry and PyramidingOn)
    // Determine if final conditions allow an entry trigger. The actual entry happens at the next bar.
    // True only during the bar where an entry triggers.
    // The 6 conditions are:
    //      1. Trade direction allowed,
    //      2. Confirmed signal,
    //      3. Date filtering allows trade,
    //      4. Enough bars have elapsed from beginning of dataset to have an ATR for entry stop,
    //      5. Ruin has not occurred,
    //      6. Circuit Breaker has not Triggered,
    LongEntryTrigger := GenerateLongs and LongEntryConfirmed and TradeDateIsAllowed() and bar_index > EntryStopType2Len and not Ruin and not CircuitBreaker
    ShortEntryTrigger := GenerateShorts and ShortEntryConfirmed and TradeDateIsAllowed() and bar_index > EntryStopType2Len and not Ruin and not CircuitBreaker


    // ————— Assembly of states for ease in logical testing.
    EntryTrigger := LongEntryTrigger or ShortEntryTrigger
    ExitTrigger := LongExitTrigger or ShortExitTrigger and TradeDateIsAllowed()
    PyramidEntry := PyramidLongEntry or PyramidShortEntry and TradeDateIsAllowed()



    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // ————————————————————————————————————————— 11. Trade Exit Processing ————————————————————————————————————————————————————
    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // On exit, all currently open positions (1st entry + pyramiding) are closed.
    // Some of the currently valid trade data is needed in further modules,
    // so we don't reset everything just yet. We will finish cleanup in next to last module before leaving the current bar.
    if ExitTrigger[1]
        // ——————————————— Trade state and numbers.
        // As for entry, everything starts from the open of the bar following trigger, unless we were stopped out
        ExitOrder := StoppedCondition[1] ? InTradeStop[1] : Ropen
        // Calculate tentative slippage from Exit order level.
        SlipPaidOut := SlippageType2 ? math.round_to_mintick(ExitOrder * SlippageType2OutPct / 100) : SlippageType3 ? math.round_to_mintick(SlippageType3OutVal * syminfo.mintick) : 0.0
        // Calculate Exit Fill including slipppage, protecting against negative values.
        ExitFill := math.max(0.0, ExitOrder + (InLong[1] ? -SlipPaidOut : SlipPaidOut))
        // Fill cannot be outside hi/lo.
        ExitFill := InLong[1] ? math.max(Rlow[StoppedCondition[1] ? 1 : 0], ExitFill) : math.min(Rhigh[StoppedCondition[1] ? 1 : 0], ExitFill)
        // Calculate slippage that actually occured (because it can be limited by bar's hi/lo).
        SlipPaidOut := math.abs(ExitFill - ExitOrder)
        // Net P&L after slippage.
        // TradePLX := (ExitFill-EntryFill)*(InLong[1]?1:-1)/entryStopGap
        TradePLX := (ExitFill - EntryFill) * (InLong[1] ? 1 : -1)

        // Highest/Lowest PL (mult of X) reached during trade.
        // TradePLXMax := max(TradePLX, TradePLXMax[1])
        // TradePLXMin := min(TradePLX, TradePLXMin[1])
        // Trade P&L in %.
        // TradePLPct := TradePLX*EntryXPct
        // Trade P&L in equity units.
        // TradePLEqu := PositionIn*TradePLX*EntryXPct
        TradePLEqu := PositionIn * TradePLX / EntryFill

        // Add initial position size to P&L to get exit position before fees.
        PositionOut := TradePLEqu + PositionIn
        // % fees are calculated on exit's position size, so taking into account P&L after slippage.
        FeesPaidOut := math.abs(FeesType2 ? PositionOut * FeesType2OutPct / 100 : 0.0)
        // ————— From here we can integrate the impact of fees in our calcs.
        // Subtract fees from previously calculated P&L (equ).
        TradePLEqu := TradePLEqu - FeesPaidIn - FeesPaidOut

        // Recalculate PLX taking fees into account.
        // TradePLX := TradePLEqu/EntryXEqu
        // label.new(bar_index, high, text=tostring(TradePLX))
        TradePLX := TradePLEqu * EntryFill / PositionIn
        // label.new(bar_index, low, text=tostring(TradePLX))

        // Recalculate PL% taking fees into account.
        // TradePLPct := TradePLX*EntryXPct
        // Convert slippage in equ.
        SlippageEqu := SlipPaidIn / EntryOrder * PositionIn + SlipPaidOut / ExitOrder * PositionOut
        // Max Drawdown (from high to low) during trade.
        TradeMaxDrawdown := math.min(0.0, (MaxReached[1] - MinReached[1]) * (InLong[1] ? -1 : 1) / MaxReached[1])

        // ——————————————— Global 1st entry trade info.
        First_Entries := First_Entries + 1
        // Win/Lose state.
        WinningTrade = TradePLX >= 0.0
        LosingTrade = not WinningTrade
        // P&L average=Expectancy=APPT in X.
        First_PLXTot := First_PLXTot + TradePLX
        First_PLXAvg := First_PLXTot / First_Entries
        // X values: update avgs.
        First_XTot := First_XTot + entryStopGap
        First_XAvg := First_XTot / First_Entries
        First_XPctTot := First_XPctTot + EntryXPct
        First_XPctAvg := First_XPctTot / First_Entries
        First_XEquTot := First_XEquTot + PositionIn * EntryXPct
        First_XEquAvg := First_XEquTot / First_Entries
        // Winning/Losing trade total X.
        // First_WinsX := First_WinsX + (WinningTrade? TradePLX:0.0)
        // First_LoseX := First_LoseX + (LosingTrade? TradePLX:0.0)
        First_WinsEqu := First_WinsEqu + (WinningTrade ? PositionOut - PositionIn : 0.0)
        First_LoseEqu := First_LoseEqu + (LosingTrade ? PositionOut - PositionIn : 0.0)
        // label.new(bar_index, high, text=tostring(First_WinsX/_initialCapital))
        // label.new(bar_index, low, text=tostring(First_WinsEqu))
        // Winning/Losing trade totals.
        First_Winning := First_Winning + OneZero(WinningTrade)
        First_Losing := First_Losing + OneZero(LosingTrade)
        // Other global trade data.
        First_TradeLengthsTot := First_TradeLengthsTot + TradeLength
        First_TradeLengthsAvg := First_TradeLengthsTot / First_Entries
        First_WinsTL := First_WinsTL + (WinningTrade ? TradeLength : 0)
        First_LoseTL := First_LoseTL + (LosingTrade ? TradeLength : 0)
        // Entry slippage and fees were added on entry; only add exit ones here.
        First_FeesEqu := First_FeesEqu + FeesPaidIn + FeesPaidOut
        First_SlipEqu := First_SlipEqu + SlippageEqu
        First_PositionInTot := First_PositionInTot + PositionIn
        First_VolumeTraded := First_VolumeTraded + PositionIn + PositionOut
        // Profit factor: gross wins / gross losses
        First_ProfitFactor := First_LoseEqu == 0.0 ? 0.0 : math.abs(nz(First_WinsEqu / First_LoseEqu))

        // If we have active pyramid entries, process them. 
        if PyrEntries > 0
            // For Pyramided positions, instead of starting from the usual TradePLX, we will be working from the total of number
            // of assets bought/sold at each successive entry, then calculating the delta between the currency value of the entries
            // and the exit. PLX and PLPct will then be derived from there.

            // Exit position before fees.
            PyrPositionOut := PyrPositionInQtyTot * ExitFill / _initialCapital
            // Exit fees.
            PyrFeesPaidOut := math.abs(FeesType2 ? PyrPositionOut * FeesType2OutPct / 100 : 0.0)
            // Add exit fees to in fees (equ).
            PyrFeesEquTot := PyrFeesEquTot + PyrFeesPaidOut
            // Add slippage out (equ) (slippage is already in PyrSlipEquTot).
            PyrSlipEquTot := PyrSlipEquTot + SlipPaidOut / ExitOrder * PyrPositionOut
            // PL (equ) including Fees & Slippage.
            PyrTradePLEqu := (PyrPositionOut - PyrPositionInTot) * (InLong[1] ? 1 : -1) - PyrFeesEquTot
            // ————— From here we can integrate the impact of fees in PL.
            // PL% taking fees into account.
            PyrTradePLPct := PyrTradePLEqu / PyrPositionInTot
            // PLX taking fees into account.
            PyrTradePLX := PyrTradePLPct / PyrEntryXPctAvg

            // ——————————————— Global trade info.
            // Total count of pyr entries (kept separate from First_Entries count).
            Pyr_Entries := Pyr_Entries + PyrEntries
            // Win/Lose state.
            PyrWinningTrade = PyrTradePLX >= 0.0
            PyrLosingTrade = not PyrWinningTrade
            // Winning/Losing trade total (equ).
            Pyr_WinsEqu := Pyr_WinsEqu + (PyrWinningTrade ? PyrTradePLEqu : 0.0)
            Pyr_LoseEqu := Pyr_LoseEqu + (PyrLosingTrade ? PyrTradePLEqu : 0.0)
            // Winning/Losing trade totals.
            Pyr_Winning := Pyr_Winning + OneZero(PyrWinningTrade) * PyrEntries
            Pyr_Losing := Pyr_Losing + OneZero(PyrLosingTrade) * PyrEntries
            // P&L average per pyr entry.
            Pyr_PLXTot := Pyr_PLXTot + PyrTradePLX * PyrEntries
            Pyr_PLXAvg := Pyr_PLXTot / Pyr_Entries
            // Other global trade data.
            // Total volume traded.
            Pyr_VolumeTraded := Pyr_VolumeTraded + PyrPositionOut + PyrPositionInTot
            // Pyr Profit factor.
            Pyr_ProfitFactor := Pyr_LoseEqu == 0.0 ? 0.0 : math.abs(nz(Pyr_WinsEqu / Pyr_LoseEqu))
            Pyr_ProfitFactor

        // ——————————————— Global combined (1st entries and pyramiding) numbers.
        All_Entries := All_Entries + 1 + PyrEntries
        // Win/Lose state.
        WinningTrade := (TradePLX + PyrTradePLX * PyrEntries) / (PyrEntries + 1) >= 0.0
        LosingTrade := not WinningTrade
        // P&L average=Expectancy=APPT in X.
        All_PLXTot := All_PLXTot + TradePLX + PyrTradePLX * PyrEntries
        All_PLXAvg := All_PLXTot / All_Entries
        // X values: update avgs.
        All_XTot := All_XTot + entryStopGap + PyrEntryXAvg * PyrEntries
        All_XAvg := All_XTot / All_Entries
        All_XPctTot := All_XPctTot + EntryXPct + PyrEntryXPctAvg * PyrEntries
        All_XPctAvg := All_XPctTot / All_Entries
        All_XEquTot := All_XEquTot + PositionIn * EntryXPct + PyrPositionInTot * PyrEntryXPctAvg
        All_XEquAvg := All_XEquTot / All_Entries
        // Winning/Losing trade total X.
        All_WinsX := All_WinsX + (WinningTrade ? TradePLX + PyrTradePLX * PyrEntries : 0.0)
        All_LoseX := All_LoseX + (LosingTrade ? TradePLX + PyrTradePLX * PyrEntries : 0.0)

        All_WinsEqu := All_WinsEqu + (WinningTrade ? PositionOut - PositionIn + PyrTradePLEqu : 0.0)
        All_LoseEqu := All_LoseEqu + (LosingTrade ? PositionOut - PositionIn + PyrTradePLEqu : 0.0)
        // Winning/Losing trade totals.
        All_Winning := All_Winning + OneZero(WinningTrade) + OneZero(WinningTrade) * PyrEntries
        All_Losing := All_Losing + OneZero(LosingTrade) + OneZero(LosingTrade) * PyrEntries
        // Other global trade data.
        All_TradeLengthsTot := All_TradeLengthsTot + TradeLength + PyrTradeLengthsAvg * PyrEntries
        All_TradeLengthsAvg := All_TradeLengthsTot / All_Entries
        All_WinsTL := All_WinsTL + (WinningTrade ? TradeLength + PyrTradeLengthsAvg * PyrEntries : 0)
        All_LoseTL := All_LoseTL + (LosingTrade ? TradeLength + PyrTradeLengthsAvg * PyrEntries : 0)
        // Entry slippage and fees were added on entry; only add exit ones here.
        All_FeesEqu := All_FeesEqu + FeesPaidIn + FeesPaidOut + PyrFeesEquTot
        All_SlipEqu := All_SlipEqu + SlippageEqu + PyrSlipEquTot
        All_PositionInTot := All_PositionInTot + PositionIn + PyrPositionInTot
        All_VolumeTraded := All_VolumeTraded + PositionIn + PositionOut + PyrPositionOut + PyrPositionInTot
        // Profit factor: gross wins / gross losses
        All_ProfitFactor := All_LoseEqu == 0.0 ? 0.0 : math.abs(nz(All_WinsEqu / All_LoseEqu))

        // ——————————————— Equity update.
        // First entry and pyramiding (if any) exited; finish up a few numbers.
        // Equity update with PL only (PositionIn never left equity) and subtracting fees.
        Equity := Equity + PositionOut - PositionIn - FeesPaidIn - FeesPaidOut + PyrTradePLEqu
        // Return on Capital.
        ReturnOnEquity := Equity - 1.0
        // Detect if capital is depleted. If so, new trades will no longer be opened.
        Ruin := Equity <= 0
        // Last update of in-trade equity before next trade.
        InTradeEquity := Equity
        InTradeEquity




    //{ this only reverses positions on exits.
    if ExitTrigger[1] and not StoppedCondition[1] and reversePosition and TradeDateIsAllowed()
        LongEntryTrigger := not InLong[1]
        ShortEntryTrigger := InLong[1]
        EntryTrigger := true
        EntryTrigger
    //}


    // ———————————————————— A.1. Global numbers

    // ————— A.1.1 Global combined (1st entries + pyramiding)
    // APPTCur11 = nz((First_Winning/First_Entries*First_WinsEqu*_initialCapital/First_Winning)-(-First_Losing/First_Entries*First_LoseEqu*_initialCapital/First_Losing))
    EquityCu = Equity * _initialCapital  //                                             "ALL: Equity (curr)", "", color=AAA)                                    // ————— Show



    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // ———————————————————————————————————————————— 16. Variable resets ———————————————————————————————————————————————————————
    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // ———————————————————— Last cleanup of exits.
    // Now that we have used and plotted all Exit information, we can do final cleanup before next bar.
    // Some of these values are reset a second time at the beginning of cycles;
    // they are nonetheless reset here for cleaner between trade display purposes.
    if ExitTrigger[1]
        EntryFill := 0.0
        EntryOrder := 0.0
        entryStopGap := 0.0
        EntryXPct := 0.0
        EntryXEqu := 0.0
        ExitOrder := 0.0
        ExitFill := 0.0
        FeesPaidIn := 0.0
        FeesPaidOut := 0.0
        InTradeEquity := Equity
        InTradeStartEquity := Equity
        LastEntryPrice := 0
        PositionIn := 0.0
        PositionOut := 0.0
        SlippageEqu := 0.0
        SlipPaidIn := 0.0
        SlipPaidOut := 0.0
        TakeProfitLevel := 0.0
        TradeLength := 0
        TradeMaxDrawdown := 0.0

        PyrEntries := 0
        PyrEntryBarTot := 0
        PyrEntryBarAvg := 0.0
        PyrEntryFill := 0.0
        PyrEntryFillTot := 0.0
        PyrEntryFillAvg := 0.0
        PyrEntryOrder := 0.0
        PyrEntryX := 0.0
        PyrEntryXAvg := 0.0
        PyrEntryXPct := 0.0
        PyrEntryXPctTot := 0.0
        PyrEntryXPctAvg := 0.0
        PyrEntryXTot := 0.0
        PyrFeesPaidIn := 0.0
        PyrFeesPaidOut := 0.0
        PyrFeesEquTot := 0.0
        PyrPositionIn := 0.0
        PyrPositionOut := 0.0
        PyrPositionInQtyTot := 0.0
        PyrPositionInTot := 0.0
        PyrSlipPaidIn := 0.0
        PyrSlipPaidOut := 0.0
        PyrSlipEquTot := 0.0
        PyrTradePLEqu := 0.0
        PyrTradePLX := 0.0
        PyrTradeLengthsTot := 0
        PyrTradeLengthsAvg := 0.0
        PyrTradeLengthsAvg




    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    // —————————————————————————————————————————— 17. Strategy() calls ————————————————————————————————————————————————————————
    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    int _signal = LongEntryTrigger ? 1 : ShortEntryTrigger ? -1 : LongExitTrigger ? 2 : ShortExitTrigger ? -2 : na


    // Calculate Drawdown Metrics
    [MaxDDPct, MaxDDPT, MaxDDDur] = f_getMaxDD(InTradeEquity, _initialCapital)
    CircuitBreaker := -MaxDDPct >= i_RiskMaxDrawdownCB  // Stops trades

    // Total Net Profit - Shows profitability and ROI
    NetProfit = EquityCu - _initialCapital

    ROI = ReturnOnEquity * 100

    // Number of Trades - Shows # of trades taken during backtesting period
    Trades = All_Winning + All_Losing

    // Number of Bars - Shows avg # of days in a trade
    ATLen = f_barsToDays(All_TradeLengthsAvg)

    // Maximum Loss - Shows largest loss experienced on a per-trade basis. Normally, don’t want to exceed more than 1-2 % of equity. 
    var float MaxLoss = 0.0
    MaxLoss := math.min(nz((Equity - Equity[1]) * _initialCapital, 0.0), MaxLoss)

    // Maximum Consecutive Losses (MCL) - The max consecutive losses endured throughout the backtesting period. Another important metric for trader psychology, this will help you understand how many losses you should be prepared to handle in order to achieve netprofit.
    MaxConsecutiveLosses = f_getMCL(Equity, Trades, All_Losing)

    // Profit to Maximum Drawdown (P:MD) - A ratio for the Net Profit to the maximum drawdown. The higher the ratio is, the better. Large profits and small losses contribute to a good PMD This metric allows us to examine the profit with respect to risk.
    PMD = NetProfit / math.abs(MaxDDPT)

    // Average Winning Trade, and Average Losing Trade = P&L total (equ) of all winning/losing trades / # Of winning/losing trades respectively.
    All_AvgWin := All_WinsEqu / All_Winning
    All_AvgLos := All_LoseEqu / All_Losing

    // Profit Loss Ratio (P:L) - Average profit over the average loss. Typically this number should be higher in trend following systems. Mean revision systems show lower values, but compensate with better win %
    PLR = math.abs(All_AvgWin / All_AvgLos)

    // Percent Winners (% W) - The percentage of winning trades. Trend systems will usually have lower win percentages, since statistically the market is only trending 30% of the time. Mean reversions systems typically should have a high % W.
    PctWinners = nz(100 * All_Winning / Trades)

    // Time Percentage (Time %) - The amount of time that the system has an open position. The more time you are in the market, the more you are exposed to the market's risk, not to mention you could be using that money for something else right? 
    TotalTradeTime = ta.cum(TradeDateIsAllowed() ? 1 : 0)
    tradingTime = ta.cum(TradeDateIsAllowed() and InTrade ? 1 : 0)
    // TimePct = round(100*(First_TradeLengthsTot+TradeLength) / TotalTradeTime, 2)
    TimePct = 100 * tradingTime / TotalTradeTime


    openProfit = TradePLEqu * _initialCapital  // Unrealized gain/loss
    _tradingDays = f_barsToDays(TotalTradeTime)  // Days the strategy ran for


    [_tradingDays, InTradeEquity, openProfit, NetProfit, ROI, Trades, ATLen, MaxDDPct, MaxDDPT, MaxLoss, MaxDDDur, MaxConsecutiveLosses, PMD, PLR, PctWinners, TimePct, _signal]




// ------ Display and Drawings
// Table Inputs
T_P1 = 'Bottom Center'
T_P2 = 'Bottom Left'
T_P3 = 'Bottom Right'
T_P4 = 'Middle Center'
T_P5 = 'Middle Left'
T_P6 = 'Middle Right'
T_P7 = 'Top Center'
T_P8 = 'Top Left'
T_P9 = 'Top Right'
i_t_position = input.string(T_P3, 'Table Position', options=[T_P1, T_P2, T_P3, T_P4, T_P5, T_P6, T_P7, T_P8, T_P9], group='Table')
t_position = i_t_position == T_P1 ? position.bottom_center : i_t_position == T_P2 ? position.bottom_left : i_t_position == T_P3 ? position.bottom_right : i_t_position == T_P4 ? position.middle_center : i_t_position == T_P5 ? position.middle_left : i_t_position == T_P6 ? position.middle_right : i_t_position == T_P7 ? position.top_center : i_t_position == T_P8 ? position.top_left : i_t_position == T_P9 ? position.top_right : na

i_t_txtSze = input.string(defval='auto', title='Table Size', options=['auto', 'tiny', 'small', 'normal', 'large', 'huge'], group='Table')
t_headerClr = color.black
t_neuColor = color.gray

t_rows = 7
t_columns = 15

var table t_portfolioResults = table.new(t_position, t_columns, t_rows, border_width=3)  // Create Global Table object



f_getColor(_val, _max, _min) =>
    // _val: Value to be converted into color
    // _max: Value that needs to be reached for Max Color
    // _min: Value that needs to be reached for Min Color
    //
    // Returns: series[color]
    color.from_gradient(_val, _min, _max, color.red, color.green)


f_fillCell(_table, _column, _row, _text, _bgcolor) =>
    // Fill the Table Cell
    t_txtHA = text.align_center
    t_txtVA = text.align_center
    t_txtClr = color.white
    table.cell(_table, _column, _row, _text, 0, 0, t_txtClr, t_txtHA, t_txtVA, i_t_txtSze, _bgcolor)


f_row(_initialCapital, _asset, _row) =>
    // Draw a Table Row
    [_tradingDays, InTradeEquity, openProfit, NetProfit, ROI, NumTrades, AvgTradeLen, MaxDDPct, MaxDDPT, MaxLoss, MaxDDDur, MaxConsecutiveLosses, PMD, PLR, PctWinners, TimePct, Signal] = request.security(_asset, timeframe.period, f_getData(_initialCapital))
    
    if _asset == syminfo.tickerid
        if Signal == 1
            label.new(bar_index, low, "Long Entry", yloc=yloc.belowbar, style=label.style_label_up, color=color.green)
        else if Signal == 2
            label.new(bar_index, high, "Long Exit", yloc=yloc.abovebar, style=label.style_label_down, color=color.yellow)
        else if Signal == -1
            label.new(bar_index, high, "Short Entry", yloc=yloc.abovebar, style=label.style_label_down, color=color.red)
        else if Signal == -2
            label.new(bar_index, low, "Short Exit", yloc=yloc.belowbar, style=label.style_label_up, color=color.yellow)

    // TEST
    // [_tradingDays, InTradeEquity, openProfit, NetProfit, ROI,NumTrades,AvgTradeLen,MaxDDPct,MaxDDPT,MaxLoss,MaxDDDur,MaxConsecutiveLosses,PMD,PLR,PctWinners,TimePct,Signal] = f_getData(_initialCapital)
    // TEST END

    // Alerts
    if MaxDDPct >= i_RiskMaxDrawdown
        alert(str.format('Max Drawdown Tolerance Exceeded for: {0} at {1, number, percent}', _asset, MaxDDPct / 100), alert.freq_all)

    if barstate.islast
        f_fillCell(t_portfolioResults, 0, _row, _asset, t_headerClr)
        f_fillCell(t_portfolioResults, 1, _row, str.tostring(NetProfit, '#.##'), f_getColor(NetProfit, _initialCapital, -_initialCapital))
        f_fillCell(t_portfolioResults, 2, _row, str.tostring(NumTrades, '#'), f_getColor(NumTrades, 200, 0))
        f_fillCell(t_portfolioResults, 3, _row, str.tostring(AvgTradeLen, '#.##'), t_neuColor)
        f_fillCell(t_portfolioResults, 4, _row, str.tostring(MaxDDPT, '#.##'), f_getColor(MaxDDPct, 0, -i_RiskMaxDrawdown))
        f_fillCell(t_portfolioResults, 5, _row, str.tostring(MaxLoss, '#.##'), f_getColor(MaxLoss, 0, -(InTradeEquity * _initialCapital) * (2.0 / 100)))
        f_fillCell(t_portfolioResults, 6, _row, str.tostring(MaxDDDur, '#'), t_neuColor)  // TODO: This depends on Timeframe and Time horizon. (pass -MaxDDDur, and adjust length by timeframe.)
        f_fillCell(t_portfolioResults, 7, _row, str.tostring(MaxConsecutiveLosses, '#'), f_getColor(-MaxConsecutiveLosses, 0, -11))
        f_fillCell(t_portfolioResults, 8, _row, str.tostring(PMD, '#.##'), f_getColor(PMD, 4, -4))
        f_fillCell(t_portfolioResults, 9, _row, str.tostring(PLR, '#.##'), f_getColor(PLR, 4, -4))
        f_fillCell(t_portfolioResults, 10, _row, str.tostring(PctWinners, format.percent), f_getColor(PctWinners, 100, 0))
        f_fillCell(t_portfolioResults, 11, _row, str.tostring(TimePct, format.percent), f_getColor(-TimePct, 0, -100))
        f_fillCell(t_portfolioResults, 12, _row, str.tostring(ROI, format.percent), f_getColor(ROI, 100, -100))
        f_fillCell(t_portfolioResults, 13, _row, str.tostring(openProfit, '#.##'), f_getColor(openProfit, NetProfit / 20, -NetProfit / 20))
        f_fillCell(t_portfolioResults, 14, _row, str.tostring(_tradingDays, '#.##'), t_neuColor)

    [nz(_tradingDays), nz(InTradeEquity), nz(openProfit), nz(NetProfit), nz(ROI), nz(NumTrades), nz(AvgTradeLen), nz(MaxDDPct), nz(MaxDDPT), nz(MaxLoss), nz(MaxDDDur), nz(MaxConsecutiveLosses), nz(PMD), nz(PLR), nz(PctWinners), nz(TimePct), Signal]




f_table() =>
    // Draw the Table.
    _i_sym1 = syminfo.tickerid
    _i_sym2 = input.symbol(defval='FX_IDC:CADJPY', title='Symbol 2', group='Portfolio Selection')
    _i_sym3 = input.symbol(defval='FX_IDC:GBPUSD', title='Symbol 3', group='Portfolio Selection')
    _i_sym4 = input.symbol(defval='TVC:USOIL', title='Symbol 4', group='Portfolio Selection')
    _i_sym5 = input.symbol(defval='BATS:BAC', title='Symbol 5', group='Portfolio Selection')

    // Draw Headers
    i_fullText = input.bool(true, 'Show Full Header Text', group='Table')
    if barstate.islast
        f_fillCell(t_portfolioResults, 0, 0, 'Portfolio', t_headerClr)
        f_fillCell(t_portfolioResults, 1, 0, i_fullText ? 'Net\nProfit' : 'NP', t_headerClr)
        f_fillCell(t_portfolioResults, 2, 0, i_fullText ? '# of\nTrades' : '#T', t_headerClr)
        f_fillCell(t_portfolioResults, 3, 0, i_fullText ? 'Average\nTrade\nLength' : 'ATL', t_headerClr)
        f_fillCell(t_portfolioResults, 4, 0, i_fullText ? 'Max\nDrawdown' : 'MD', t_headerClr)
        f_fillCell(t_portfolioResults, 5, 0, i_fullText ? 'Max\nLoss' : 'ML', t_headerClr)
        f_fillCell(t_portfolioResults, 6, 0, i_fullText ? 'Max\nDrawdown\nDuration' : 'MDD', t_headerClr)
        f_fillCell(t_portfolioResults, 7, 0, i_fullText ? 'Max\nConsecutive\nLosing\nTrades' : 'MCL', t_headerClr)
        f_fillCell(t_portfolioResults, 8, 0, i_fullText ? 'Profit\n—————\nMax\nDrawdown' : 'P:MD', t_headerClr)
        f_fillCell(t_portfolioResults, 9, 0, i_fullText ? 'Profit\n—————\nLoss' : 'P:L', t_headerClr)
        f_fillCell(t_portfolioResults, 10, 0, i_fullText ? '%\nWinning\nTrades' : '%W', t_headerClr)
        f_fillCell(t_portfolioResults, 11, 0, i_fullText ? '%\nTime\nin\nMarket' : '%T', t_headerClr)
        f_fillCell(t_portfolioResults, 12, 0, i_fullText ? 'Return\nOn\nInvestment' : 'ROI', t_headerClr)
        f_fillCell(t_portfolioResults, 13, 0, i_fullText ? 'Open\nProfit' : 'OP', t_headerClr)
        f_fillCell(t_portfolioResults, 14, 0, i_fullText ? 'Trading\nDays' : 'TD', t_headerClr)



    // Draw Rows
    numberOfAssetsInPortfolio = 5
    portfolioAllocation = PosTypeCapital / numberOfAssetsInPortfolio
    [TradingDays1, ITE1, openProfit1, NetProfit1, ROI1, NumTrades1, AvgTradeLen1, MaxDDPct1, MaxDDPT1, MaxLoss1, MaxDDDur1, MaxConsecutiveLosses1, PMD1, PLR1, PctWinners1, TimePct1, Signal1] = f_row(portfolioAllocation, _i_sym1, 1)
    [TradingDays2, ITE2, openProfit2, NetProfit2, ROI2, NumTrades2, AvgTradeLen2, MaxDDPct2, MaxDDPT2, MaxLoss2, MaxDDDur2, MaxConsecutiveLosses2, PMD2, PLR2, PctWinners2, TimePct2, Signal2] = f_row(portfolioAllocation, _i_sym2, 2)
    [TradingDays3, ITE3, openProfit3, NetProfit3, ROI3, NumTrades3, AvgTradeLen3, MaxDDPct3, MaxDDPT3, MaxLoss3, MaxDDDur3, MaxConsecutiveLosses3, PMD3, PLR3, PctWinners3, TimePct3, Signal3] = f_row(portfolioAllocation, _i_sym3, 3)
    [TradingDays4, ITE4, openProfit4, NetProfit4, ROI4, NumTrades4, AvgTradeLen4, MaxDDPct4, MaxDDPT4, MaxLoss4, MaxDDDur4, MaxConsecutiveLosses4, PMD4, PLR4, PctWinners4, TimePct4, Signal4] = f_row(portfolioAllocation, _i_sym4, 4)
    [TradingDays5, ITE5, openProfit5, NetProfit5, ROI5, NumTrades5, AvgTradeLen5, MaxDDPct5, MaxDDPT5, MaxLoss5, MaxDDDur5, MaxConsecutiveLosses5, PMD5, PLR5, PctWinners5, TimePct5, Signal5] = f_row(portfolioAllocation, _i_sym5, 5)
    // [TradingDays6, ITE6, NetProfit6,ROI6,NumTrades6,AvgTradeLen6,MaxDDPct6,MaxDDPT6,MaxLoss6,MaxDDDur6,MaxConsecutiveLosses6,PMD6,PLR6,PctWinners6,TimePct6,Signal6] = f_row("XRPUSD", 7, _i_size,_i_textAlign)

    // Track Totals
    maxTD = math.max(TradingDays1, TradingDays2, TradingDays3, TradingDays4, TradingDays5)
    totalITE = math.avg(ITE1, ITE2, ITE3, ITE4, ITE5)
    totalOP = openProfit1 + openProfit2 + openProfit3 + openProfit4 + openProfit5
    totalNetProfit = NetProfit1 + NetProfit2 + NetProfit3 + NetProfit4 + NetProfit5
    totalROI = totalNetProfit / PosTypeCapital * 100
    totalTrades = NumTrades1 + NumTrades2 + NumTrades3 + NumTrades4 + NumTrades5
    [totalMaxDDPct, totalMaxDDPT, totalMaxDDDur] = f_getMaxDD(totalITE, PosTypeCapital)

    NetChange = ta.change(totalNetProfit)
    totalMaxLoss = 0.
    totalMaxLoss := math.min(NetChange, nz(totalMaxLoss[1]))
    var totalMCLCount = 0
    totalMCLCount := NetChange < 0 ? totalMCLCount + 1 : 0
    var totalMCL = 0
    totalMCL := math.max(totalMCLCount, totalMCL)
    totalPMD = totalNetProfit / math.abs(totalMaxDDPT)
    totalPLR = math.avg(PLR1, PLR2, PLR3, PLR4, PLR5)
    totalPW = (NumTrades1 * PctWinners1 / 100 + NumTrades2 * PctWinners2 / 100 + NumTrades3 * PctWinners3 / 100 + NumTrades4 * PctWinners4 / 100 + NumTrades5 * PctWinners5 / 100) / totalTrades * 100

    InTrade = TradeDateIsAllowed() and (ta.change(totalITE) ? true : false)
    TotalTradeBars = ta.cum(TradeDateIsAllowed() ? 1 : 0)
    TotalInTradeBars = ta.cum(InTrade ? 1 : 0)
    totalATL = f_barsToDays(TotalInTradeBars / totalTrades)
    totalTP = TotalInTradeBars / TotalTradeBars * 100
    totalSignal = Signal1




    _totalRow = numberOfAssetsInPortfolio + 1
    if barstate.islast
        // Draw Totals
        f_fillCell(t_portfolioResults, 0, _totalRow, 'Total', t_headerClr)
        f_fillCell(t_portfolioResults, 1, _totalRow, str.tostring(totalNetProfit, '#.##'), f_getColor(totalNetProfit, PosTypeCapital, -PosTypeCapital))
        f_fillCell(t_portfolioResults, 2, _totalRow, str.tostring(totalTrades, '#'), f_getColor(totalTrades, 200, 0))
        f_fillCell(t_portfolioResults, 3, _totalRow, str.tostring(totalATL, '#.##'), t_neuColor)
        f_fillCell(t_portfolioResults, 4, _totalRow, str.tostring(totalMaxDDPT, '#.##'), f_getColor(totalMaxDDPT, 0, -totalITE * PosTypeCapital * (i_RiskMaxDrawdown / 100)))
        f_fillCell(t_portfolioResults, 5, _totalRow, str.tostring(totalMaxLoss, '#.##'), f_getColor(totalMaxLoss, 0, -totalITE * PosTypeCapital * (2.0 / 100)))
        f_fillCell(t_portfolioResults, 6, _totalRow, str.tostring(totalMaxDDDur, '#'), t_neuColor)
        f_fillCell(t_portfolioResults, 7, _totalRow, str.tostring(totalMCL, '#'), f_getColor(-totalMCL, 0, -11))
        f_fillCell(t_portfolioResults, 8, _totalRow, str.tostring(totalPMD, '#.##'), f_getColor(totalPMD, 4, -4))
        f_fillCell(t_portfolioResults, 9, _totalRow, str.tostring(totalPLR, '#.##'), f_getColor(totalPLR, 4, -4))
        f_fillCell(t_portfolioResults, 10, _totalRow, str.tostring(totalPW, format.percent), f_getColor(totalPW, 100, 0))
        f_fillCell(t_portfolioResults, 11, _totalRow, str.tostring(totalTP, format.percent), f_getColor(-totalTP, 0, -100))
        f_fillCell(t_portfolioResults, 12, _totalRow, str.tostring(totalROI, format.percent), f_getColor(totalROI, 100, -100))
        f_fillCell(t_portfolioResults, 13, _totalRow, str.tostring(totalOP, '#.##'), f_getColor(totalOP, totalNetProfit / 20, -totalNetProfit / 20))
        f_fillCell(t_portfolioResults, 14, _totalRow, str.tostring(maxTD, '#.##'), t_neuColor)

f_table()

Overview

Portfolio Backtester evaluates modular entry strategies, trade directions, entry stop-loss types such as ATR multiples or fixed percentages, and a wide variety of trade filters including MACD, RSI, bar direction, volume trends, and moving averages. The indicator plots execution markers and calculates detailed performance metrics, including net profit, max drawdown, win rate, return on investment, and trade duration statistics.

Details

Published by

f-cksociety on GitHub

Code credit

pAulseperformance, as written in the script's header

Type

Indicator, Pine Script v5

Draws

On the price chart

Category

Trend, Oscillators & momentum, Moving averages, Screeners & dashboards

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Trade DirectionstringTD1TradeDirection
Entry Strategy AstringEN1EntryA
Reverse SignalsboolfalseEntryAReverse
Entry Strategy BstringEN0EntryB
Reverse SignalsboolfalseEntryBReverse
Entry Stop SelectionstringES0EntryStopType
  2. ATRfloat1.5EntryStopType2Mult
EntryStopType2Lenint14EntryStopType2Len
  3. Fixed %float3.0EntryStopType3Pct
1. RandomboolfalseFilterType1
2. MACDboolfalseFilterType2
FilterType2Len1int12FilterType2Len1
Show all 40 settings
SettingTypeDefaultVariable
FilterType2Len2int26FilterType2Len2
FilterType2Len3int9FilterType2Len3
3. Bar direction=Trade directionboolfalseFilterType3
4. Rising VolumeboolfalseFilterType4
5. Rising/Falling MA ↴           boolfalseFilterType5
  ↳ Lengthint50FilterType5Len
  ↳ For n Barsint1FilterType5Bars
6. Maximum % Stop allowed on entryboolfalseFilterType6
  float6.0FilterType6MaxRisk
7. Maximum close change (%) allowed on entryboolfalseFilterType7
FilterType7IncPctfloat10.0FilterType7IncPct
8. Minimum close change (%) allowed on entryboolfalseFilterType8
FilterType8IncPctfloat1.0FilterType8IncPct
9. RSI OB/OS ↴           boolfalseFilterType9
  ↳ Lengthint20FilterType9Len
  ↳ Oversold Levelint25FilterType9OS
  ↳ Overbought Levelint75FilterType9OB
10. MAboolfalseFilterType10
FilterType10MATypestringSMAFilterType10MAType
FilterType10SourcesourcecloseFilterType10Source
FilterType10Lenint200FilterType10Len
In-Trade Stop SelectionstringIS0InTradeStopType
  1. X (Entry Stop's Amplitude) Multiplefloat1.0InTradeStopType1Mult
  2. %float2.0InTradeStopType2Pct
  3. Valuefloat0.001InTradeStopType3Val
  5. ATRint20InTradeStopType567Len
InTradeStopType567Multfloat3.0InTradeStopType567Mult
In-Trade Stop Kicks In When...stringIK1InTradeKickType
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

218 stars

TradersPost Trading Checklist is an overlay indicator that evaluates a suite of popular technical conditions for long and short trade setups. It checks session-anchored VWAP, fast and slow EMA alignments and crosses, 200 SMA, RSI overbought or oversold conditi

Indicator v6 Screeners & dashboardsTrend
Updated 3 months ago · None stated GitHub repository of TradersPost Trading Checklist
53 stars

This indicator analyzes historical candle direction and price range over a configurable lookback window to gauge market trend. It categorizes trend direction using candle count, price range sum, or both. Signals are filtered using a Simple Moving Average to en

Indicator v6 TrendOscillators & momentum
Updated 2 months ago · MPL-2.0 GitHub repository of Candle Counter + MA + RSI + Mode

Pocket Scout - SPY/QQQ

by chris-c-thomas
14 stars

Pocket Scout - SPY/QQQ is a mobile-optimized intraday bias indicator designed for major US equity ETFs and indices. It overlays a 9/21/50 EMA ribbon, session VWAP, prior day high/low/close levels, and intraday high/low lines directly on the main price chart. T

Indicator v6 TrendOscillators & momentum Repaints
Updated 6 months ago · MIT GitHub repository of Pocket Scout - SPY/QQQ

KamaFama

by Mastaaa1987
141 stars

KamaFama is an indicator that translates a Freqtrade trading strategy into Pine Script for visualization. It combines Kaufman Adaptive Moving Average (KAMA), MAMA/FAMA calculations derived via Hilbert Transform, Williams %R, and correlation trend filters to ev

Indicator v6 Moving averagesOscillators & momentum
Updated 6 weeks ago · MIT GitHub repository of KamaFama

MTF WaveTrend Opportunity Hunter calculates multi-timeframe momentum and confluence scores using WaveTrend across custom Regime, Setup, Trigger, and Context timeframes. It plots a net confluence score histogram, multi-layer heat ribbons, an RRG-style Rotation

Indicator v6 TrendOscillators & momentum
Updated yesterday · MIT GitHub repository of MTF WaveTrend Opportunity Hunter [WavesUnchained]
948 stars

The Adaptive RSI Moving Average (ARSIMA) is an overlay indicator that dynamically adjusts its smoothing factor based on the Relative Strength Index (RSI). When the RSI moves toward extreme overbought or oversold conditions, the smoothing factor increases to ma

Indicator v4 Moving averagesOscillators & momentum
Updated 10 days ago · GPL-3.0 GitHub repository of Adaptive RSI Moving Average

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers