Announcement

Collapse

Looking for a User App or Add-On built by the NinjaTrader community?

Visit NinjaTrader EcoSystem and our free User App Share!

Have a question for the NinjaScript developer community? Open a new thread in our NinjaScript File Sharing Discussion Forum!
See more
See less

Partner 728x90

Collapse

Creating Custom Performance Metric

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Creating Custom Performance Metric

    I'm trying to create a custom performance metric called Risk Adjusted Return. The idea is to take the net profit from a backtest and divide it by the number of years in the backtest to get an average annualized profit. Finally, divide the average annualized profit by the maximum adverse excursion of all trades, to get a risk adjusted return. For example, if your average annualized profit is $1,000 and your maximum adverse excursion is also $1,000, your RAR is 100%, because you earned $1,000/yr for every $1,000 risked.

    I tried to code this by following the SampleCumProfit example. I made some changes, because I only want to perform the calculations at the end of each backtest, not after each trade. Also, since this metric is only applicable for currency-based backtests, I only populated the targetMetrics.Values[(int)Cbi.PerformanceUnit.Currency] array. The script seems to perform the calculations correctly, but when I run a backtest, instead of displaying the result, it displays System.Double[]. I've attached a screen shot that shows this and I've posted the code below. Please help me get this working.

    Code:
    #region Using declarations
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Xml.Serialization;
    using NinjaTrader.Cbi;
    using NinjaTrader.Gui;
    using NinjaTrader.Gui.Chart;
    using NinjaTrader.Gui.SuperDom;
    using NinjaTrader.Gui.Tools;
    using NinjaTrader.Data;
    using NinjaTrader.NinjaScript;
    using NinjaTrader.Core.FloatingPoint;
    
    #endregion
    
    //This namespace holds Performance metrics in this folder and is required. Do not change it. 
    namespace NinjaTrader.NinjaScript.PerformanceMetrics
    {
        public class RiskAdjustedReturn : PerformanceMetric
        {
            private double maeAllTrades;
            private Cbi.Currency denomination = (Cbi.Currency) (-1);
    
            protected override void OnStateChange()
            {
                if (State == State.SetDefaults)
                {
                    Description                                    = @"";
                    Name                                        = "Risk Adjusted Return";
                }
                else if (State == State.Active)
                {
                    maeAllTrades = 0;
                }
            }
    
            protected override void OnAddTrade(Cbi.Trade trade)
            {
                if (denomination == (Cbi.Currency) (-1))
                    denomination = trade.Exit.Account.Denomination;
    
                if (trade.MaeCurrency > maeAllTrades)
                    maeAllTrades = trade.MaeCurrency;
            }
    
            // This is called as the values of a trade metric are saved, which occurs e.g. in the strategy analyzer on optimizer runs
            protected override void OnCopyTo(PerformanceMetricBase target)
            {
                // You need to cast, in order to access the right type
                RiskAdjustedReturn targetMetrics = (target as RiskAdjustedReturn);
    
                double annualizedNetProfit  = TradesPerformance.NetProfit / ((TradesPerformance.MaxDate - TradesPerformance.MinDate).TotalDays / 365); // Net Profit divided by years
                targetMetrics.Values[(int)Cbi.PerformanceUnit.Currency] = (annualizedNetProfit / maeAllTrades) / 100;
            }
    
            // This is called as the trade metric is merged, which occurs e.g. in the strategy analyzer for the total row and for aggregate 
            protected override void OnMergePerformanceMetric(PerformanceMetricBase target)
            {
                // You need to cast, in order to access the right type
                RiskAdjustedReturn targetMetrics = (target as RiskAdjustedReturn);
    
                // This is just a simple weighted average sample
                if (targetMetrics != null && TradesPerformance.TradesCount + targetMetrics.TradesPerformance.TradesCount > 0)
                    for (int i = 0; i < Values.Length; i++)
                        targetMetrics.Values[i] = (targetMetrics.Values[i] * targetMetrics.TradesPerformance.TradesCount + Values[i] * TradesPerformance.TradesCount) / (TradesPerformance.TradesCount + targetMetrics.TradesPerformance.TradesCount);
            }
    
    
    
            #region Properties
            // The display attribute determines the name of the performance value on the grid (the actual property name below is irrelevant for that matter)
            [Display(ResourceType = typeof(Custom.Resource), Description = "", Name = "Risk adjusted return", Order = 0)]
            public double[] Values
            { get; private set; }
            #endregion
    
        }
    }

    #2
    Hello bkonia,

    Thank you for the post.

    I took a look at the code however I don't specifically see what may be causing that problem. Without digging through the code and making comparisons against the other sample it would be hard to say. For this what I would suggest to do would be to recreate your script by duplicating the sample again and then editing that. That gives a working starting point to where you can just modify the calculation. Once you see your modification is working you could start removing the other parts you don't need. I am guessing that part of the script was removed that is needed however I don't see what that is offhand.

    I look forward to being of further assistance.
    JesseNinjaTrader Customer Service

    Comment


      #3
      Hi Jesse,

      I've already spent hours trying to troubleshoot this and I'm getting nowhere. Could you please install it on your machine and test it to see why it's doing that?

      Comment


        #4
        I added the formatting section from the sample to my metrics implementation and it now displays a value, before it was System.double[]

        Code:
                #region Miscellaneous
                // The format method allows you to customize the rendering of the performance value on the summary grid.
                public override string Format(object value, Cbi.PerformanceUnit unit, string propertyName)
                {
                    double[] tmp = value as double[];
                    if (tmp != null && tmp.Length == 5)
                        switch (unit)
                        {
                            case Cbi.PerformanceUnit.Currency    : return Core.Globals.FormatCurrency(tmp[0], denomination);
                            case Cbi.PerformanceUnit.Percent    : return tmp[1].ToString("P");
                            case Cbi.PerformanceUnit.Pips        : return Math.Round(tmp[2]).ToString(Core.Globals.GeneralOptions.CurrentCulture);
                            case Cbi.PerformanceUnit.Points        : return Math.Round(tmp[3]).ToString(Core.Globals.GeneralOptions.CurrentCulture);
                            case Cbi.PerformanceUnit.Ticks        : return Math.Round(tmp[4]).ToString(Core.Globals.GeneralOptions.CurrentCulture);
                        }
                    return value.ToString();            // should not happen
                }
                #endregion
        Last edited by MojoJojo; 06-09-2020, 10:28 PM.

        Comment


          #5
          Thanks for your suggestions, I got it working!

          Comment


            #6
            Great that the code is working now.

            After experimenting a bit, my optimizations take about 3 times longer with one custom metric.
            This is the shortest and most efficient version I was able to come up with that will display the proper output.

            Code:
                public class ProfitDrawdownMetric : PerformanceMetric {
                    protected override void OnStateChange() {
                        if (State == State.SetDefaults) {
                            Name    = "ProfitDrawdownMetric";
                        } else if (State == State.Configure) {
                            Values    = new double[1];
                            Values[0] = double.MinValue;
                        }
                    }
            
                    private double Calculate() {
                        double drawDown = TradesPerformance.Currency.Drawdown;
                        return drawDown != 0 ? TradesPerformance.NetProfit / drawDown * -1 : 1;
                    }
            
                    public override string Format(object value, Cbi.PerformanceUnit unit, string propertyName) {
                        if(Values[0] == double.MinValue) {
                            Values[0] = Calculate();
                        }
                        return Math.Round(Values[0] ,2).ToString(Core.Globals.GeneralOptions.CurrentCulture);
                    }
            
                    // The attribute determines the name of the performance value on the grid (the actual property name below is irrelevant for that matter)
                    [Display(ResourceType = typeof(Custom.Resource), Description = "Profit to drawdown", Name = "P/D", Order = 0)]
                    public double[] Values
                    { get; private set; }
                }
            But performance is still severely impacted despite not calculating on each trade.
            With 2 metrics it's much worse, the effect is at least additive if not multiplicative.

            Also the metric does not show up in the optimization results table.

            This is just a heads up for people using custom metrics or intending to. I would have liked to generate spreadsheets with such values, but it's not necessary.

            Quick question for the NT team, can the "Note" column in the optimization table be filled with a value from within strategy code or is it user input only?
            Last edited by MojoJojo; 06-12-2020, 02:02 AM.

            Comment


              #7
              Hello MojoJojo,

              I took a look at the code here but I don't see anything specifically that would slow it down substantially, this will add some extra work to the analyzer as it is more calculations but should generally work similar to the sample. I could still suggest to avoid creating one from scratch and duplicate the sample, then modify that. It has all the working parts in the file already so it serves as a good starting point.

              The note column cannot currently be filled with a value from code, that would be a user editable field.

              I look forward to being of further assistance.
              JesseNinjaTrader Customer Service

              Comment


                #8
                Hello Jesse,

                thank you for the information about the notes column and for reviewing my code.
                I had already decided to dynamically update the spreadsheets externally and after experimenting with a "Null Metric" the decision feels right to me.

                Too bad that the notes column cannot be used for data exchange, I also tried adding a string parameter field but its value is not transferred to the table.
                If the need arises I am certain because of the flexibility of NinjaScript, that hashing the parameters and writing them along with the data to a file, db or using ipc would be feasible.

                Anyways, I am happy with how things have turned out and NT has made it possible.

                Comment


                  #9
                  Hello
                  I'm not familiar with building performance metrics it's my first time and i can't find much exemples like the other type of ninjascripts , Can i get the code How you Built Max.Consec, Losers ??

                  Comment


                    #10
                    Hello ossamaelouadih,

                    The internal performance metrics are not available as source code, the only sample would be the included SampleCumProfit script. That has all that is needed to show how you would access performance information and use it as a metric.
                    JesseNinjaTrader Customer Service

                    Comment

                    Latest Posts

                    Collapse

                    Topics Statistics Last Post
                    Started by ZenCortexAuCost, Today, 04:24 AM
                    0 responses
                    3 views
                    0 likes
                    Last Post ZenCortexAuCost  
                    Started by ZenCortexAuCost, Today, 04:22 AM
                    0 responses
                    0 views
                    0 likes
                    Last Post ZenCortexAuCost  
                    Started by SantoshXX, Today, 03:09 AM
                    0 responses
                    13 views
                    0 likes
                    Last Post SantoshXX  
                    Started by DanielTynera, Today, 01:14 AM
                    0 responses
                    2 views
                    0 likes
                    Last Post DanielTynera  
                    Started by yertle, 04-18-2024, 08:38 AM
                    9 responses
                    42 views
                    0 likes
                    Last Post yertle
                    by yertle
                     
                    Working...
                    X