Extended Floating Profit & Loss - Backtesting data utility For ThinkOrSwim

Status
Not open for further replies.
This thread has exhausted its substantive discussion of this indicator so it has been locked


This thread is still available for reading.
If looking for specific information in this thread,
here is a great hack for searching many-paged threads.​

One of the areas that the strategies & default Floating P&L really fell short was in the P&L metrics provided when running the "show report" for a strategy. So I've been working on an extended P&L study that includes some more useful metrics and are updated dynamically, as signals from your strategy are triggered. The goal is to allow for quicker & more comprehensive backtest data on the fly when assessing the performance of a TOS strategy without having to deal with the process of exporting, importing, formatting, the report data . There's still a bunch of work to do with this, but I think at this stage it'd be great to share with the community to see if anyone has any other ideas or have modifications they'd like to contribute.

Caveats:
The downside to this script is that because it uses the Entry() function it cannot be added as a lower indicator. Therefore it's used on the full chart, requiring us to hide the actual price candles for the underlying ticker. Additionally, the code itself has to be appended to the end of the script your attempting to test. This is probably the most cumbersome part of implementing this script but once again it's all due to the lack of public api access to the Entry() function from within a lower study.

Additionally, the current version of the script is pretty resource intensive, so I'd recommend creating a copy of your strategy with the script appended to it for testing purposes only (reserving the your original script WITHOUT the extended P&L for actual live trading).

Usage:
I've been utilizing the script with two approaches - without the FPL histogram & with the FPL histogram. When displaying with the histogram, I've been using a grid in order to keep the chart as clean as possible. After some experimenting, I recommend the first option (without the FPL histogram) as it's the easiest to setup and clearest to read when viewing the strategy chart :

Without FPL histogram:
- Append the script directly to your strategy then in the strategy properties, set the hide price setting to No and the hide fpl setting to Yes. Backtest your strategy as you normally would.
GW4gQMq.png


With FPL histogram:
- Setup a two panel grid with identical timeframes and aggregation periods for a single ticker on both panels. Applying the strategy to be tested on the first chart & a copy of the strategy with the extended P&L script appended to it on the 2nd chart.
M7GCYpS.png


Current Features:
  • Total trades
  • Win count, loss count, win rate & win/loss ratio
  • Avg return per trade, per win & per loss
  • Highest Return
  • Lowest Return
  • Max Drawdown (Peak to Valley calculation method)
  • Lowest P&L Value
  • Highest P&L Value
  • Last Entry Price
  • Current trade return percent
  • Day's profit
  • Total Profit
  • Customizable target win/loss ratio & win rate
  • Ability to enable/disable FPL histogram and underlying strategy prices

Work in Progress Features:

Features that are planned for a future release & are already in development.
  • Sharpe Ratio
  • Sortino Ratio
  • Calmar Ratio
  • Avg trade duration
  • Drawdown duration
  • Figure out workaround for using Entry() in a lower indicator in order to completely decouple the script from the associated strategy
Planned Features - HELP WANTED!!!:
Feature requests or ideas submitted by the community that are planned for a future release but not yet started. If any of the below features are something that you feel you could contribute time towards working on, please post a comment in the thread below.

Current Version: 1.2

Links:
Study: https://tos.mx/20Tk8H2
Example Strategy: https://tos.mx/VrWjG4c

Changelog:
Code:
v1.2
- Changelog in progress

v1.1
Study: https://tos.mx/ySvkDhe
Example Strategy: https://tos.mx/w7vxxNs
- add HighestReturn & LowestReturn values;
- add additional settings to disable the FPL histogram and strategy price plots;
- update label order;
- general code cleanup;

v1.0
- initial release;

Code:
Python:
############################
# FPL Extended
# Extended Floating P&L study.
# Author: Eddielee394
# Version: 1.2
# inspired by FPL Dashboard script developed by Mobius
#
############################

#Hint: An extended floating P&L study to be used alongside TOS strategies for measuring hypothetical strategy performance.\nThis is a work in progress.  And may contain some bugs or other programming issues.

############################
# Instructions
# - Due to limitations with the thinkscript public api, this specific script must be added to a "strategy" study.
#   Generally best practice is to append this script to the end of your custom strategy (ensuring it runs AFTER the
#   `AddOrder()` function is called from the strategy).  A better method would be to use as a lower study but unless
#    a workaround is implemented to handle the `Entry()` function in a lower study, it can only be applied to upper strategies.
#
# - the script uses the `HidePrice()` function which will hide the actual price candles within the upper study,
#   only displaying the FPL histogram.
#
############################


############################
#    Metrics              
#  - Active Trade return %
#  - Entry Count          
#  - Winning Entry Count  
#  - Win rate             
#  - Avg return           
#  - avg win              
#  - avg loss             
#  - peak to valley dd    
#  - largest equity dd    
#  - P&L low              
#  - P&L high             
#  - Highest return     
#  - Lowest return      
############################

############################
#     Todo:               
# - Sharpe Ratio     
# - Sortino Ratio     
# - Calmar Ratio     
# - Avg trade          
#   duration             
# -Buy/hold comparison    
############################


