Page 1 of 1

Coding Variable Position Size in Pine

Posted: Thu Aug 11, 2022 4:19 am
by MFA
Hi Pine Scripters,

I've been working on a simple script derived from Matt's in his latest YouTube video. I'm new to Pine (and to programming), but I am part-way through a strategy that allows the user to set a percentage of their account balance to risk per trade, where the balance is understood to be (strategy.initial_capital + strategy.netprofit). Starting with $1000 and a risk of 1%, were the first trade to be stopped out, the second trade would risk 1% of the remaining balance ($990) and so on. I understand the formulae there are for calculating position size, but I can''t seem to make the code work.

Here's the important bit:

Code: Select all

long_diff = math.abs(long_entryprice - dc_lower_at_buy)
long_tp := long_entryprice + (ireward_multi * long_diff)

equitytorisk = equity * (i_risk/100)
pos_size = equitytorisk / long_diff
pos_size_cash = pos_size * long_entryprice
Line one gets the difference between my buy and stop prices. Line four calculates 1% of the account balance. Line five, calculates the position size by dividing the latter by the former. Have I made a mistake in the calculation?

I then set the entry function for longs with 'pos_size' as the quantity:

Code: Select all

if long_condition
    strategy.entry(id="Long", direction=strategy.long, qty=pos_size)
Note that I calculate the position cash value under 'pos_size_cash'. I've tried using both pos_size and 'pos_size_cash' in the entry function, above, but the script seems to default to either buying one contract with every trade, or else buying miniscule amounts that I can't trace to what I've written. What's more, when I change the risk percentage user input (i_risk) in the strategy testing menu, the profit and loss figures - which should change - don't change at all.

I've tried troubleshooting by plotting each of the variables in the calculation above to the chart so I can check them. They all come out as you would expect, including the position size. I'm using an Ethereum chart on the 5 or 15m timeframes, so the position sizes are all fractions of 1, with corresponding cash values (Ethereum is worth about $1700 at the moment.)

So, for example, I have one order that shows readings for equity of $1000, equity risk-per-trade of $10, a price difference of $26, and a position size of 0.38 Eth, for a total of $657. Nevertheless, the script bought 0.0005 Eth, and for some reason the user-menu is set to risking $1 per trade.

I'll post the whole script below - just bear in mind that I'm not worried about the entry criteria and other bits and pieces, so much as understanding why the script doesn't buy the correct position size.

Thanks in advance for your time.

Code: Select all

//@version=5
strategy("My strategy",
     overlay=true, 
     initial_capital=1000, 
     default_qty_type=strategy.cash,
     commission_type=strategy.commission.cash_per_contract, 
     commission_value=0.005,
     margin_long=0,
     margin_short=0)

equity = strategy.initial_capital + strategy.netprofit 

//User Inputs

i_mfilength = input.int(title="MFI Length", defval=14, step=1, group="Strategy Parameters", tooltip="MFI Length")
i_emalength = input.int(title="EMA Length", defval=200, step=1, group="Strategy Parameters", tooltip="Long Term MA")
// istoppercent = input.int(title="Stop Loss Percentage", defval=2, step=1, group="Strategy Parameters", tooltip ="Failsafe Stop Loss, Percentage Decline")
ibtstarttime = input.time(title="Start Backtest", defval=timestamp("01 Jan 2022 00:00 +0000"), group="Backtest Period")
ibtendtime   = input.time(title="End Backtest", defval=timestamp("01 Jan 2099"), group="Backtest Period")
ireward_multi = input.float(title="Reward as a Multiple of Risk", defval=1.5, step=0.1, group="Strategy Parameters")
i_risk = input.float(title="Risk as Percentage of Equity", defval=1, step=0.1, group="Strategy Parameters")
i_mfiob = input.int(title="MFI Overbought Level", defval=80, step=1, minval = 1, maxval=100, group="Strategy Parameters")
i_mfios = input.int(title="MFI Oversold Level", defval=20, step=1, minval = 1, maxval = 100, group="Strategy Parameters")
idcl = input.int(title="Donchian Channels Length", defval=20, step=1, group="Strategy Parameters")

//Get Donchian Channels

lower = ta.lowest(idcl)
upper = ta.highest(idcl)
basis = math.avg(upper, lower)

//Get EMA

ema = ta.ema(close, i_emalength)

//Get MFI

