Calculating Forex LOT SIZES

This lesson demonstrates how to calculate forex lot sizes to be used in TradingView's Strategy Tester to get a more accurate representation of your strategy's historical performance

Check out my other free lessons!

Source Code

// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © ZenAndTheArtOfTrading / www.PineScriptMastery.com
//
// System Rules:
// Indicators: MACD indicator (default values), ATR (14), EMA (200)
// 1. Price must be trading above/below the 200 EMA
// 2. Only 1 bar can close above/below the 200 EMA over the past 5 bars
// 3. We take the FIRST MACD cross and ignore subsequent signals until TP/SL is hit
// 4. Stop Loss = 0.5 ATR above/below the recent swing high/low (7 bars lookback)
// 5. First take profit = 1:1 (25%)
// 6. Second take profit = 2:1 (100%)
// 7. Move stop loss to break-even after 1st target is hit
//
// @version=5
strategy("[2022] MACD Cross Strategy", overlay=true,
     currency="USD",
     calc_on_order_fills=true,
     use_bar_magnifier=true,
     initial_capital=10000,
     default_qty_type=strategy.percent_of_equity,
     default_qty_value=100, // 100% of balance invested on each trade
     commission_type=strategy.commission.cash_per_contract)
     //commission_value=0.005) // Interactive Brokers rate

// Get user input
var g_system    = "System Entry Settings"
i_ema_filter    = input.int(title="EMA Filter Length", defval=200, group=g_system)
i_ema_filter2   = input.int(title="EMA Max Bars Above/Below", defval=1, group=g_system)
i_stop_multi    = input.float(title="Stop Loss Multiplier", defval=0.5, step=0.5, group=g_system)
i_stop_lookback = input.int(title="Stop Loss Lookback", defval=7, group=g_system)
var g_risk      = "System Risk Settings"
i_rr1           = input.float(title="Risk:Reward Target 1", defval=1.0, group=g_risk)
i_rr2           = input.float(title="Risk:Reward Target 2", defval=2.0, group=g_risk)
i_target1       = input.float(title="Profit % Target 1", defval=25, group=g_risk)
i_riskPerTrade  = input.float(title="Forex Risk Per Trade %", defval=1.0)
i_useLots       = input.bool(title="Use Lots Instead Of Units", defval=false)
var g_macd      = "MACD Settings"
i_price_src     = input.source(title="Price Source", defval=close, group=g_macd)
i_fast_length   = input.int(title="Fast Length", defval=12, group=g_macd)
i_slow_length   = input.int(title="Slow Length", defval=26, group=g_macd)
i_signal_length = input.int(title="Signal Smoothing", minval=1, maxval=50, defval=9, group=g_macd)
i_sma_source    = input.string(title="Oscillator MA Type", defval="EMA", options=["SMA", "EMA"], group=g_macd)
i_sma_signal    = input.string(title="Signal Line MA Type", defval="EMA", options=["SMA", "EMA"], group=g_macd)

//------------- DETERMINE CURRENCY CONVERSION RATE ------------- { //
// Import ZenLibrary
import ZenAndTheArtOfTrading/ZenLibrary/5 as zen
// Custom function for converting units into lot sizes
unitsToLots(units) =>
    float lots = units / 100000
    lots := math.round(lots, 2)
    _return = lots * 100000
// Check if our account currency is the same as the base or quote currency or neither (for risk $ conversion purposes)
accountSameAsCounterCurrency = strategy.account_currency == syminfo.currency
accountSameAsBaseCurrency = strategy.account_currency == syminfo.basecurrency
accountNeitherCurrency = not accountSameAsCounterCurrency and not accountSameAsBaseCurrency
// Get currency conversion rates if applicable
conversionCurrencyPair = accountSameAsCounterCurrency ? syminfo.tickerid : strategy.account_currency + syminfo.currency
conversionCurrencyRate = accountSameAsBaseCurrency or accountNeitherCurrency ? request.security(conversionCurrencyPair, "D", close, ignore_invalid_symbol=true) : 1.0
// Display the current conversion currency ticker (for debug purposes)
if barstate.islastconfirmedhistory
    table t = table.new(position.top_right, 1, 2, color.black)
    table.cell(t, 0, 0, "Conversion: " + conversionCurrencyPair + " (" + str.tostring(conversionCurrencyRate) + ")", text_color=color.white, text_size=size.small)
    table.cell(t, 0, 1, "Account: $" + str.tostring(zen.truncate(strategy.equity)), text_color=color.white, text_size=size.small)
//------------- END CURRENCY CONVERSION RATE CODE ------------- }//

// Calculate MACD
[macdLine, signalLine, histLine] = ta.macd(i_price_src, i_fast_length, i_slow_length, i_signal_length)

// Get indicator values
ema = ta.ema(close, i_ema_filter)
atr = ta.atr(14)

// Check for zero-point crosses
crossUp     = ta.crossover(signalLine, macdLine)
crossDown   = ta.crossunder(signalLine, macdLine)

// Check general system filters
tradeFilters = not na(ema) and not na(atr)

// Check trend conditions
upTrend     = close > ema
downTrend   = close < ema

// Check trade conditions
longConditions  = tradeFilters and macdLine[1] < 0 and signalLine[1] < 0
shortConditions = tradeFilters and macdLine[1] > 0 and signalLine[1] > 0