#Globals
def nan = Double.NaN;
def bn = if !IsNaN(close) and !IsNaN(close[1]) and !IsNaN(close[-1]) then BarNumber() else bn[1];

#Inputs
input fplBegin = 0000;
#hint fplBegin: start time: the time in which then dailyHighLow profit should consider the day start.  Recommended value is 0000.

input fplTargetWinLoss = .50;
#hint fplTargetWinLoss: sets the target winlossRatio (in percent) which determines display colors of the W/L label.

input fplTargetWinRate = 1;
#hint fplTargetWinRate: sets the target winRate (float) which determines display colors of the WinRate label;

input fplHidePrice = no;
#hint fplHidePrice: hide's the underlying price graph. \nDefault is no.

input fplHideFPL = yes;
#hint fplHideFPL: hide's the underlying P&L graph.\nDefault is yes.

input fplShowEntryBubbles = no;
#hint fplShowEntryBubbles: display bubbles on the FPL showing the entry and exit P&L values

input fplEnableDebugging = no;
#hint fplEnableDebugging: displays various debugging labels and chart bubbles. \nIt's recommended to hide the price chart & set the fpl hide fpl setting to yes, when enabling.

#temp input var references
def begin = fplBegin;
def targetWinLoss = fplTargetWinLoss;
def targetWinRate = fplTargetWinRate;
def hidePrice = fplHidePrice;
def hideFPL = fplHideFPL;
def showEntryBubbles = fplShowEntryBubbles;
def enableDebugging = fplEnableDebugging;

#hide chart candles
HidePricePlot(hidePrice);

#Plot default Floating P&L
plot FPL = FPL();
FPL.SetPaintingStrategy(PaintingStrategy.SQUARED_HISTOGRAM);
FPL.DefineColor("Positive and Up", Color.GREEN);
FPL.DefineColor("Positive and Down", Color.DARK_GREEN);
FPL.DefineColor("Negative and Down", Color.RED);
FPL.DefineColor("Negative and Up", Color.DARK_RED);
FPL.AssignValueColor(if FPL >= 0
                            then if FPL > FPL[1]
                            then FPL.Color("Positive and Up")
                            else FPL.Color("Positive and Down")
                            else if FPL < FPL[1]
                            then FPL.Color("Negative and Down")
                            else FPL.Color("Negative and Up"));
FPL.SetHiding(hideFPL);

plot ZeroLine = if IsNaN(close)
                then nan
                else 0;
ZeroLine.SetDefaultColor(Color.GRAY);
ZeroLine.SetHiding(hideFPL);

#Global Scripts
script calculateDrawdown {
    input plLow = 1;
    input plHigh = 1;

    def _drawdown = if plHigh == 0
                   then 0 #handles the divide by zero error
                   else (plLow - plHigh) / plHigh;
    plot calculateDrawdown = _drawdown;
}

script incrementValue {
    input condition = yes;
    input increment =  1;
    input startingValue = 0;

    def _value = CompoundValue(1,
                 if condition
                 then _value[1] + increment
                 else _value[1], startingValue);

    plot incrementValue = _value;
}
;

script getDurationInMins {
    input gdimBarCount = 1;

    #get the aggregation period (MS per bar)
    def aggPeriod = GetAggregationPeriod();

    #multiply length of bars by aggPeriod to determine total milliseconds then convert to minutes
    def _getDurationInMins = Floor((gdimBarCount * aggPeriod) / 60000);
    plot getDurationInMins = _getDurationInMins;
}

script formatDuration {
    input fdBarCount = 1;
    def _fdDuration = getDurationInMins(fdBarCount);
    def _formatDuration = if _fdDuration >= 60 and _fdDuration < 1440 #hours but not days
                         then 1
                         else if _fdDuration >= 1440 #days
                         then 2
                         else 0;

    plot formatDuration = _formatDuration;
}

script calculateDuration {
    input cdBarCount = 1;
    def _cdDurationFormat = formatDuration(cdBarCount);
    def _cdDuration = getDurationInMins(cdBarCount);
   
    #if minutes > hour convert to hour, else if minutes > day, then convert to days
    def _calculateDuration = if _cdDurationFormat == 1
                             then _cdDuration / 60 #convert to hours if greater than 60min but less than a day (1440)
                             else if  _cdDurationFormat == 2
                             then Ceil(_cdDuration / 1440) #convert to days if greater than 1440mins
                             else _cdDuration; #fallback to minutes

    plot calculateDuration = _calculateDuration;
}

# Entry Calculations.  Note: Only parses on a Strategy Chart
def entry = EntryPrice();

def entryPrice = if !IsNaN(entry)
                 then entry
                 else entryPrice[1];

def hasEntry = !IsNaN(entry);

def isNewEntry = entryPrice != entryPrice[1];

#is active trade
def Active = if SecondsTillTime(begin) == 0 and
                SecondsFromTime(begin) == 0
             then 1
             else 0;

def highFPL = HighestAll(FPL);
def lowFPL = LowestAll(FPL);

def fplreturn = (FPL - FPL[1]) / FPL[1];
def cumsum = Sum(fplreturn);

