// 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
library("Strategy", true)
// Strategy Library
// v4, 2024.11.08
// This code was written using the recommendations from the Pine Script™ User Manual's Style guide:
// https://www.tradingview.com/pine-script-docs/writing/style-guide/
//#region ———————————————————— Library functions
// @function Converts a percentage of the average entry price or a specified price to ticks when the
// strategy has an open position.
// @param percent (series int/float) The percentage of the `from` price to express in ticks, e.g.,
// a value of 50 represents 50% (half) of the price.
// @param from (series int/float) Optional. The price from which to calculate a percentage and convert
// to ticks. The default is `strategy.position_avg_price`.
// @returns (float) The number of ticks within the specified percentage of the `from` price if
// the strategy has an open position. Otherwise, it returns `na`.
export percentToTicks(series float percent, series float from = strategy.position_avg_price) =>
strategy.position_size != 0 ? percent / 100 * from / syminfo.mintick : float(na)
// @function Calculates the price value that is a specific percentage distance away from the average
// entry price or a specified price when the strategy has an open position.
// @param percent (series int/float) The percentage of the `from` price to use as the distance. If the value
// is positive, the calculated price is above the `from` price. If negative, the result is
// below the `from` price. For example, a value of 10 calculates the price 10% higher than
// the `from` price.
// @param from (series int/float) Optional. The price from which to calculate a percentage distance.
// The default is `strategy.position_avg_price`.
// @returns (float) The price value at the specified `percentage` distance away from the `from` price
// if the strategy has an open position. Otherwise, it returns `na`.
export percentToPrice(series float percent, series float from = strategy.position_avg_price) =>
strategy.position_size != 0 ? (1 + percent / 100) * from : float(na)
// @variable Expresses the percentage of a specified price as an amount of the symbol's currency.
// @param price (series int/float) The price from which to calculate the percentage.
// @param percent (series int/float) The percentage of the `price` to calculate.
// @returns (float) The amount of the symbol's currency represented by the percentage of the specified
// `price`.
export percentToCurrency(series float price, series float percent) =>
price * percent / 100 * syminfo.pointvalue
// @function Calculates the expected profit/loss of the open position if it were to close at the
// specified `exitPrice`, expressed as a percentage of the average entry price.
//
// NOTE: This function may not return precise values for positions with multiple open trades
// because it only uses the average entry price.
// @param exitPrice (series int/float) The position's hypothetical closing price.
// @returns (float) The expected profit percentage from exiting the position at the `exitPrice`. If
// there is no open position, it returns `na`.
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 value to ticks.
// @param price (series int/float) The price to convert.
// @returns (float) The value of the `price`, expressed in ticks.
export priceToTicks(series float price) =>
price / syminfo.mintick
// @function Calculates the price value at the specified number of ticks away from the average entry
// price or a specified price when the strategy has an open position.
// @param ticks (series int/float) The number of ticks away from the `from` price. If the value is positive,
// the calculated price is above the `from` price. If negative, the result is below the `from`
// price.
// @param from (series int/float) Optional. The price to evaluate the tick distance from. The default is
// `strategy.position_avg_price`.
// @returns (float) The price value at the specified number of ticks away from the `from` price if
// the strategy has an open position. Otherwise, it returns `na`.
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 a specified number of ticks to an amount of the symbol's currency.
// @param ticks (series int/float) The number of ticks to convert.
// @returns (float) The amount of the symbol's currency represented by the tick distance.
export ticksToCurrency(series float ticks) =>
ticks * syminfo.mintick * syminfo.pointvalue
// @function Calculates a stop-loss level using a specified tick distance from the position's average
// entry price. A script can plot the returned value and use it as the `stop` argument in a
// `strategy.exit()` call.
// @param ticks (series int/float) The number of ticks from the position's average entry price to the
// stop-loss level. If the position is long, the value represents the number of ticks *below*
// the average entry price. If short, it represents the number of ticks *above* the price.
// @returns (float) The calculated stop-loss value for the open position. If there is no open position,
// it returns `na`.
export ticksToStopLevel(series float ticks) =>
ticksToPrice(-ticks * math.sign(strategy.position_size))
// @function Calculates a take-profit level using a specified tick distance from the position's average
// entry price. A script can plot the returned value and use it as the `limit` argument in a
// `strategy.exit()` call.
// @param ticks (series int/float) The number of ticks from the position's average entry price to the
// take-profit level. If the position is long, the value represents the number of ticks *above*
// the average entry price. If short, it represents the number of ticks *below* the price.
// @returns (float) The calculated take-profit value for the open position. If there is no open
// position, it returns `na`.
export ticksToTpLevel(series float ticks) =>
ticksToPrice(ticks * math.sign(strategy.position_size))
// @function Calculates the entry quantity required to risk a specified percentage of the strategy's
// current equity at a tick-based stop-loss level.
// @param stopLossTicks (series int/float) The number of ticks in the stop-loss distance.
// @param riskPercent (series int/float) The percentage of the strategy's equity to risk if a trade moves
// `stopLossTicks` away from the entry price in the unfavorable direction.
// @returns (int) The number of contracts/shares/lots/units to use as the entry quantity to risk the
// specified percentage of equity at the stop-loss level.
export calcPositionSizeByStopLossTicks(series float stopLossTicks, series float riskPercent) =>
float risk = strategy.equity * riskPercent / 100
math.floor(risk / ticksToCurrency(stopLossTicks))
// @function Calculates the entry quantity required to risk a specified percentage of the strategy's
// current equity at a percent-based stop-loss level.
// @param stopLossPercent (series int/float) The percentage of the `entryPrice` to use as the stop-loss distance.
// @param riskPercent (series int/float) The percentage of the strategy's equity to risk if a trade moves
// `stopLossPercent` of the `entryPrice` in the unfavorable direction.
// @param entryPrice (series int/float) Optional. The entry price to use in the calculation. The default is
// `close`.
// @returns (int) The number of contracts/shares/lots/units to use as the entry quantity to risk the
// specified percentage of equity at the stop-loss level.
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 for the `strategy.exit()` function designed for creating stop-loss and
// take-profit orders at percentage distances away from the position's average entry price.
//
// NOTE: This function calls `strategy.exit()` without a `from_entry` ID, so it creates exit
// orders for *every* entry in an open position until the position closes. Therefore, using
// this function when the strategy has a pyramiding value greater than 1 can lead to
// unexpected results. See the "Exits for multiple entries" section of our User Manual's
// "Strategies" page to learn more about this behavior.
// @param id (series string) Optional. The identifier of the stop-loss/take-profit orders, which
// corresponds to an exit ID in the strategy's trades after an order fills. The default is
// `"Exit"`.
// @param lossPercent (series int/float) The percentage of the position's average entry price to use as the
// stop-loss distance. The function does not create a stop-loss order if the value is `na`.
// @param profitPercent (series int/float) The percentage of the position's average entry price to use as the
// take-profit distance. The function does not create a take-profit order if the value is `na`.
// @param qty (series int/float) Optional. The number of contracts/lots/shares/units to close when an
// exit order fills. If specified, the call uses this value instead of `qtyPercent` to
// determine the order size. The exit orders reserve this quantity from the position, meaning
// other orders from `strategy.exit()` cannot close this portion until the strategy fills or
// cancels those orders. The default is `na`, which means the order size depends on the
// `qtyPercent` value.
// @param qtyPercent (series int/float) Optional. A value between 0 and 100 representing the percentage of the
// open trade quantity to close when an exit order fills. The exit orders reserve this
// percentage from the open trades, meaning other calls to this command cannot close this
// portion until the strategy fills or cancels those orders. The percentage calculation
// depends on the total size of the applicable open trades without considering the reserved
// amount from other `strategy.exit()` calls. The call ignores this parameter if the `qty`
// value is not `na`. The default is 100.
// @param comment (series string) Optional. Additional notes on the filled order. If the value is specified
// and not an empty "string", the Strategy Tester and the chart show this text for the order
// instead of the specified `id`. The default is `na`.
// @param when (series bool) Optional. A condition that determines when the function creates orders.
// The default is `true`.
// @param alertMessage (series string) Optional. Custom text for the alert that fires when an order fills. If the
// value is specified and not an empty "string", and the "Message" field of the "Create Alert"
// dialog box contains the `{{strategy.order.alert_message}}` placeholder, the alert message
// replaces the placeholder with this text. The default is `na`.
// @returns (void) The function does not return a usable value.
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 ) =>
if when
strategy.exit(id, qty = qty, qty_percent = qtyPercent, loss = math.ceil(percentToTicks(lossPercent)),
profit = math.round(percentToTicks(profitPercent)), comment = comment, alert_message = alertMessage)
// @function A wrapper for the `strategy.close_all()` function designed to close all open trades with a
// market order when the last bar in the current day's session closes. It uses the command's
// `immediately` parameter to exit all trades at the last bar's `close` instead of the `open`
// of the next session's first bar.
// @param comment (series string) Optional. Additional notes on the filled order. If the value is specified
// and not an empty "string", the Strategy Tester and the chart show this text for the order
// instead of the automatically generated exit identifier. The default is `na`.
// @param alertMessage (series string) Optional. Custom text for the alert that fires when an order fills. If the
// value is specified and not an empty "string", and the "Message" field of the "Create Alert"
// dialog box contains the `{{strategy.order.alert_message}}` placeholder, the alert message
// replaces the placeholder with this text. The default is `na`.
// @returns (void) The function does not return a usable value.
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 for the `strategy.close()` function designed to close specific open trades with a
// market order when the last bar in the current day's session closes. It uses the command's
// `immediately` parameter to exit the trades at the last bar's `close` instead of the `open`
// of the next session's first bar.
// @param entryID (series string) The entry identifier of the open trades to close. If more than one open
// trade has this entry ID, the market order from the call closes all of them.
// @param comment (series string) Optional. Additional notes on the filled order. If the value is specified
// and not an empty "string", the Strategy Tester and the chart show this text for the order
// instead of the automatically generated exit identifier. The default is `na`.
// @param alertMessage (series string) Optional. Custom text for the alert that fires when an order fills. If the
// value is specified and not an empty "string", and the "Message" field of the "Create Alert"
// dialog box contains the `{{strategy.order.alert_message}}` placeholder, the alert message
// replaces the placeholder with this text. The default is `na`.
// @returns (void) The function does not return a usable value.
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)
// @function Converts a UNIX timestamp in milliseconds to the total number of whole months since
// midnight UTC on January 1, 1970.
// @param t (series int) The UNIX timestamp to evaluate.
// @returns (int) The total number of months elapsed from January 1, 1970 to the `t` timestamp.
months(series int t) =>
int result = (year(t, "UTC") - 1970) * 12 + month(t, "UTC") - 1
// @function Calculates the number of whole calendar months between two UNIX timestamps.
// @param prevT (series int) The UNIX timestamp representing the starting time.
// @param curT (series int) The UNIX timestamp representing the ending time.
// @returns (int) The number of months between the `prevT` and `currT` timestamps.
numberOfPeriods(series int prevT, series int curT) =>
int result = (months(curT) - months(prevT))
addPeriodsReturns(series int prevTime, series int xTime, series float profit, series array<float> periodsReturn) =>
int periods = numberOfPeriods(prevTime, xTime)
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) =>
float result = interestRateYearlyPercent / 12.0 / 100.0
periodsReturnsPercent() =>
array<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), 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), periodsReturn)
while array.size(periodsReturn) > 0
if array.get(periodsReturn, array.size(periodsReturn) - 1) == 0
array.pop(periodsReturn)
else
break
array<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)
array<float> result = periodsReturnPercent
downsideDeviation(array<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(simple float interestRate) =>
array<float> periodsReturnPcnt = periodsReturnsPercent()
float minRateOfReturn = interestRatePerPeriod(interestRate)
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(simple float interestRate) =>
array<float> periodsReturnPcnt = periodsReturnsPercent()
float minRateOfReturn = interestRatePerPeriod(interestRate)
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
// @function Calculates the Sortino ratio of the strategy based on realized monthly returns.
// @param interestRate (simple int/float) Optional. The *annual* "risk-free" return percentage to compare against
// strategy returns. The default is 2, meaning it uses an annual benchmark of 2%.
// @param forceCalc (series bool) Optional. A value of `true` forces the function to calculate the ratio on the
// current bar. If the value is `false`, the function calculates the ratio only on the latest
// available bar for efficiency. The default is `false`.
// @returns (float) The Sortino ratio, which estimates the strategy's excess return per unit of
// downside volatility.
export sortinoRatio(simple float interestRate = 2.0, series bool forceCalc = false) =>
if barstate.islastconfirmedhistory or barstate.islast or forceCalc
float result = sortinoRatioImpl(interestRate)
// @function Calculates the Sharpe ratio of the strategy based on realized monthly returns.
// @param interestRate (simple int/float) Optional. The *annual* "risk-free" return percentage to compare against
// strategy returns. The default is 2, meaning it uses an annual benchmark of 2%.
// @param forceCalc (series bool) Optional. A value of `true` forces the function to calculate the ratio on the
// current bar. If the value is `false`, the function calculates the ratio only on the latest
// available bar for efficiency. The default is `false`.
// @returns (float) The Sortino ratio, which estimates the strategy's excess return per unit of
// total volatility.
export sharpeRatio(simple float interestRate = 2.0, series bool forceCalc = false) =>
if barstate.islastconfirmedhistory or barstate.islast or forceCalc
float result = sharpeRatioImpl(interestRate)
//#endregion
//#region ———————————————————— Example Code
// —————————— Inputs and calculations
// Inputs for stop-loss and take-profit percentage distances.
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")
// We can calculate the stop-loss and take-profit prices for open trades based on the input percentages and
// the trade's direction. We achieve the result here by using `percentToTicks()` to convert percentages of
// `strategy.position_avg_price` into ticks, and then passing the result to the `ticksToStopLevel()` and
// `ticksToTpLevel()` functions. These variables have non-na values only when there is an open position.
float sl = ticksToStopLevel(percentToTicks(percentStop))
float tp = ticksToTpLevel(percentToTicks(percentTP))
// Declare variables to hold position information and the latest exit price.
float direction = math.sign(strategy.position_size)
bool long = direction == 1
bool short = direction == -1
bool enterLong = long and not long [1]
bool enterShort = short and not short[1]
bool enter = enterLong or enterShort
bool exit = direction == 0 and direction[1] != 0
float avgPrice = strategy.position_avg_price
float exitPrice = strategy.closedtrades.exit_price(strategy.closedtrades - 1)
// Declare variables to reference alternating styles for stop-loss and take-profit labels.
string stopStyle = long ? label.style_label_up : label.style_label_down
string limitStyle = short ? label.style_label_up : label.style_label_down
// @variable Is `color.fuchsia` if the exit price is a less favorable value than the `avgPrice`, `color.lime` otherwise.
color arrowColor = exitPrice * direction[1] < avgPrice[1] * direction[1] ? color.fuchsia : color.lime
// Calculate two moving averages with fixed lengths for entry conditions.
float maFast = ta.sma(close, 10)
float maSlow = ta.sma(close, 30)
// Create conditions to signal a long entry when `maFast` crosses over `maSlow`, and a short entry when `maFast`
// crosses under `maSlow`.
bool longCondition = ta.crossover(maFast, maSlow)
bool shortCondition = ta.crossunder(maFast, maSlow)
// —————————— Strategy commands
// Place a market order to close any existing short position and enter a long position wen the `longCondition` occurs.
if longCondition
strategy.entry("My Long Entry Id", strategy.long)
// Place a market order to close any existing long position and enter a short position when the `shortCondition` occurs.
if shortCondition
strategy.entry("My Short Entry Id", strategy.short)
// Create stop-loss and take-profit orders for the open long or short position using the `exitPercent()` function with
// `percentStop` and `percentTP` as `lossPercent` and `profitPercent` arguments.
exitPercent("exit", percentStop, percentTP)
// —————————— Plots and drawings
// Plot the position's average entry price and exit levels as lines with visual breaks.
plot(avgPrice, "Position average entry price", enter ? na : color.new(color.orange, 60), 2, plot.style_linebr)
plot(sl, "Stop-loss", enter ? na : color.new(color.fuchsia, 60), 6, plot.style_linebr)
plot(tp, "Take-profit", enter ? na : color.new(color.green, 60), 6, plot.style_linebr)
// Plot green and fuchsia arrows for `enterLong` and `enterShort` conditions.
color longColor = color.new(color.green, 30)
color shortColor = color.new(color.fuchsia, 30)
plotshape(enterLong, "New long entry", shape.arrowup, location.belowbar, longColor, 0, "Long", longColor)
plotshape(enterShort, "New short entry", shape.arrowdown, location.abovebar, shortColor, 0, "Short", shortColor)
// Plot an arrow character for SL or TP exits at the `exitPrice`, colored using the `arrowColor`.
plotchar(exit ? exitPrice : na, "Exit Arrow", "◄", location.absolute, color.new(arrowColor, 20))
if barstate.islastconfirmedhistory or barstate.islast
// Initialize labels to display stop-loss and take-profit percentages on the last bar.
var label stopLabel = label.new(na, na, "", color = color.new(color.red, 70), textcolor = color.white)
var label limitLabel = label.new(na, na, "", color = color.new(color.green, 70), textcolor = color.white)
// Update the `x` and `y` values of the `stopLabel` and `limitLabel` to anchor them to the `sl` and `tp` levels
// at the latest `bar_index`.
stopLabel.set_xy(bar_index, sl)
limitLabel.set_xy(bar_index, tp)
// Update the style of the labels based on the `stopStyle` and `limitStyle`.
stopLabel.set_style(stopStyle)
limitLabel.set_style(limitStyle)
// Update the `text` properties of the label with the formatted `percentProfit()` of the `sl` and `tp` to confirm
// that the calculated results still match the specified input percentages.
stopLabel.set_text(str.tostring(percentProfit(sl), format.percent))
limitLabel.set_text(str.tostring(percentProfit(tp), format.percent))
// Plot the Sortino and Sharpe ratios with 2% annual benchmarks in all locations except for the chart pane.
ratioDisplay = display.all - display.pane
plot(sortinoRatio(2.0), "Sortino ratio", display = ratioDisplay)
plot(sharpeRatio(2.0), "Sharpe ratio", display = ratioDisplay)
//#endregion
Show all 456 lines