Function to provide S&P500 index

User avatar
fbertram
Posts: 166
Joined: 16 Oct 2014
Location: Seattle, USA
Has thanked: 37 times
Been thanked: 73 times
Contact:

Function to provide S&P500 index

Postby fbertram » 12 Apr 2018

this is a little function that I use to have access to the S&P500 index from my strategies - without the need to add a secondary data series. It uses MultiCharts' DataLoader to load the data into memory.

I find this quite useful with Portfolio Trader, and I hope you do too.

Best regards, Felix


Code: Select all

//==============================================================================
// Name:        __FUB_Benchmark
// Description: Function to return benchmark index
// History:     2017x17, FUB, created
//==============================================================================
// Copyright:   2017-2018, Bertram Solutions LLC
//              http://www.bertram.solutions
// License:     this code is licensed under GPL v3.0
//==============================================================================

//------------------------------------------------------------------------------
// usage:
//
//   public class MyFancySignal : PortfolioSignalObject
//   {
//      private __FUB_Benchmark Benchmark;
//      protected override void Create()
//      {
//         Benchmark = new __FUB_Benchmark(this);
//      }
//      protected override void StartCalc()
//      {
//         Benchmark.BenchmarkTicker = "^GSPC"; // optional assignment of symbol
//      }
//      protected override void CalcBar()
//      {
//         double benchmark = Benchmark[0];
//         Output.WriteLine("{0:MM/dd/yyyy}: S&P 500 = {1} (2:F2}x",
//            Bars.Time[0], benchmark, benchmark / Benchmark.InitialValue);
//      }
//   }
//
//------------------------------------------------------------------------------
// requirements:
// * your benchmark symbol should be added to QuoteManager as an index
//   we use ^GSPC from Free Quotes, but any other index should work
// * your strategy should run on daily bars
//------------------------------------------------------------------------------

#define CLEAR_ON_STARTCALC

using System;
using System.Drawing;
using System.Linq;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using ATCenterProxy.interop;

namespace PowerLanguage
{
   namespace Function
   {
      public sealed class __FUB_Benchmark : FunctionSeries<System.Double>
      {
         [Input] public string BenchmarkTicker {get; set;}
         
         public __FUB_Benchmark(CStudyControl _master) : base(_master) { }
         public __FUB_Benchmark(CStudyControl _master, int _ds) : base(_master, _ds) { }

         private static Dictionary<string, double> BenchmarkData = BenchmarkData = new Dictionary<string, double>();
         private static ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
         private double? _InitialValue;
         public double InitialValue {get {return _InitialValue != null ? (double)_InitialValue : 0.0;}}
         