def highBarNumber = CompoundValue(1, if FPL == highFPL
                                     then bn
                                     else highBarNumber[1], 0);

def lowBarNumber = CompoundValue(1, if FPL == lowFPL
                                    then bn
                                    else lowBarNumber[1], 0);


#Win/Loss ratios
def entryBarsTemp = if hasEntry
                    then bn
                    else nan;

def entryBarNum = if hasEntry and isNewEntry
                  then bn
                  else entryBarNum[1];

def isEntryBar = entryBarNum != entryBarNum[1];

def entryBarPL = if isEntryBar
                 then FPL
                 else entryBarPL[1];

def exitBarsTemp = if !hasEntry
                   and bn > entryBarsTemp[1]
                   then bn
                   else nan;

def exitBarNum = if !hasEntry and !IsNaN(exitBarsTemp[1])
                 then bn
                 else exitBarNum[1];

def isExitBar = exitBarNum != exitBarNum[1];

def exitBarPL = if isExitBar
                then FPL
                else exitBarPL[1];

def entryReturn = if isExitBar then exitBarPL - exitBarPL[1] else entryReturn[1];
def isWin = if isExitBar and entryReturn >= 0 then 1 else 0;
def isLoss = if isExitBar and entryReturn < 0 then 1 else 0;
def entryReturnWin = if isWin then entryReturn else entryReturnWin[1];
def entryReturnLoss = if isLoss then entryReturn else entryReturnLoss[1];
def entryFPLWins = if isWin then entryReturn else 0;
def entryFPLLosses = if isLoss then entryReturn else 0;
def entryFPLAll = if isLoss or isWin then entryReturn else 0;


#Counts
def entryCount = incrementValue(entryFPLAll);
def winCount = incrementValue(isWin);
def lossCount = incrementValue(isLoss);

def highestReturn = if entryReturnWin[1] > highestReturn[1]
                    then entryReturnWin[1]
                    else highestReturn[1];

def lowestReturn = if entryReturnLoss[1] < lowestReturn[1]
                   then entryReturnLoss[1]
                   else lowestReturn[1];


def winRate = winCount / lossCount;
def winLossRatio = winCount / entryCount;
def avgReturn = TotalSum(entryFPLAll) / entryCount;
def avgWin = TotalSum(entryFPLWins) / winCount;
def avgLoss = TotalSum(entryFPLLosses) / lossCount;

#Drawdown
def lowestLowBarNumber = HighestAll(if FPL == lowFPL then bn else 0);
def highestHighBarNumber = HighestAll(if FPL == highFPL and FPL != FPL[1] then bn else 0);
def hasPrevLow = lowestLowBarNumber < highestHighBarNumber;

def isPeak = FPL > Highest(FPL[1], 12) and FPL > Highest(FPL[-12], 12);
def isTrough = FPL < Lowest(FPL[1], 12) and FPL < Lowest(FPL[-12], 12);
def _peak = if isPeak then FPL else nan;
def _trough = if isTrough then FPL else nan;
def peak = if !IsNaN(_peak) then FPL else peak[1];
def trough = if !IsNaN(_trough) then FPL else trough[1];
def peakBN = if isPeak then bn else peakBN[1];
def troughBN = if isTrough then bn else troughBN[1];

def ptvDrawdown = if !hasPrevLow then calculateDrawdown(lowFPL, highFPL) else ptvDrawdown[1];
def equityDrawdown = if isTrough and trough < peak then trough - peak else equityDrawdown[1];
def equityDrawdownPercent = if isTrough and trough < peak then calculateDrawdown(trough, peak) else equityDrawdownPercent[1];
def largestEquityDrawdown = LowestAll(equityDrawdown);
def largestEquityDrawdownPercent = LowestAll(equityDrawdownPercent);

def drawdown = if hasPrevLow
               then largestEquityDrawdownPercent
               else ptvDrawdown;

# Drawdown Durations
def equityDrawdownLength = if bn >= peakBN and bn <= troughBN
                           then troughBN - peakBN
                           else equityDrawdownLength[1];

def ptvDrawdownLength = if bn >= highestHighBarNumber and bn <= lowestLowBarNumber
                        then lowestLowBarNumber - highestHighBarNumber
                        else ptvDrawdownLength[1];

def equityDrawdownDuration = calculateDuration(HighestAll(equityDrawdownLength));
def equityDrawdownDurationFormat = formatDuration(HighestAll(equityDrawdownLength));
def ptvDrawdownDuration = calculateDuration(ptvDrawdownLength);

def ptvDrawdownDurationFormat = formatDuration(ptvDrawdownLength);

#Daily profit
def Midnight = if Active then FPL else Midnight[1];
def DaysProfit = FPL - Midnight;

#Plots

AddChartBubble(!hideFPL and showEntryBubbles and isEntryBar, FPL, "Entry: " + entryBarPL + " | " + bn, Color.WHITE);
AddChartBubble(!hideFPL and showEntryBubbles and isExitBar, FPL, "Exit: " + exitBarPL,
               color = if isWin
                       then Color.LIGHT_GREEN
                       else if isLoss
                       then Color.DARK_RED
                       else Color.GRAY,
               up = no
              );

#Labels