// Confirm long & short setups
longSignal   = longConditions and upTrend and crossDown
shortSignal  = shortConditions and downTrend and crossUp

// Calculate stop loss
longStop    = ta.lowest(low, i_stop_lookback) - (atr * i_stop_multi)
shortStop   = ta.highest(high, i_stop_lookback) + (atr * i_stop_multi)

// Save stops & targets
var float tradeStop = na
var float tradeTarget1 = na
var float tradeTarget2 = na
var float tradeSize = na

// Count bars above/below MA
int barsAboveMA = 0
int barsBelowMA = 0

for i = 1 to 5
    if close[i] < ema[i]
        barsBelowMA += 1
    if close[i] > ema[i]
        barsAboveMA += 1

// Combine signal filters
longTrade   = longSignal  and barsBelowMA <= i_ema_filter2 and strategy.position_size == 0
shortTrade  = shortSignal and barsAboveMA <= i_ema_filter2 and strategy.position_size == 0

// Handle long trade entry (enter position, reset stops & targets)
if longTrade
    if syminfo.type == "forex"
        tradeStop := longStop
        stopDistance = close - tradeStop
        tradeTarget1 := close + (stopDistance * i_rr1)
        tradeTarget2 := close + (stopDistance * i_rr2)
        tradeSize := na
        positionSize = zen.av_getPositionSize(strategy.equity, i_riskPerTrade, zen.toWhole(stopDistance) * 10, conversionCurrencyRate)
        strategy.entry(id="Long", direction=strategy.long, qty=i_useLots ? unitsToLots(positionSize) : positionSize)
    else
        strategy.entry(id="Long", direction=strategy.long)
        tradeStop := na
        tradeTarget1 := na
        tradeTarget2 := na

// Handle short trade entry (enter position, reset stops & targets)
if shortTrade
    if syminfo.type == "forex"
        tradeStop := shortStop
        stopDistance = tradeStop - close
        tradeTarget1 := close - (stopDistance * i_rr1)
        tradeTarget2 := close - (stopDistance * i_rr2)
        tradeSize := na
        positionSize = zen.av_getPositionSize(strategy.equity, i_riskPerTrade, zen.toWhole(shortStop - close) * 10, conversionCurrencyRate)
        strategy.entry(id="Short", direction=strategy.short, qty=i_useLots ? unitsToLots(positionSize) : positionSize)
    else
        strategy.entry(id="Short", direction=strategy.short)
        tradeStop := na
        tradeTarget1 := na
        tradeTarget2 := na
    
// Handle forex trade size tracking variable
if syminfo.type == "forex" and strategy.position_size != 0 and na(tradeSize)
    tradeSize := strategy.position_size

// Handle long stops & target calculation
if strategy.position_size > 0 and na(tradeStop) and syminfo.type != "forex"
    tradeStop := longStop
    stopDistance = strategy.position_avg_price - tradeStop
    tradeTarget1 := strategy.position_avg_price + (stopDistance * i_rr1)
    tradeTarget2 := strategy.position_avg_price + (stopDistance * i_rr2)
    tradeSize := strategy.position_size

// Handle short stops & target calculation
if strategy.position_size < 0 and na(tradeStop) and syminfo.type != "forex"
    tradeStop := shortStop
    stopDistance = tradeStop - strategy.position_avg_price
    tradeTarget1 := strategy.position_avg_price - (stopDistance * i_rr1)
    tradeTarget2 := strategy.position_avg_price - (stopDistance * i_rr2)
    tradeSize := strategy.position_size

// Handle trade exits
float exitPartialUnits = math.abs(strategy.position_size / (100 / i_target1))
float exitPartialLots = unitsToLots(exitPartialUnits)
strategy.exit(id="Long Exit #1",  from_entry="Long",  limit=tradeTarget1, stop=tradeStop, qty=i_useLots ? exitPartialLots : exitPartialUnits)
strategy.exit(id="Long Exit #2",  from_entry="Long",  limit=tradeTarget2, stop=tradeStop, qty_percent=100)
strategy.exit(id="Short Exit #1", from_entry="Short", limit=tradeTarget1, stop=tradeStop, qty=i_useLots ? exitPartialLots : exitPartialUnits)
strategy.exit(id="Short Exit #2", from_entry="Short", limit=tradeTarget2, stop=tradeStop, qty_percent=100)

// Handle both long & short trade break-even stops (do this AFTER first position has exited above ^)
if strategy.position_size != tradeSize
    tradeStop := strategy.position_avg_price
    tradeTarget1 := na

// Draw conditional data
plot(ema, color=close > ema ? color.green : color.red, linewidth=2, title="EMA")
plotshape(longTrade,  style=shape.triangleup,   color=color.green, location=location.belowbar, title="Long Setup")
plotshape(shortTrade, style=shape.triangledown, color=color.red,   location=location.abovebar, title="Short Setup")

// Draw stops & targets
plot(strategy.position_size != 0 ? tradeStop : na,   color=color.red,   style=plot.style_linebr, title="Stop Loss")
plot(strategy.position_size != 0 ? tradeTarget1 : na, color=color.green, style=plot.style_linebr, title="Profit Target 1")
plot(strategy.position_size != 0 ? tradeTarget2 : na, color=color.green, style=plot.style_linebr, title="Profit Target 2")