mfi = ta.mfi(close, i_mfilength)

//Check Position Relative to Date Filter

//insidebtperiod = time >= ibtstarttime and time <= ibtendtime

//EMA, MFI and Donchian Buy Conditions

var float buyprice = 0.0
var bool dc_upsignal = false
var bool mfi_buysignal = false

if upper > upper[1] and (close > ema)
    dc_upsignal := true 
if close < ema
    dc_upsignal := false

if mfi < i_mfios
    mfi_buysignal := true 
if close < ema
    mfi_buysignal := false

mfi_sellsignal = mfi > i_mfiob

long_condition = 
     strategy.position_size == 0 and 
     mfi_buysignal 
     //dc_upsignal and 
     //close > upper [1]

//SL and TP

var float long_entryprice = 0.0
var float long_sl = na
var float long_tp = na
var float dc_lower_at_buy = na

long_diff = math.abs(long_entryprice - dc_lower_at_buy)
long_tp := long_entryprice + (ireward_multi * long_diff)

//Position Size

equitytorisk = equity * (i_risk/100)
pos_size = equitytorisk / long_diff
pos_size_cash = pos_size * long_entryprice

//percentloss = (long_diff/long_entryprice)*100

// ENTRY/EXIT

if long_condition
    strategy.entry(id="Long", direction=strategy.long, qty=pos_size_cash)
    dc_lower_at_buy := lower

if long_condition[1]
    long_entryprice := open

//strategy.exit("EXIT LONG","LONG", stop=long_sl, limit=long_tp)

stop_condition = strategy.position_size > 0 ? close < dc_lower_at_buy : na
profit_condition = strategy.position_size > 0 ? close > long_tp : na

if profit_condition or stop_condition
    strategy.close(id="Long", comment="Exit")
    long_entryprice := na
    dc_lower_at_buy := na
    long_tp := na
// Plot Entry, TP and SL

//Plot Longs

plot(long_entryprice, color=color.lime, style=plot.style_linebr)
plot(dc_lower_at_buy, color=color.red, style=plot.style_linebr, offset=1, linewidth=2)
plot(long_tp, color=color.yellow, style=plot.style_linebr, linewidth=2)
   
//Plot Indicators 

//Donchian Channels 