AddLabel(yes,
         text = "LastEntry: " + AsPrice(entryPrice)
         );

AddLabel(hasEntry,
         text = "Current Trade % Return:  " + AsPercent(cumsum),
         color = if cumsum > 0
         then Color.GREEN
         else Color.RED
        );

AddLabel(yes,
         text = "Total Trades: " + entryCount,
         color = Color.WHITE
         );

AddLabel(yes,
         text = "WinCount: " + winCount +
                " | LossCount: " + lossCount +
                " | WinRate: " + winRate,
         color = if winRate >= targetWinRate
                 then Color.GREEN
                 else Color.RED
         );

AddLabel(yes,
         text = "W/L: " + AsPercent(winLossRatio),
         color = if winLossRatio > targetWinLoss
                 then Color.GREEN
                 else Color.RED
         );

AddLabel(yes,
        text = "HighestReturn: " +  AsDollars(highestReturn),
        color = if highestReturn > 0
                then Color.GREEN
                else Color.RED
        );

AddLabel(yes,
        text = "LowestReturn: " +  AsDollars(lowestReturn),
        color = if lowestReturn > 0
                then Color.GREEN
                else Color.RED
        );

AddLabel(yes,
         text = "AvgReturn: " + AsDollars(avgReturn) +
                " | AvgWin: " + AsDollars(avgWin) +
                " | AvgLoss: " + AsDollars(avgLoss),
         color = if avgReturn >= 0
                 then Color.LIGHT_GREEN
                 else Color.RED
         );

AddLabel(yes,
        text = "PeakToValley DD: " +  AsPercent(drawdown) +
               " | Duration: " + ptvDrawdownDuration +
                if ptvDrawdownDurationFormat == 1
                then " hours"
                else if ptvDrawdownDurationFormat == 2
                then " days"
                else " mins" ,
        color = if drawdown > 0
                then Color.GREEN
                else Color.RED
        );


AddLabel(largestEquityDrawdown < 0,
        text = "Largest Equity DD: " +  AsDollars(largestEquityDrawdown) +
               " | Duration: " + equityDrawdownDuration +
                if equityDrawdownDurationFormat == 1
                then " hours"
                else if equityDrawdownDurationFormat == 2
                then " days"
                else " mins",
        color = if largestEquityDrawdown > 0
                then Color.GREEN
                else Color.RED
        );

AddLabel(yes,
         text = "P&L High" +
                (if enableDebugging
                then " at bar " + highBarNumber
                else "") +
                ":  " + AsDollars(highFPL),
        color = Color.GREEN
       );

AddLabel(yes,
         text = "P&L Low" +
                (if enableDebugging
                then " at bar " + lowBarNumber
                else "") +
                ":  " + AsDollars(lowFPL),
        color = Color.RED
       );

AddLabel(yes,
         text = "Days Profit: $" + DaysProfit,
         color = if DaysProfit > 0
                 then Color.GREEN
                 else Color.RED
        );

AddLabel(yes,
         text = "Total Profit: " + AsDollars(FPL),
         color = if FPL > 0
                 then Color.GREEN
                 else Color.RED
        );

#debugging

#peaks & troughs
AddChartBubble(enableDebugging and isPeak, FPL,
               text = "FPL: " + FPL
                      + " | Peak: " + peak
                      + " | Trough: " + trough[-1]
                      + " | Drawdown: " + AsPercent(calculateDrawdown(trough, peak))
                      + " | PeakBN: " + peakBN
                      + " | BarNumber: " + bn,
               color = Color.LIME
              );

AddChartBubble(enableDebugging and isTrough, FPL,
               text = "FPL: " + FPL
                      + " | Peak: " + peak
                      + " | Trough: " + trough
                      + " | Drawdown: " + AsPercent(calculateDrawdown(trough, peak))
                      + " | TroughBN: " + troughBN
                      + " | BarNumber: " + bn,
              color = Color.LIGHT_RED,
              up = no
             );

AddVerticalLine(enableDebugging and isEntryBar,
                text = "EntryBarNum: " + entryBarNum
                       + " | ExitBarNum: " + exitBarNum[-1]
                       + " | BarNumber: " + bn,
                color = Color.WHITE
);

AddVerticalLine(enableDebugging and isExitBar,
                text =  "EntryBarNum: " + entryBarNum[1]
                        + " | ExitbarNum: " + exitBarNum
                        + " | BarNumber: " + bn
                        + " | EntryReturn: " + entryReturn,
                color = if isWin
                        then Color.LIGHT_GREEN
                        else if isLoss
                        then Color.LIGHT_RED
                        else Color.LIGHT_GREEN
);
 
Last edited by a moderator:
Thank you for sharing this. Such a useful tool from the look of it. Will try it out and provide feedback (if there’s any) :)
 
awesome work @eddielee394 thanks again for leading this. would you be open to other features suggestions. I was thinking of working on a "stop library" some come stop types to exit positions such as trailing stop, fix price, fix percentage, ma cross overs and then you can test various stop or exit conditions on the strategy and see the effects of the stats. I just haven't had the time. perhaps we can all contribute.
 
@diazlaz Yeah, definitely open to contributions. It's opensource, as far as I'm concerned :) .

