// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © TradingView
//@version=5
// @description This library contains strategy helper functions that assist in various positional calculations such as PnL, percent, ticks, price, or currency.
library("Strategy", true)
// ———————————————————— Library functions {
// @function Converts a percentage of the supplied price or the average entry price to ticks.
// @param percent (series int/float) The percentage of supplied price to convert to ticks. 50 is 50% of the entry price.
// @param from (series int/float) A price that can be used to calculate from. Optional. The default value is `strategy.position_avg_price`.
// @returns (float) A value in ticks.
export percentToTicks(series float percent, series float from = strategy.position_avg_price) =>
strategy.position_size != 0 ? percent / 100 * from / syminfo.mintick : float(na)
// @function Converts a percentage of the supplied price or the average entry price to a price.
// @param percent (series int/float) The percentage of the supplied price to convert to price. 50 is 50% of the supplied price.
// @param from (series int/float) A price that can be used to calculate from. Optional. The default value is `strategy.position_avg_price`.
// @returns (float) A value in the symbol's quote currency (USD for BTCUSD).
export percentToPrice(series float percent, series float from = strategy.position_avg_price) =>
strategy.position_size != 0 ? (1 + percent / 100) * from : float(na)
// @function Converts the percentage of a price to money.
// @param price (series int/float) The symbol's price.
// @param percent (series int/float) The percentage of `price` to calculate.
// @returns (float) A value in the symbol's currency.
export percentToCurrency(series float price, series float percent) =>
price * percent / 100 * syminfo.pointvalue
// @function Calculates the profit (as a percentage of the position's `strategy.position_avg_price` entry price) if the trade is closed at `exitPrice`.
// @param exitPrice (series int/float) The potential price to close the position.
// @returns (float) Percentage profit for the current position if closed at the `exitPrice`.
export percentProfit(series float exitPrice) =>
if strategy.position_size != 0
(exitPrice - strategy.position_avg_price) / strategy.position_avg_price * math.sign(strategy.position_size) * 100
else
float(na)
// @function Converts a price to ticks.
// @param price (series int/float) Price to convert to ticks.
// @returns (float) A quantity of ticks.
export priceToTicks(series float price) =>
price / syminfo.mintick
// @function Converts ticks to a price offset from the supplied price or the average entry price.
// @param ticks (series int/float) Ticks to convert to a price.
// @param from (series int/float) A price that can be used to calculate from. Optional. The default value is `strategy.position_avg_price`.
// @returns (float) A price level that has a distance from the entry price equal to the specified number of ticks.
export ticksToPrice(series float ticks, series float from = strategy.position_avg_price) =>
float offset = ticks * syminfo.mintick
strategy.position_size != 0 ? from + offset : float(na)
// @function Converts ticks to money.
// @param ticks (series int/float) Number of ticks.
// @returns (float) Money amount in the symbol's currency.
export ticksToCurrency(series float ticks) =>
ticks * syminfo.mintick * syminfo.pointvalue
// @function Calculates a stop loss level using a distance in ticks from the current `strategy.position_avg_price` entry price. This value can be plotted on the chart, or used as an argument to the `stop` parameter of a `strategy.exit()` call. NOTE: The stop level automatically flips based on whether the position is long or short.
// @param ticks (series int/float) The distance in ticks from the entry price to the stop loss level.
// @returns (float) A stop loss level for the current position.
export ticksToStopLevel(series float ticks) =>
ticksToPrice(-ticks * math.sign(strategy.position_size))
// @function Calculates a take profit level using a distance in ticks from the current `strategy.position_avg_price` entry price. This value can be plotted on the chart, or used as an argument to the `limit` parameter of a `strategy.exit()` call. NOTE: The take profit level automatically flips based on whether the position is long or short.
// @param ticks (series int/float) The distance in ticks from the entry price to the take profit level.
// @returns (float) A take profit level for the current position.
export ticksToTpLevel(series float ticks) =>
ticksToPrice(ticks * math.sign(strategy.position_size))
// @function Calculates the position size needed to implement a given stop loss (in ticks) corresponding to `riskPercent` of equity.
// @param stopLossTicks (series int) The stop loss (in ticks) that will be used to protect the position.
// @param riskPercent (series int/float) The maximum risk level as a percent of current equity (`strategy.equity`).
// @returns (int) A quantity of contracts.
export calcPositionSizeByStopLossTicks(series int stopLossTicks, series float riskPercent) =>
float risk = strategy.equity * riskPercent / 100
math.floor(risk / ticksToCurrency(stopLossTicks))
// @function Calculates the position size needed to implement a given stop loss (%) corresponding to `riskPercent` of equity.
// @param stopLossPercent (series int/float) The stop loss in percent that will be used to protect the position.
// @param riskPercent (series int/float) The maximum risk level as a percent of current equity (`strategy.equity`).
// @param entryPrice (series int/float) The entry price of the position.
// @returns (int) A quantity of contracts.
export calcPositionSizeByStopLossPercent(series float stopLossPercent, series float riskPercent, series float entryPrice = close) =>
float risk = strategy.equity * riskPercent / 100
math.floor(risk / percentToCurrency(entryPrice, stopLossPercent))
// @function A wrapper of the `strategy.exit()` built-in which adds the possibility to specify loss & profit as a value in percent. NOTE: this function may work incorrectly with pyramiding turned on due to the use of `strategy.position_avg_price` in its calculations of stop loss and take profit offsets.
// @param id (series string) The order identifier of the `strategy.exit()` call.
// @param lossPercent (series int/float) Stop loss as a percent of the entry price.
// @param profitPercent (series int/float) Take profit as a percent of the entry price.
// @param qty (series int/float) Number of contracts/shares/lots/units to exit a trade with. Optional. The default value is `na`.
// @param qtyPercent (series int/float) The percent of the position's size to exit a trade with. If `qty` is `na`, the default value of `qty_percent` is 100.
// @param comment (series string) Optional. Additional notes on the order.
// @param when (series bool) Condition of the order. The order is placed if it is true.
// @param alertMessage (series string) An optional parameter which replaces the {{strategy.order.alert_message}} placeholder when it is used in the "Create Alert" dialog box's "Message" field.
// @returns (void)
export exitPercent(series string id = "Exit",
series float lossPercent ,
series float profitPercent ,
series float qty = na ,
series float qtyPercent = na ,
series string comment = na ,
series bool when = true ,
series string alertMessage = na ) =>
strategy.exit(id, qty = qty, qty_percent = qtyPercent, loss = math.ceil(percentToTicks(lossPercent)), profit = math.round(percentToTicks(profitPercent)), comment = comment, when = when, alert_message = alertMessage)
// @function A wrapper of the `strategy.exit()` built-in which adds the possibility to specify loss & profit as a value in ticks.
// @param id (series string) The order identifier of the `strategy.exit()` call.
// @param lossTicks (series int/float) Loss in ticks.
// @param profitTicks (series int/float) Profit in ticks.
// @param qty (series int/float) Number of contracts/shares/lots/units to exit a trade with. Optional. The default value is `na`.
// @param qtyPercent (series int/float) The percent of the position's size to exit a trade with. If `qty` is `na`, the default value of `qty_percent` is 100.
// @param comment (series string) Optional. Additional notes on the order.
// @param when (series bool) Condition of the order. The order is placed if it is true.
// @param alertMessage (series string) An optional parameter which replaces the {{strategy.order.alert_message}} placeholder when it is used in the "Create Alert" dialog box's "Message" field.
// @returns (void)
export exitTicks(series string id = "Exit",
series float lossTicks ,
series float profitTicks ,
series float qty = na ,
series float qtyPercent = na ,
series string comment = na ,
series bool when = true ,
series string alertMessage = na ) =>
strategy.exit(id, qty = qty, qty_percent = qtyPercent, loss = lossTicks * syminfo.mintick, profit = profitTicks * syminfo.mintick, comment = comment, when = when, alert_message = alertMessage)
// @function A wrapper of the `strategy.close_all()` built-in which closes all positions on the close of the last bar of the trading session.
// @param comment (series string) Optional. Additional notes on the order.
// @param alertMessage (series string) An optional parameter which replaces the {{strategy.order.alert_message}} placeholder when it is used in the "Create Alert" dialog box's "Message" field.
// @returns (void) Closes all positions on the `close` of the last bar of the trading session, instead of the `open` of the next session.
export closeAllAtEndOfSession(series string comment = na, series string alertMessage = na) =>
if session.islastbar and barstate.isconfirmed
strategy.close_all(comment = comment, alert_message = alertMessage, immediately = true)
// @function A wrapper of the `strategy.close()` built-in which closes the position matching the `entryId` on the close of the last bar of the trading session.
// @param entryId (series string) A required parameter. The order identifier. It is possible to close an order by referencing its identifier.
// @param comment (series string) Optional. Additional notes on the order.
// @param alertMessage (series string) An optional parameter which replaces the {{strategy.order.alert_message}} placeholder when it is used in the "Create Alert" dialog box's "Message" field.
// @returns (void) Closes `entryId` on the `close` of the last bar of the trading session, instead of the `open` of the next session.
export closeAtEndOfSession(series string entryId, series string comment = na, series string alertMessage = na) =>
if session.islastbar and barstate.isconfirmed
strategy.close(entryId, comment, alert_message = alertMessage, immediately = true)
days(series int t) =>
int result = math.floor(t / 86400000.0 + 2440587.5)
months(series int t) =>
int result = year(t) * 12 + month(t)
numberOfPeriodsInYear(series bool useMonths) =>
int result = useMonths ? 12 : 365
numberOfPeriods(series int prevT, series int curT, series bool useMonths) =>
int result = useMonths ? (months(curT) - months(prevT)) : (days(curT) - days(prevT))
addPeriodsReturns(series int prevTime, series int xTime, series float profit, series bool useMonths, series float[] periodsReturn) =>
int periods = numberOfPeriods(prevTime, xTime, useMonths)
if periods > 0
for j = 0 to periods - 1
array.push(periodsReturn, 0)
if array.size(periodsReturn) == 0
array.push(periodsReturn, 0)
int lastIdx = array.size(periodsReturn) - 1
float periodProfit = array.get(periodsReturn, lastIdx) + profit
array.set(periodsReturn, lastIdx, periodProfit)
int result = xTime
interestRatePerPeriod(series float interestRateYearlyPercent, series bool useMonths) =>
float result = interestRateYearlyPercent / numberOfPeriodsInYear(useMonths) / 100
periodsReturnsPercent(series bool useMonths) =>
float[] periodsReturn = array.new<float>()
if strategy.closedtrades > 0
int prevTime = strategy.closedtrades.entry_time(0)
for i = 0 to strategy.closedtrades - 1
prevTime := addPeriodsReturns(prevTime, strategy.closedtrades.exit_time(i), strategy.closedtrades.profit(i), useMonths, periodsReturn)
if strategy.opentrades > 0
for i = 0 to strategy.opentrades - 1
int prevTime = strategy.opentrades.entry_time(0)
addPeriodsReturns(prevTime, time, strategy.opentrades.profit(i), useMonths, periodsReturn)
while array.size(periodsReturn) > 0
if array.get(periodsReturn, array.size(periodsReturn) - 1) == 0
array.pop(periodsReturn)
else
break
float[] periodsReturnPercent = array.new<float>()
float cum = strategy.initial_capital
for pR in periodsReturn
float pp = cum == 0 ? 0 : pR / cum
cum += pR
array.push(periodsReturnPercent, pp)
float[] result = periodsReturnPercent
downsideDeviation(float[] returns, series float targetReturn) =>
int count = 0
float sum = 0
for r in returns
float rr = math.min(0, r - targetReturn)
sum += rr * rr
count += 1
sum /= count
float result = 0 < sum ? math.sqrt(sum) : na
sortinoRatioImpl(series bool useMonths, simple float interestRate) =>
float[] periodsReturnPcnt = periodsReturnsPercent(useMonths)
float minRateOfReturn = interestRatePerPeriod(interestRate, useMonths)
float dd = downsideDeviation(periodsReturnPcnt, minRateOfReturn)
if not na(dd)
float avgPP = array.avg(periodsReturnPcnt)
float result = not na(avgPP) ? (avgPP - minRateOfReturn) / dd : na
else
na
sharpeRatioImpl(series bool useMonths, simple float interestRate) =>
float[] periodsReturnPcnt = periodsReturnsPercent(useMonths)
float minRateOfReturn = interestRatePerPeriod(interestRate, useMonths)
float stdev = array.stdev(periodsReturnPcnt)
if not na(stdev)
float avgPP = array.avg(periodsReturnPcnt)
float result = not na(avgPP) ? (avgPP - minRateOfReturn) / stdev : na
else
na
detectPeriod() =>
int start = strategy.closedtrades > 0 ? strategy.closedtrades.entry_time(0) : strategy.opentrades > 0 ? strategy.opentrades.entry_time(0) : int(na)
int end = strategy.opentrades > 0 ? time_close : strategy.closedtrades > 0 ? strategy.closedtrades.exit_time(strategy.closedtrades - 1) : int(na)
if not(na(start) or na(end))
int daysDiff = days(end) - days(start)
int monthsDiff = math.floor(daysDiff/30)
bool result = monthsDiff > 2 ? true : daysDiff > 2 ? false : bool(na)
// @function Calculates the Sortino Ratio for the trading period.
// @param interestRate (simple int/float) The benchmark "risk-free" rate of return to compare strategy risk against in the ratio.
// @param forceCalc (series bool) When `true`, forces a recalculation on each bar. Optional. The default is `false`, whereby calculations are performed only on the last bar, for efficiency.
// @returns (float) The Sortino Ratio. Useful for measuring the risk-adjusted return of a strategy, taking into account only drawdowns.
export sortinoRatio(simple float interestRate = 2.0, series bool forceCalc = false) =>
if barstate.islastconfirmedhistory or barstate.islast or forceCalc
bool useMonths = detectPeriod()
float result = not na(useMonths) ? sortinoRatioImpl(useMonths, interestRate) : na
// @function Calculates the Sharpe Ratio for the trading period.
// @param interestRate (simple int/float) The benchmark "risk-free" rate of return to compare strategy risk against in the ratio.
// @param forceCalc (series bool) When `true`, forces a recalculation on each bar. Optional. The default is `false`, whereby calculations are performed only on the last bar, for efficiency.
// @returns (float) The Sharpe Ratio. Useful for measuring the risk-adjusted return of a strategy, taking into account both run-ups and drawdowns.
export sharpeRatio(simple float interestRate = 2.0, series bool forceCalc = false) =>
if barstate.islastconfirmedhistory or barstate.islast or forceCalc
bool useMonths = detectPeriod()
float result = not na(useMonths) ? sharpeRatioImpl(useMonths, interestRate) : na
// }
// ———————————————————— Example code {
// —————————— Calculations
// Inputs for stop and limit orders in percent.
float percentStop = input.float(1.0, "Percent Stop", minval = 0.0, step = 0.25, group = "Exit Orders")
float percentTP = input.float(2.0, "Percent Limit", minval = 0.0, step = 0.25, group = "Exit Orders")
// Using the input stop/ limit percent, we can convert to ticks and use the ticks to level functions.
// This can be used to calculate the take profit and stop levels.
float sl = ticksToStopLevel (percentToTicks (percentStop))
float tp = ticksToTpLevel (percentToTicks (percentTP))
// Conditions used to reference position and determine trade bias
bool long = strategy.position_size > 0
bool short = strategy.position_size < 0
bool enterLong = long and not long [1]
bool enterShort = short and not short[1]
bool enter = enterLong or enterShort
bool exit = strategy.position_size == 0 and not (strategy.position_size == 0)[1]
// Condition to flip labels based on trade bias.
string stopStyle = short ? label.style_label_up : label.style_label_down
string limitStyle = long ? label.style_label_up : label.style_label_down
// Used to determine the exit price to draw an arrow for stop and limit exits
// and determine a color for the arrow (fuchsia for stop exits, lime for limits).
// We can use the trade bias as a multiplier to invert the condition for short trades.
exitPrice = strategy.closedtrades.exit_price(strategy.closedtrades-1)
bias = math.sign(strategy.position_size)
avg = strategy.position_avg_price
plotCol = exitPrice * bias[1] < avg[1] * bias[1] ? color.fuchsia : color.lime
// —————————— Strategy Calls
// Calculate two moving averages.
float maFast = ta.sma(close, 10)
float maSlow = ta.sma(close, 30)
// Create cross conditions for the averages.
bool longCondition = ta.crossover(maFast, maSlow)
bool shortCondition = ta.crossunder(maFast, maSlow)
// Create entries based on the cross conditions for both trades biases.
if longCondition
strategy.entry("My Long Entry Id", strategy.long)
if shortCondition
strategy.entry("My Short Entry Id", strategy.short)
// Create a hard exit level for a take profit and stop loss.
// Using the `strategy.exit` wrapper function, we can simply specify the percent stop and limit using the input variables.
exitPercent("exit", percentStop, percentTP)
// Another option is to use the "ticksToLevel" functions with the percent to ticks functions to convert percent directly to the stop or limit level:
// strategy.exit("exit", stop = ticksToStopLevel (percentToTicks (percentStop)), limit = ticksToTpLevel (percentToTicks (percentTP)), comment = "exit")
// —————————— Plots
// Plot the entry level.
plot(strategy.position_avg_price, "strategy.position_avg_price", not enter ? color.new(color.orange, 60) : na, 2, plot.style_linebr)
// Plot the exit levels.
plot(sl, "Stop2", not enter ? color.new(color.fuchsia, 60) : na, 6, plot.style_linebr)
plot(tp, "TP2" , not enter ? color.new(color.green, 60) : na, 6, plot.style_linebr)
// Plot an entry tag for both directions
plotshape(enterLong, "longCondition", shape.arrowup, location.belowbar, color.new(color.green, 30), text = "Long")
plotshape(enterShort, "shortCondition", shape.arrowdown, location.abovebar, color.new(color.fuchsia, 30), text = "Short")
// Plot and arrow for stop or limit exits at the trade exit price. We use the price and the color from above.
plotchar(exit ? exitPrice : na, "Exit Arrow", "◄", location.absolute, color.new(plotCol, 20))
// Plot labels using the percentProfit() functions to display the distance from entry to the level for the current trade
label labLimit = label.new(bar_index, sl, str.tostring(percentProfit(sl), format.percent), color = color.new(color.red, 70), style = limitStyle, textcolor = color.white)
label labStop = label.new(bar_index, tp, str.tostring(percentProfit(tp), format.percent), color = color.new(color.green, 70), style = stopStyle, textcolor = color.white)
// We delete the labels drawn one bar ago, or we end up with many labels drawn
label.delete(labLimit[1])
label.delete(labStop [1])
// Show ratios in indicator values and the Data Window.
plotchar(sortinoRatio(2.0), "Sortino", "", location.top, size = size.tiny)
plotchar(sharpeRatio(2.0), "Sharpe", "", location.top, size = size.tiny)
// }
Show all 378 lines