         private double GetBenchmarkValue()
         {
            string key = String.Format("{0:MM/dd/yyyy}", Bars.Time[0]);
            string key1 = String.Format("{0:MM/dd/yyyy}", Bars.Time[1]);
            
            bool hasKey;
            try
            {
               Lock.EnterReadLock();
               hasKey = BenchmarkData.ContainsKey(key);
               if (hasKey && Bars.Time.Value < DateTime.Now.Date)
                  return BenchmarkData[key];
            }
            finally
            {
               Lock.ExitReadLock();
            }
            
            // when we get here, one of 2 things has happened
            // 1) the date is not available in the cache
            // 2) the date equals today, and we should update the cache
            
            // find our index symbol in the database
            var benchmarkInfo = SymbolStorage.GetDataFeeds()
               .SelectMany(it =>
                  SymbolStorage.GetSymbols(it, ESymbolCategory.Index)
                  .Select(it2 => new {
                     feed = it,
                     syminfo = it2}))
               .Where(it => it.syminfo.SymbolName == BenchmarkTicker)
               .First();
            
            // populate data request structure
            // see https://www.multicharts.com/trading-software/index.php/4.7.4_Receiving_the_data_for_any_symbol_and_any_resolution._DataLoader
            InstrumentDataRequest Req = Bars.Request;
            Req.From = Bars.Time[2];
            Req.To = Bars.Time[0] + TimeSpan.FromDays(365);
            // alternative way of populating data range:
            //   DataRequest _DataRequest = new DataRequest();
            //   _DataRequest.RequestType = DataRequestType.BarsBack;
            //   _DataRequest.Count = Bars.FullSymbolData.Count;
            //   Req.Range = _DataRequest;
            //Req.Resolution = ... // FIXME: should force this to daily bars
            Req.Category = (ESymbolCategory)benchmarkInfo.syminfo.SymbolCategory; // can we cast MCPA_MCSymbolCategories => ESymbolCategory ???
            Req.Symbol = benchmarkInfo.syminfo.SymbolName;
            Req.DataFeed = benchmarkInfo.feed;
            Req.Exchange = benchmarkInfo.syminfo.SymbolExchange;
            Req.RTSymbol = Req.Symbol;
            Req.RTDataFeed = Req.DataFeed;
            Req.RTExchange = Req.Exchange;

            AutoResetEvent autoEvent = new AutoResetEvent(false);
            
            IDataLoaderResult iRes = DataLoader.BeginLoadData( Req, Result =>
                  {   // public void OnData(IDataLoaderResult Result){ }
                     if (Result.IsCompleted)
                     {
                        try
                        {
                           Lock.EnterWriteLock();
                           foreach (var quote in Result.Data)
                           {
                              string k = String.Format("{0:MM/dd/yyyy}", quote.Time);
                              BenchmarkData[k] = quote.Close;
                           }
                        }
                        finally
                        {
                           Lock.ExitWriteLock();
                        }
                     }
                     
                     DataLoader.EndLoadData( Result );
                     autoEvent.Set();
                  },
               null);
            
            autoEvent.WaitOne();

            try
            {
               Lock.EnterReadLock();
               hasKey = BenchmarkData.ContainsKey(key);
               bool hasKey1 = BenchmarkData.ContainsKey(key1);
               
               // we might not be able to load today's value
               // if that happens, we revert to yesterday's value
               
               return hasKey
                  ? BenchmarkData[key]
                  : (hasKey1 ? BenchmarkData[key1] : 0.0);
            }
            finally
            {
               Lock.ExitReadLock();
            }            
         }
         
         protected override void Create()
         {
            BenchmarkTicker = "^GSPC";
         }
         
         protected override void StartCalc()
         {
         #if CLEAR_ON_STARTCALC
            BenchmarkData.Clear();
         #endif
         }
         
         protected override System.Double CalcBar()
         {
            double benchmark = GetBenchmarkValue();
            
            if (_InitialValue == null)
               _InitialValue = benchmark;
            
            return benchmark;
         }
      }
   }
}

//==============================================================================
// end of file
Attachments
__FUB_Benchmark.pln
(2.73 KiB) Downloaded 246 times
These users thanked the author fbertram for the post (total 2):
daroborad

darob
Posts: 174
Joined: 20 Nov 2014
Has thanked: 46 times
Been thanked: 23 times

Re: Function to provide S&P500 index

Postby darob » 04 May 2018

Hi Felix, thanks so much for sharing this. I just need something like this to work in a 5 min timeframe. Are you willing to offer an opinion on that possibility? Of course I understand if not.

Alternatively, if MC added secondary data series functionality to the scanner that would be awesome.

User avatar
fbertram
Posts: 166
Joined: 16 Oct 2014
Location: Seattle, USA
Has thanked: 37 times
Been thanked: 73 times
Contact:

Re: Function to provide S&P500 index

Postby fbertram » 04 May 2018

Hi Darob,

that shouldn't be hard to do. There are a few things that would need to be changed:
    * the timeframe should be taken from the 'master' instrument
    * the key used to cache the benchmark data should be changed to the epoch of the bar


Sounds like a great activity for a rainy afternoon. PM me, if you'd like to discuss.


Cheers, Felix


Return to “User Contributed Studies”