Ideally, I'd like to keep this first v1.x version focused on optimizing the current performance metrics and adding a few new ones. Also, I think the code itself needs some housekeeping done along with squashing a few bugs. There's a bug I just found with the Peak to Valley drawdown calculations when the lowestLow is 0 and also precedes the first bar in the FPL data set. It ends up breaking the label UI. There's probably other edge cases that may end up causing issues as well, so i'd like to catch as many as possible since this may potentially play a significant role in the decision process for users on whether a strategy is worth using or not. So I want to make sure all the numbers are as accurate as they can be.

I like the idea of a "stop library", but I'm not sure if it's something that fits with the purpose of this specific script. It's more focused on calculating & displaying performance data and not so much managing the trades themselves. I feel like that's more along the lines of the strategy's responsibility, especially when the stop conditions become more complex & go beyond just static values like basic trailing stops, etc. Plus, it's bad enough that due to the limitations with thinkscript we're restricted to hardcoding this into each of the strategies we want to test. So for the sake of portability across different strategies, we should be careful how tightly we couple the logic with any one specific strategy.
 
@diazlaz Yeah, definitely open to contributions. It's opensource, as far as I'm concerned :) .

Ideally, I'd like to keep this first v1.x version focused on optimizing the current performance metrics and adding a few new ones. Also, I think the code itself needs some housekeeping done along with squashing a few bugs. There's a bug I just found with the Peak to Valley drawdown calculations when the lowestLow is 0 and also precedes the first bar in the FPL data set. It ends up breaking the label UI. There's probably other edge cases that may end up causing issues as well, so i'd like to catch as many as possible since this may potentially play a significant role in the decision process for users on whether a strategy is worth using or not. So I want to make sure all the numbers are as accurate as they can be.

I like the idea of a "stop library", but I'm not sure if it's something that fits with the purpose of this specific script. It's more focused on calculating & displaying performance data and not so much managing the trades themselves. I feel like that's more along the lines of the strategy's responsibility, especially when the stop conditions become more complex & go beyond just static values like basic trailing stops, etc. Plus, it's bad enough that due to the limitations with thinkscript we're restricted to hardcoding this into each of the strategies we want to test. So for the sake of portability across different strategies, we should be careful how tightly we couple the logic with any one specific strategy.
agreed. thanks perhaps we can tackle it as a sub module or the development of a set of patterns that can be integrated as needed to keep it loosely coupled. look forward to the upcoming releases and i'm sure you will have great support here!
 
Hi @eddielee394, another feature for consideration, is to capture the hold PL front the first bar to last, to determine the delta between the strategy itself and the PL if you would have just bought the stock in the beginning and display the strategy PL and the hold PL.
 
Hi @eddielee394, another feature for consideration, is to capture the hold PL front the first bar to last, to determine the delta between the strategy itself and the PL if you would have just bought the stock in the beginning and display the strategy PL and the hold PL.
I love this idea! I'll update the todo list with it on the original post just in case someone gets motivated and wants to tackle it before i get a chance to.

I found an issue with the highest/lowest return calculations. Not quite sure how to resolve it yet, but I created another post in the Questions forum hoping someone may be able to shed some light on what's happening.

Just rolled out another minor release: v1.2. Added some new label UI features & settings. Also fixed a few known bugs, specifically with the Highest/Lowest return labels displaying the incorrect values on charts with more than 2000 bars.

Code & links in the OP have been updated. Will post a full changelog & some new screenshots this weekend (might even get that buy/hold comparison feature added by then as well).
 
Last edited:
I've been using your code with a lot of my stuff now lately, but needed fractional win and loss, edge, expectancy and SQN. Since you shared, maybe the following will further you along if not there yet.


Code:
# mcdon's additions 3/14/2020
# Fractional Win
# Fractional Loss
# edge
# Expectancy
# SQN
#  use member Zachc  sheet for the above formulas-  https://usethinkscript.com/threads/how-to-export-and-process-thinkorswim-backtesting-data.679/
# Sortino
# Sharpe
# CAGR    note: may need work
# used https://indexswingtrader.blogspot.com/2013/10/how-to-time-market-stochastics-rulez.html#disqus_thread  for the above
# Calmar  note:  may need work
script Sharpe {
    input price = close;
    def length = 63;
    def AvgPrice63 = Average(price, length);
    def expectedReturn = (price / price[length]) - 1;
# riskfreeReturn: 13-week treasuries as a proxy for a risk free return
  ##  def riskfreeReturn = if !IsNaN( close)
##then Average(close, length) / 100
    def riskfreeReturn = if !IsNaN( close("IRX"))
then Average(close("IRX"), length) / 100
else riskfreeReturn[1];
    def effectiveReturn = expectedReturn - riskfreeReturn;
    def AvgVariance63 = ( fold i = 0 to length with AV do AV + Sqr(GetValue(price, i) - AvgPrice63) ) / length;
    def sd63 = Sqrt(AvgVariance63);
    def volatility63 = sd63 / AvgPrice63;
    plot Sharpe = Sqrt(length / 252) * (effectiveReturn / volatility63); # annual
}
script Sortino {
    input price = close;
    def length = 63;
    def AvgPrice63 = Average(price, length);
    def expectedReturn = (price / price[length]) - 1;
# riskfreeReturn: 13-week treasuries as a proxy for a risk free return
##   def riskfreeReturn = if !IsNaN( close)
##then Average(close, length) / 100
  def riskfreeReturn = if !IsNaN( close("IRX"))
then Average(close("IRX"), length) / 100
else riskfreeReturn[1];
    def effectiveReturn = expectedReturn - riskfreeReturn;
    def AvgUnderperf63 = ( fold n = 0 to length with AUP do AUP + if GetValue(price, n) - AvgPrice63 > 0
then 0 else Sqr(GetValue(price, n) - AvgPrice63) ) / length;
    def sdUnderperf63 = Sqrt(AvgUnderperf63);
    def volatilityUP63 = sdUnderperf63 / AvgPrice63;
    plot Sortino = Sqrt(length / 252) * (effectiveReturn / volatilityUP63); # annual
}