u = plot(upper, "Donchian Channel Upper Limit", color=#2962FF)
l = plot(lower, "Donchian Channel Lower Limit", color=#2962FF)
plot(basis, "Donchian Channel Basis", color=#FF6D00)
fill(u, l, color=color.rgb(33, 150, 243, 95), title="Background")

plot(ema, title="EMA", color=color.red, linewidth=2)
plot(equity, "equity", color=color.black)
plot(equitytorisk, "Equity Risked", color=color.purple)
plot(long_diff, "Price Difference, Entry and Exit", color = color.blue)
//plot(percentloss, "Percentage Loss", color = color.red)
plot(pos_size, "Position Size", color = color.black)
plot(pos_size_cash, "Position Size in Cash", color = color.green)

Update

Posted: Thu Aug 11, 2022 12:44 pm
by MFA
I've also tried adding the position size as a percent of equity and using that as the 'qty' argument in my strategy.entry function - I just divide the position size in cash by the account balance:

Code: Select all

equitytorisk = equity * (i_risk/100)
//percentloss = (long_diff/long_entryprice)*100
pos_size = equitytorisk / long_diff
pos_size_cash = pos_size * long_entryprice
pos_size_pctequity = (pos_size_cash/equity)*100
...

Code: Select all

 strategy.entry(id="Long", direction=strategy.long, qty=pos_size_pctequity)
Unfortunately, the script still buys a percentage amount other than pos_size_pctequity, and adjusting the percentage risk input still has no impact on the strategy's profit and loss statistics.

I was also concerned that I'd written the script in such a way as it attempted to set the position size after the order had been opened, or before all the 'pos_size' constituent variables had been properly determined. Maybe that's the issue?

Re: Coding Variable Position Size in Pine

Posted: Fri Aug 12, 2022 12:20 am
by MFA
Update

After looking over the code again this evening, I seem to have fixed the position-sizing issue, though I would still appreciate some clarity on the problem - I'm not exactly sure how I fixed it.

However, I'm now having trouble plotting entry, profit, and loss lines to the chart as they were before I made any changes.

Here's an example:

Image

You can see that the profit and loss lines follow the Donchian channel as it alters for the duration of the order, rather than simply extending horizontally.

Here's the code as it stands now:

Code: Select all

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

//@version=5
strategy("My strategy",
     overlay=true, 
     margin_long=0, 
     margin_short=0,
     commission_type=strategy.commission.cash_per_order,
     commission_value=0.005,
     initial_capital = 1000)

import TradingView/Strategy/3 as s3

///User Inputs///

i_mfilength = input.int(title="MFI Length", defval=14, step=1, group="Indicator Parameters", tooltip="MFI Length")
i_emalength = input.int(title="EMA Length", defval=200, step=1, group="Indicator Parameters", tooltip="Long Term MA")
i_btstarttime = input.time(title="Start Backtest", defval=timestamp("01 Jan 2022 00:00 +0000"), group="Backtest Period")
i_btendtime   = input.time(title="End Backtest", defval=timestamp("01 Jan 2099"), group="Backtest Period")
i_rewardmulti = input.float(title="Reward as a Multiple of Risk", defval=1.5, step=0.1, group="Strategy Parameters")
i_risk = input.float(title="Risk as Percentage of Equity", defval=1, step=0.1, group="Strategy Parameters")
i_mfiob = input.int(title="MFI Overbought Level", defval=80, step=1, minval = 1, maxval=100, group="Indicator Parameters")
i_mfios = input.int(title="MFI Oversold Level", defval=20, step=1, minval = 1, maxval = 100, group="Indicator Parameters")
i_dcl = input.int(title="Donchian Channels Length", defval=20, step=1, group="Indicator Parameters")

///Indicators///

//Donchian Channels

lower = ta.lowest(i_dcl)
upper = ta.highest(i_dcl)
basis = math.avg(upper, lower)

//EMA

ema = ta.ema(close, i_emalength)

//MFI

mfi = ta.mfi(close, i_mfilength)

///Buy Conditions///

//Persistent MFI Switch

var bool mfibuy = na

if mfi < i_mfios
    mfibuy := true 
if close < ema
    mfibuy := false

//MFI and EMA 

long_condition =
     mfibuy and 
     close > ema

///Calculate distance from Buy Price to Lower DC channel 

var float ldc_atbuy = na 
var float entry_price = na
var float long_tp = na

long_diff = math.abs(entry_price - ldc_atbuy)
long_tp := entry_price + (i_rewardmulti * long_diff)
sl_percent = (long_diff/entry_price)*100
equityrisked = (i_risk/100) * (strategy.initial_capital + strategy.netprofit)
pos_size = (equityrisked / long_diff)

///Trade Execution///

if long_condition and strategy.position_size == 0
    entry_price := upper[1]
    ldc_atbuy := lower
    strategy.entry(id="Long", stop=entry_price, direction=strategy.long, qty=pos_size)

if close < ema
    strategy.cancel_all()
    
if (close > long_tp[1] or close < ldc_atbuy) and strategy.position_size > 0
    strategy.close(id="Long", comment = "Exit")
    ldc_atbuy := na
    entry_price := na
    long_diff := na
    long_tp := na
    sl_percent := na
    
///Plots///

//Debugging

plot(equityrisked, title="Equity Risked ($)", color=color.purple, display=display.data_window)
plot(long_diff, title="(E-SL) Potential Loss per Unit ($)", color=color.orange, display=display.data_window)
plot(sl_percent, title="Potential Percentage Loss (%)", color=color.red, display=display.data_window)
plot(pos_size, title="Position Size", color=color.blue, display=display.data_window)

//Donchian Channels 

u = plot(upper, "Donchian Channel Upper Limit", color=#2962FF, display=display.pane)
l = plot(lower, "Donchian Channel Lower Limit", color=#2962FF, display=display.pane)
plot(basis, "Donchian Channel Basis", color=#FF6D00, display=display.pane)
fill(u, l, color=color.rgb(33, 150, 243, 95), title="Background")

//Ema

plot(ema, title="EMA", color=color.red, linewidth=2, display=display.pane)

//Long Entry, SL and TP

plot(entry_price, color=color.lime, style=plot.style_linebr, display=display.pane)
plot(ldc_atbuy, color=color.red, style=plot.style_linebr, display=display.pane, offset=1, linewidth=2)
plot(long_tp, color=color.yellow, style=plot.style_linebr, display=display.pane, linewidth=2)