## begin
def winRaten = winCount / entryCount;
Def FractionalWin = avgWin/fplBegin;
Def FractionalLoss = avgLoss/fplBegin;
Def Edge =(FractionalWin* winLossRatio)-FractionalLoss *(1-winLossRatio);
Def Expectancy =((winRaten  - avgwin  )-((winRaten )*avgLoss))*-1;
## Trading expectancy is a statistic that combines the win rate and reward:risk ratio. It provides a dollar figure for ##the expected profit or loss on each trade. Positive is good, and shows that the trading system is producing ##profitable results. A negative number indicates the strategy is, or will, lose money.
## need SQN
## SQN Scale
## 1.6 to 1.9    (poor but tradeable)
## 2.0 to 2.4    (average)
## 2.5 to 2.9    (good)
## 3 to 5         (excellent)
## 5 to 6.9       (superb)
## 7 and above (holy grail)
  def FPLvariance =  TotalSum(entryFPLAll) - avgReturn;
                def FPLsqrt=  totalsum( if entryFPLAll  then power(FPLvariance ,2) else 0);
                def FPLdiv =FPLsqrt/( entryCount-1);
                def FPLdeviation = sqrt(FPLdiv);               
                Def SQN =1+sqrt(entryCount*Expectancy)/FPLdeviation;
Input additions = yes;
def openFPL = if hasEntry  then fpl  else 0;
def NLV = if !IsNaN(close) then fpl + openFPL else Double.NaN;
def RoR = 100 * (NLV -fplBegin) / fplBegin;
# CAGR calculation
def init = CompoundValue(1, if entry && !entry[1] then 1 else init[1], 0);
def initBar = CompoundValue(1, if init and !init[1] then BarNumber() else initBar[1], 0);
def endBar = BarNumber();
def chartAgg = GetAggregationPeriod();
def nBarYear = if chartAgg == AggregationPeriod.WEEK then 52 else if chartAgg == AggregationPeriod.DAY then 252 else 0;
def years = (endBar - initBar) / nBarYear;
def CAGR = 100 * (Power( (NLV /fplBegin), (1 / years) )) - 100;
def Sharpe = Sharpe(RoR);
def Sortino = Sortino(RoR);
###CR = CAR / MD. where. CR = Calmar Ratio. CAR = Compounded Annual Return.AsPercent(calculateDrawdown(trough, peak)
def Calmar = round(avgReturn/drawdown);
## end
AddLabel(additions,
         text = " FractionalWin:  " + asdollars(FractionalWin)+
         " |  FractionalLoss " +   asdollars(FractionalLoss),  
         color = color.WHITE
);
AddLabel(additions,
         text = "Edge : " + AsPercent(Edge)+
          " |  Expectancy " +   asdollars(Expectancy)+
           " |  SQN " +  (SQN),  
        color =  color.white
         );

AddLabel(additions,
         text = "Sharpe: " + round(Sharpe)+
          " |  Sortino " +  round(Sortino )+
          " |  CAGR  " +   round(CAGR)+
          " |  Calmar " +  round(Calmar),  
        color =  color.white
         );
 
Last edited:
I've been using your code with a lot of my stuff now lately, but needed fractional win and loss, edge, expectancy and SQN. Since you shared, maybe the following will further you along if not there yet.


Code:
# mcdon's additions 3/14/2020
# Fractional Win
# Fractional Loss
# edge
# Expectancy
# SQN
#  use member Zachc  sheet for the above formulas-  https://usethinkscript.com/threads/how-to-export-and-process-thinkorswim-backtesting-data.679/
# Sortino
# Sharpe
# CAGR    note: may need work
# used https://indexswingtrader.blogspot.com/2013/10/how-to-time-market-stochastics-rulez.html#disqus_thread  for the above
# Calmar  note:  may need work
script Sharpe {
    input price = close;
    def length = 63;
    def AvgPrice63 = Average(price, length);
    def expectedReturn = (price / price[length]) - 1;
# riskfreeReturn: 13-week treasuries as a proxy for a risk free return
  ##  def riskfreeReturn = if !IsNaN( close)
##then Average(close, length) / 100
    def riskfreeReturn = if !IsNaN( close("IRX"))
then Average(close("IRX"), length) / 100
else riskfreeReturn[1];
    def effectiveReturn = expectedReturn - riskfreeReturn;
    def AvgVariance63 = ( fold i = 0 to length with AV do AV + Sqr(GetValue(price, i) - AvgPrice63) ) / length;
    def sd63 = Sqrt(AvgVariance63);
    def volatility63 = sd63 / AvgPrice63;
    plot Sharpe = Sqrt(length / 252) * (effectiveReturn / volatility63); # annual
}
script Sortino {
    input price = close;
    def length = 63;
    def AvgPrice63 = Average(price, length);
    def expectedReturn = (price / price[length]) - 1;
# riskfreeReturn: 13-week treasuries as a proxy for a risk free return
##   def riskfreeReturn = if !IsNaN( close)
##then Average(close, length) / 100
  def riskfreeReturn = if !IsNaN( close("IRX"))
then Average(close("IRX"), length) / 100
else riskfreeReturn[1];
    def effectiveReturn = expectedReturn - riskfreeReturn;
    def AvgUnderperf63 = ( fold n = 0 to length with AUP do AUP + if GetValue(price, n) - AvgPrice63 > 0
then 0 else Sqr(GetValue(price, n) - AvgPrice63) ) / length;
    def sdUnderperf63 = Sqrt(AvgUnderperf63);
    def volatilityUP63 = sdUnderperf63 / AvgPrice63;
    plot Sortino = Sqrt(length / 252) * (effectiveReturn / volatilityUP63); # annual
}

## begin
def lossRate = lossCount /winCount;
Def FractionalWin = avgWin/fplBegin;
Def FractionalLoss = avgLoss/fplBegin;
Def Edge =(FractionalWin* winLossRatio)-FractionalLoss *(1-winLossRatio);
### expectacy may need work. each time showing negative when should be positive
Def Expectancy =(winLossRatio  - avgWin  )-((1-winLossRatio)*avgLoss);
## Trading expectancy is a statistic that combines the win rate and reward:risk ratio. It provides a dollar figure for ##the expected profit or loss on each trade. Positive is good, and shows that the trading system is producing ##profitable results. A negative number indicates the strategy is, or will, lose money.
## need SQN
## SQN Scale
## 1.6 to 1.9    (poor but tradeable)
## 2.0 to 2.4    (average)
## 2.5 to 2.9    (good)
## 3 to 5         (excellent)
## 5 to 6.9       (superb)
## 7 and above (holy grail)
  def FPLvariance =  TotalSum(entryFPLAll) - avgReturn;
                def FPLsqrt=  totalsum( if entryFPLAll  then power(FPLvariance ,2) else 0);
                def FPLdiv =FPLsqrt/( entryCount-1);
                def FPLdeviation = sqrt(FPLdiv);               
                Def SQN =1+(entryCount*avgLoss)/FPLdeviation;
Input additions = yes;
def openFPL = if hasEntry  then fpl  else 0;
def NLV = if !IsNaN(close) then fpl + openFPL else Double.NaN;
def RoR = 100 * (NLV -fplBegin) / fplBegin;
# CAGR calculation
def init = CompoundValue(1, if entry && !entry[1] then 1 else init[1], 0);
def initBar = CompoundValue(1, if init and !init[1] then BarNumber() else initBar[1], 0);
def endBar = BarNumber();
def chartAgg = GetAggregationPeriod();
def nBarYear = if chartAgg == AggregationPeriod.WEEK then 52 else if chartAgg == AggregationPeriod.DAY then 252 else 0;
def years = (endBar - initBar) / nBarYear;
def CAGR = 100 * (Power( (NLV /fplBegin), (1 / years) )) - 100;
def Sharpe = Sharpe(RoR);
def Sortino = Sortino(RoR);
###CR = CAR / MD. where. CR = Calmar Ratio. CAR = Compounded Annual Return.AsPercent(calculateDrawdown(trough, peak)
def Calmar = round(avgReturn/drawdown);
## end
AddLabel(additions,
         text = " FractionalWin:  " + asdollars(FractionalWin)+
         " |  FractionalLoss " +   asdollars(FractionalLoss),  
         color = color.WHITE
);
AddLabel(additions,
         text = "Edge : " + AsPercent(Edge)+
          " |  Expectancy " +   asdollars(Expectancy)+
           " |  SQN " +  (SQN),  
        color =  color.white
         );

AddLabel(additions,
         text = "Sharpe: " + round(Sharpe)+
          " |  Sortino " +  round(Sortino )+
          " |  CAGR  " +   round(CAGR)+
          " |  Calmar " +  round(Calmar),  
        color =  color.white
         );
Getting a lot of error msg's around line 65
 
@eddielee394 Thank you for a great tool!

Is there any chance you can have it where it calculate based on one (or user input) futures contract (ES, NQ, CL, YM, RTY) and tick value of each instead of backtesting futures like stocks.
 
@eddielee394 Thank you for a great tool!

Is there any chance you can have it where it calculate based on one (or user input) futures contract (ES, NQ, CL, YM, RTY) and tick value of each instead of backtesting futures like stocks.
@tradegeek I'm not sure what you mean, exactly. Could you please elaborate further? Currently It can be applied to any strategy and aggregation period, regardless of whether it's a futures contract or an equity. TOS handles calculating the tick value for the P&L based on the underlying instrument and the quantity can be adjusted through your strategy settings.
 
Profit Loss Code Portion

Code:
## V2.1  - 071715 - Dilbert - Correct calculation of profitLoss again
## V2    - 071515 - Dilbert - Correct calculation of profitLoss
## Profit/Loss label and bubbles, added by linus (2014-07-31)
## Note: Insert at the bottome of Mobius' RSI in Laguerre Time V02.07.2014 study.
input bubbles = {default OFF, POINTS, DOLLARS};
input PandL_Label = Yes;

def orderDir = CompoundValue(1, if RSI > OB then 1 else if RSI < OS then -1 else orderDir[1], 0);
def isOrder = orderDir crosses 0;

def orderCount = CompoundValue(1, if IsNaN(isOrder) then 0 else if isOrder then orderCount[1] + 1 else orderCount[1], 0);

def noBar = IsNaN(open[-1]);

def orderPrice = if isOrder then if noBar then close else open[-1] else orderPrice[1];
def profitLoss = if !isOrder or orderCount == 1
then 0
else if orderDir < 0 then orderPrice[1] - orderPrice
else if orderDir > 0 then orderPrice - orderPrice[1] else 0;
#else if orderDir > 0 then orderPrice[1] - orderPrice
#else if orderDir < 0 then orderPrice - orderPrice[1] else 0;
#def profitLoss = if !isOrder or orderCount == 1 then 0 else orderPrice - orderPrice[1];
def profitLossSum = CompoundValue(1, if IsNaN(isOrder) then 0 else if isOrder then profitLossSum[1] + profitLoss else profitLossSum[1], 0);

def orderWinners = CompoundValue(1, if IsNaN(isOrder) then orderWinners[1] else if orderCount > 1 and profitLoss > 0 then orderWinners[1] + 1 else orderWinners[1], 0);

AddLabel(PandL_Label, orderCount + " orders (" + AsPercent(orderWinners / orderCount) + ") | P/L " + AsDollars((profitLossSum / TickSize()) * TickValue()), if profitLossSum > 0 then Color.GREEN else if profitLossSum < 0 then Color.RED else Color.GRAY);

AddChartBubble(bubbles and isOrder and orderDir > 0, RSI, if bubbles == bubbles.POINTS then "" + profitLoss else AsDollars((profitLoss / TickSize()) * TickValue()), if noBar then Color.LIGHT_GRAY else Color.GREEN, 0);

AddChartBubble(bubbles and isOrder and orderDir > 0, RSI, if bubbles == bubbles.POINTS then "" + orderPrice else AsDollars((orderPrice / TickSize()) * TickValue()), if noBar then Color.LIGHT_GRAY else Color.GREEN, 1);

AddChartBubble(bubbles and isOrder and orderDir < 0, RSI, if bubbles == bubbles.POINTS then "" + orderPrice else AsDollars((orderPrice / TickSize()) * TickValue()), if noBar then Color.GRAY else Color.RED, 0);

AddChartBubble(bubbles and isOrder and orderDir < 0, RSI, if bubbles == bubbles.POINTS then "" + profitLoss else AsDollars((profitLoss / TickSize()) * TickValue()), if noBar then Color.GRAY else Color.RED, 1);   

# End Linus Profit/Loss Label
 
Status
Not open for further replies.

Join useThinkScript to post your question to a community of 21,000+ developers and traders.

Not the exact question you're looking for?

Start a new thread and receive assistance from our community.

87k+ Posts
330 Online
Create Post

The Market Trading Game Changer

Join 2,500+ subscribers inside the useThinkScript VIP Membership Club
  • Exclusive indicators
  • Proven strategies & setups
  • Private Discord community
  • ‘Buy The Dip’ signal alerts
  • Exclusive members-only content
  • Add-ons and resources
  • 1 full year of unlimited support

Frequently Asked Questions

What is useThinkScript?

useThinkScript is the #1 community of stock market investors using indicators and other tools to power their trading strategies. Traders of all skill levels use our forums to learn about scripting and indicators, help each other, and discover new ways to gain an edge in the markets.

How do I get started?

We get it. Our forum can be intimidating, if not overwhelming. With thousands of topics, tens of thousands of posts, our community has created an incredibly deep knowledge base for stock traders. No one can ever exhaust every resource provided on our site.

If you are new, or just looking for guidance, here are some helpful links to get you started.

What are the benefits of VIP Membership?
VIP members get exclusive access to these proven and tested premium indicators: Buy the Dip, Advanced Market Moves 2.0, Take Profit, and Volatility Trading Range. In addition, VIP members get access to over 50 VIP-only custom indicators, add-ons, and strategies, private VIP-only forums, private Discord channel to discuss trades and strategies in real-time, customer support, trade alerts, and much more. Learn all about VIP membership here.
How can I access the premium indicators?
To access the premium indicators, which are plug and play ready, sign up for VIP membership here.
Back
Top