Overall Statistics
Total Orders
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
100000
End Equity
100000
Net Profit
0%
Sharpe Ratio
0
Sortino Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
-0.461
Tracking Error
0.159
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion

namespace FirenzeTech
{
    public enum AssetIdx { Hogs = 0, Corn = 1, Soymeal = 2, Count = 3 }
    public enum PosSize { Hogs = 20, Corn = 8, Soymeal = 3 }
    public enum OrderState { None, Open, ClosePending, Closed }

    /*===================================================================================
     * H2 position is HE, ZC and ZM orders with 20:8:3 ratio
     * 
     * 
     * 
     * TODO:
     * - Open/close orders in steps of 5:2:1 lots
     * - Close positions that don't confirm to required ratio
     * - Open limit orders slightly worse than current price to guarantee fills
     * - Notify on errors
     * 
     *==================================================================================*/
    public class H2PositionInfo
    {
        public bool longHogs;                                   /* true if the position opened to buy hogs, sell grains */
        public bool longGrains { get { return (!longHogs); } }  /* true if the position opened to buy grains, sell hogs */
        public OrderTicket[] openTickets;                       /* tickets created when the order was opened */
        public OrderTicket[] closeTickets;                      /* tickets created to close the order */
        public OrderState[] orderState;
        public static int[] buyRatio = { 20, -8, -3 };
        public static int[] sellRatio = { -20, 8, 3 };
        public double fillMargin = 0.001;                       /* factor to move limit orders price slightly below market price so it has a better chance of */
                                                                /* being filled. The factor is a percentage of asset price, so 0.001 is 0.1% of asset price.  */
                                                                /* This works out to be ~$800 for H2 combined price based on 20:8:3 lots */
        public H2PositionInfo()
        {
            
        }

        public H2PositionInfo(SpreadGroup sg, bool buyHogs) : this()
        {
        }

        public bool Update()
        {
            return (true);
        }

        public bool IsClosed()
        {
            return (true);
        }

        public bool CloseAtLimit(SpreadGroup sg)
        {
            return (true);
        }

        public bool CloseAtMarket()
        {
            return (true);
        }

        private bool CloseOrder(int orderIdx, decimal limitPrice = decimal.MinValue)
        {
            return (true);
        }

        private void UpdateOrderStatus(int idx)
        {
        }
    }

    public class OrderExecution
    {
        public uint posCount { get ; }    /* number of positions in positions list */
        public int signedPosCount { get ; }                                          /* signed number of positions (<0 = short hogs, >0 = long hogs) */

        public OrderExecution()
        { 
        }

        public void ManagePositions(int reqPos, SpreadGroup sg)
        {
        }
    }
}
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion

namespace FirenzeTech
{
    public class H2Algorithm : FTAlgorithm
    {
        public override DateTime OnInitialize(List<DateTime> h2ContractsList, Resolution dataRate, History histDB)
        {
            return (DateTime.MinValue);
        }

        public override List<SpreadGroup> OnNewData(Slice slice)
        {
            return new List<SpreadGroup>();
        }

        public void UpdateGroupsOnNewPrice(Symbol contractSymbol, double bidPrice, double askPrice, DateTime sliceTime, ref List<SpreadGroup> readyGroups)
        {
        }

        public void AddRelevantFutureContract(Symbol futureSymbol, int futureIndex, DateTime date)
        {
        }
    }
}
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion

namespace FirenzeTech
{
    public class H2Constants
    {
    }

    public class H2Strategy : FTStrategy
    {
        private List<DateTime> h2ContractDates = new List<DateTime>();          /* list of all required h2 contracts year/month to test, sorted in ascending order */

        public override void OnInitialize()
        {
            /*================================================================
             * Read and parse user supplied parameters
             *===============================================================*/
            string h2DatesStrg = BrokerIF.GetParameter("h2_date_range", "5/2012");        /* default test range is 2008 until current date */
            h2ContractDates = Tools.ParseContractDatesParam(h2DatesStrg);
            if (h2ContractDates == null)
            {
                Tools.FatalError($"The supplied H2 contracts dates range \"{h2DatesStrg} \"is invalid.\n" +
                                 $"The accepted format is \"mm/yy - mm/yy, ...\"");
            }

        }

        public override void OnNewData(Slice data)
        {
        }

        void AddH2GroupChart(string name)
        {
        }

        void ExecuteTradeLogic(SpreadGroup sg)
        {            
        }
    }
}
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion

namespace FirenzeTech
{
    /*===================================================================================
     * Class to manage historical data
     * 
     * 
     * 
     * 
     * 
     *==================================================================================*/
    public class History
    {
        public double[] means = new double[12];                                 /* Current means for every month chain */
        public double[] StdDev = new double[12];                                /* Current standard deviation for every month chain */
        private H2Chain[] h2Chains = new H2Chain[12];                           /* H2 historical data for each month */

        /*====================================================================================
         * Create historical means database from csv string
         * 
         * Entry:   csvMeans  = csv string of historical spread and mean values
         *          startYear = H2 year to start the database from (should be >= 2000)
         *          success   = receives true if the operation completed successfully
         *          
         * Return:  None
         * 
         * Notes:
         * 
         * TODO: - extract standard deviation (also get csv values that include std dev)
         *
         *===================================================================================*/
        public History(string csvMeans, int startYear, out bool success)
        {
            List<object> colList = Tools.CsvToColList(csvMeans, "FfFfDII", 1);
            success = ((colList != null) && (colList.Count == 7));
            if (success)
            {
                success = false;

                double[] volumeCol = (double[])colList[0];          /* volume column */
                double[] sdCol = (double[])colList[1];              /* standard deviation column */
                double[] spreadCol = (double[])colList[2];          /* spread column */
                double[] meanCol = (double[])colList[3];            /* mean column */
                DateTime[] sampleDateCol = (DateTime[])colList[4];  /* sample date column */
                int[] groupYearCol = (int[])colList[5];             /* group year column */
                int[] groupMonthCol = (int[])colList[6];            /* group month column */
                int rowCount = spreadCol.Length;

                if (groupMonthCol[0] != 1) return;      /* month 1 must be the first month in the array */

                int currYearStartIdx = 0;               /* starting row of the chain month we will extract */
                int nextChainStart;                     /* starting row of the chain next month we will extract */
                /*===================================================================================
                 * currYearStartIdx = starting row of the chain we will extract
                 * nextChainStart   = starting row of the next chain we will extract
                 * 
                 * loop through each chain month
                 *==================================================================================*/
                for (int month = 1; month <= 12; month++)
                {
                    H2Chain h2Chain = h2Chains[month - 1] = new H2Chain(startYear);
                    if (month == 12)
                    {
                        nextChainStart = Array.LastIndexOf(groupMonthCol, month, rowCount - 1);      /* this serached backwards from rowCount - 1 */
                        if (nextChainStart > 0) nextChainStart++;
                    }
                    else
                    {
                        nextChainStart = Array.IndexOf(groupMonthCol, month + 1, currYearStartIdx);
                    }
                    if ((nextChainStart < 0) || (nextChainStart <= currYearStartIdx))
                    {
                        return;
                    }
                    /*=======================================================
                     * chain start and end index found
                     * fnd the start of the required year in the chain
                     *======================================================*/
                    int chainLen = nextChainStart - currYearStartIdx;
                    currYearStartIdx = Array.IndexOf(groupYearCol, startYear, currYearStartIdx, chainLen);
                    if (currYearStartIdx < 0)
                    {
                        return; ;
                    }
                    chainLen = nextChainStart - currYearStartIdx;
                    if (chainLen <= 0)
                    {
                        return;
                    }
                    /*=======================================================
                     * currYearStartIdx = curr year start idx in curr chain
                     * nextChainStart   = chain end index + 1
                     * chainLen = diff between the above two vars
                     * 
                     * Loop through years in the chain
                     *======================================================*/
                    int nextYearStartIdx = 0;
                    for (int yr = startYear; nextYearStartIdx < nextChainStart; yr++)
                    {
                        /*=======================================================
                         * find the end index of current year
                         *======================================================*/
                        nextYearStartIdx = Array.IndexOf(groupYearCol, yr + 1, currYearStartIdx, chainLen);
                        if (nextYearStartIdx < 0)
                        {
                            /*===================================================================================
                             * if start of next year was not found, this must be the last year in the chain
                             * and as such, [next chain start - 1] must contain current year
                             *==================================================================================*/
                            if (groupYearCol[nextChainStart - 1] != yr)
                            {
                                return;
                            }
                            nextYearStartIdx = nextChainStart;
                        }
                        int yearSampleCount = nextYearStartIdx - currYearStartIdx;
                        if (yearSampleCount <= 0)
                        {
                            return;
                        }
                        /*===========================================================
                         * create and copy samples of the group (i.e. current year)
                         *==========================================================*/
                        GroupRecords grpData = new GroupRecords();
                        grpData.sampleDates = sampleDateCol.Skip(currYearStartIdx).Take(yearSampleCount).ToList();
                        grpData.spreads = spreadCol.Skip(currYearStartIdx).Take(yearSampleCount).ToList();
                        grpData.means = meanCol.Skip(currYearStartIdx).Take(yearSampleCount).ToList();
                        grpData.stdDev = sdCol.Skip(currYearStartIdx).Take(yearSampleCount).ToList();

                        /*=======================================================
                         * fill missing means and std dev at group start from 
                         * overlapping days in the previous year 
                         *======================================================*/
                        if (grpData.means[0] == double.MinValue)
                        {
                            /*=======================================================
                             * find last empty mean at current year start
                             *======================================================*/
                            int overlapCnt = 0;
                            while ((overlapCnt < grpData.means.Count) && (grpData.means[overlapCnt] == double.MinValue))
                            {
                                overlapCnt++;
                            }
                            int ovStartIdx = currYearStartIdx - overlapCnt;     /* overlap start index */

                            /*==============================================================
                             * if overlap start data doesn't match current year start data
                             *=============================================================*/
                            if ((ovStartIdx < 0) || (grpData.sampleDates[0] != sampleDateCol[ovStartIdx]) ||
                                (groupYearCol[ovStartIdx] != yr - 1) || (groupMonthCol[ovStartIdx - 1] != month))
                            {
                                if ((overlapCnt == 1) && (grpData.means.Count > 1))
                                {
                                    grpData.means[0] = grpData.means[1];
                                    grpData.stdDev[0] = grpData.stdDev[1];
                                }
                                else
                                {
                                    return;
                                }
                            }
                            else
                            {
                                /*=======================================================
                                 * copy mean from last year group end
                                 *======================================================*/
                                for (int i = 0; i < overlapCnt; i++)
                                {
                                    if (grpData.sampleDates[i] != sampleDateCol[ovStartIdx + i])    /* make sure dates match */
                                    {
                                        return;
                                    }
                                    grpData.means[i] = meanCol[ovStartIdx + i];
                                    grpData.stdDev[i] = sdCol[ovStartIdx + i];
                                }
                            }
                        }

                        h2Chain.chainGroups.Add(grpData);
                        currYearStartIdx += yearSampleCount;
                        chainLen-= yearSampleCount;
                    }
                    currYearStartIdx = nextChainStart;
                }
                success = true;
            }
        }

        /*====================================================================================
         * Return a history record for the specified spread group and date
         * 
         * Entry:   date  = date to get the record for (hour not important)
         *          month = h2 month to get the record for (1 - 12)
         *          year  = h2 year to get the record for
         * 
         * Return:  history record or null
         * 
         * Notes:   
         * 
         * TODO: calculate the mean if we have enough history records
         *
         *===================================================================================*/
        public HistoryRecord GetRecord(DateTime date, int month, int year)
        {
            if ((month < 1) || (month > 12))
            {
                Tools.FatalError($"Invalid history month value ({month})");
            }
            return (h2Chains[month - 1].GetRecord(year, date));
        }

        /*====================================================================================
         * Check if specified record date is valid
         * 
         * Entry:   date  = date to check the record for (hour not important)
         *          month = chain month to check the record for (1 - 12)
         *          year  = chain year to check the record for
         * 
         * Return:  true if record date is valid 
         *              (date is a weekday and a record exist for the previous or next date)
         *          false if record date is invalid
         *              (date is a weekend or a record doesn't exist for the previous or 
         *              next date)
         *          
         * Notes:   This method should typically be used to check if the supplied request
         *          date was valid if GetReord() returns null
         * 
         *===================================================================================*/
        public bool RecordDateValid(DateTime date, int month, int year)
        {
            if (GetRecord(date, month, year) == null)
            {
                if ((date.DayOfWeek == DayOfWeek.Saturday) || (date.DayOfWeek == DayOfWeek.Sunday))
                {
                    return (false);
                }
                if ((h2Chains[month - 1].GetRecord(year, date.AddDays(-1)) == null) && (h2Chains[month - 1].GetRecord(year, date.AddDays(1)) == null))
                {
                    return (false);
                }
            }
            return (true);
        }
    }

    /*===================================================================================
     * The H2Chain class contains daily historical data for an H2 chain (group month)
     * from ~1978 to the currently active contracts expiry dates
     * 
     * The records in the arrays are sorted according to the following criteria
     *  - Group Month (1-12)
     *  - Group Year (1978 - current)
     *  - record date within the group (1978 - current)
     * 
     * Example of the sorted groups in JAN chain:
     *
     * Jan 1978 group
     *      first trading date of the group
     *      ...
     *      last trading date of the group
     * Jan 1979 group
     *      first trading date of the group
     *      ...
     *      last trading date of the group
     * Jan 1980 group
     *      ...
     *
     * Note that the last few trading dates of a group may overlap the first few 
     * trading dates of the next year group. The overlapping dates in the newer year
     * group will not contain any mean values as those means are included in the 
     * overlapped days of the older year group
     *
     *
     * 
     *==================================================================================*/
    public class H2Chain
    {
        public int startYear { get; }           /* starting year for groups in chainGroups (1978--) */
        public List<GroupRecords> chainGroups;  /* list of GroupRecords for all available years in the chain */
                                                /* groupYears[0] = groups records for first year in the chain */
                                                /* groupYears[n] = groups records for first nth year in the chain */
        public H2Chain(int startYr)
        {
            chainGroups = new List<GroupRecords>();
            startYear = startYr;
        }

        /*====================================================================================
         * return record index from group year and record date
         * 
         * Entry:   grpYear  = year of the required group in the chain
         *          sampDate = date of the record within the group
         * 
         * Return:  index of requested record or -1 if noty found
         * 
         * Notes:
         *
         *===================================================================================*/
        public int GetRecordIndex(int grpYear, DateTime sampDate)
        {
            int yearIdx = grpYear - startYear;
            if ((yearIdx < 0) || (yearIdx >= chainGroups.Count))
            {
                return (-1);
            }
            return (chainGroups[yearIdx].GetRecordIndexFromDate(sampDate));
        }

        /*====================================================================================
         * return record from group year and record date
         * 
         * Entry:   grpYear  = year of the required group in the chain
         *          sampDate = date of the record within the group
         * 
         * Return:  record or null
         * 
         * Notes:
         *
         *===================================================================================*/
        public HistoryRecord GetRecord(int grpYear, DateTime sampDate)
        {
            int yearIdx = grpYear - startYear;
            if ((yearIdx < 0) || (yearIdx >= chainGroups.Count))
            {
                return (null);
            }
            return (chainGroups[yearIdx].GetRecordFromDate(sampDate));
        }
    }

    /*===================================================================================
     * database for any single spread groups (e.g., JAN 2024)
     * 
     * 
     * 
     * 
     *==================================================================================*/
    public class GroupRecords
    {
        public int lastReqSampleIdx { get; private set; }   /* index of last requested sample. -1 if not defined */
        public List<DateTime> sampleDates;                  /* date of each sample in the group */
        public List<double> spreads;                        /* daily spread value */
        public List<double> means;                          /* daily historical mean */
        public List<double> stdDev;                         /* std deviation */

        public GroupRecords()
        {
            sampleDates = new List<DateTime>();
            spreads= new List<double>();
            means= new List<double>();
            stdDev= new List<double>();
            lastReqSampleIdx = -1;
        }

        /*====================================================================================
         * return record index from record date
         * 
         * Entry:   sampDate = date of the record within the group. must exactly match.
         * 
         * Return:  index of requested record or -1 if not found
         * 
         * Notes:
         *
         *===================================================================================*/
        public int GetRecordIndexFromDate(DateTime sampDate)
        {
            sampDate = new DateTime(sampDate.Year, sampDate.Month, sampDate.Day);
            if (lastReqSampleIdx >= 0)
            {
                if (sampDate >= sampleDates[lastReqSampleIdx])
                {
                    if (sampDate == sampleDates[lastReqSampleIdx])
                    {
                        return (lastReqSampleIdx);
                    }
                    lastReqSampleIdx++;
                    if (lastReqSampleIdx < sampleDates.Count)
                    {
                        if (sampDate == sampleDates[lastReqSampleIdx])
                        {
                            return (lastReqSampleIdx);
                        }
                    }
                }
            }
            lastReqSampleIdx = sampleDates.BinarySearch(sampDate);
            return (lastReqSampleIdx);
        }

        public HistoryRecord GetRecordFromDate(DateTime sampDate)
        {
            int idx = GetRecordIndexFromDate(sampDate);
            if (idx >= 0)
            {
                return (new HistoryRecord(sampleDates[idx], means[idx], spreads[idx], stdDev[idx]));
            }
            return(null);
        }
    }

    public class HistoryRecord
    {
        public DateTime date;
        public double mean;
        public double stdDev;
        public double spread;
        public bool valid;

        public HistoryRecord(DateTime dt, double mn, double sprd, double stdv)
        {
            date = dt;
            mean = mn;
            spread = sprd;
            stdDev = stdv;
        }
    }
}
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;   
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Storage;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion
namespace FirenzeTech
{
    public class FirenzePlatform : QCAlgorithm
    {
        private static List<FTStrategy> strategyList = new List<FTStrategy>();

        public override void Initialize()
        {
            Tools.Init(this);
            H2Strategy h2 = new H2Strategy();
            h2.Init(this, "H2");
            strategyList.Add(h2);

            foreach (FTStrategy stratX in strategyList)
            {
                stratX.OnInitialize();
            }
        }
        public override void OnData(Slice data)
        {
        }
        public override void OnEndOfAlgorithm()
        {
        }
    }

    abstract public class FTStrategy
    {
        public static QCAlgorithm BrokerIF { get; private set; }      /* broker interface */
        public string Name { get; private set; }                      /* Strategy name */
        public void Init(QCAlgorithm b, string nm)
        {
            if (Name == null)
            {
                BrokerIF = b;
                Name = nm;
            }
        }

        public abstract void OnInitialize();                    /* called once on strategy startup. Must be implemented by derived strategies */
        public abstract void OnNewData(Slice data);             /* called on every new quote (tick or bar). Must be implemented by derived strategies */

        public virtual void OnTerminate()                       /* called when the strategy terminates */
        {
        }
    }

    abstract public class FTAlgorithm
    {
        public static QCAlgorithm BrokerIF { get; private set; }          /* broker interface */
        public string Name { get; private set; }                          /* algorithm name */
        public void Init(QCAlgorithm b, string nm)
        {
            if (Name == null)
            {
                BrokerIF = b;
                Name = nm;
            }
        }
        public abstract DateTime OnInitialize(List<DateTime> h2ContractsList, Resolution dataRate, History histDB);
                                                                        /* called once on strategy startup. Must be implemented by derived algorithms */
                                                                        /* returns the backtest start date for the supllied h2 contracts */
        public abstract List<SpreadGroup> OnNewData(Slice data);        /* called on every new quote (tick or bar). Must be implemented by derived algorithms */
                                                                        /* returns a list of the spread groups that has a new h2 signal value, or null if none */
        public virtual void OnTerminate()                               /* called when the strategy terminates */
        {
        }
    }
}

#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion

namespace FirenzeTech
{
    public enum PriceComponent { Bid, Ask, Median }

    /*===================================================================================
     * 
     * 
     * 
     * TODO: 
     * - Which spread value (at 1:00pm?) should be used to calculate the mean
     * 
     *==================================================================================*/
    public class SpreadStatus
    {
        public OrderExecution PositionMgmt;             /* current Positions */
        public PriceComponent priceComponent;           /* which price to use to create the spread */

        public bool LevelsFrozen;                       /* true if trigger levels frozen */
        public double Mean { get { return (LevelsFrozen ? FrozenMean : CurrentMean); } }
        public double StdDev { get { return (LevelsFrozen ? FrozenStdDeviation : StdDeviation); } }
        public double TrigWidth { get { return (LevelsFrozen ? FrozenTriggerWidth : TriggerWidth); } }

        private History historyDB;                      /* mean history database */
        private int meanErrors = 0;                     /* counter for consecutive mean calculation errors */
        private DateTime lastUpdateTime;                /* time of last mean update */
        private double CurrentMean;                     /* rolling mean over the past 18 month for this group, calculated at end of the day */
        private double StdDeviation;                    /* standard deviation for this group, calculated daily */
        private double TriggerWidth;                    /* trigger level width for this group, = stdDeviation * 0.85 */

        private double FrozenMean;                      /* Frozen mean. = CurrentMean if not frozen */
        private double FrozenStdDeviation;              /* Frozen standard deviation. = StdDeviation if not frozen */
        private double FrozenTriggerWidth;              /* Frozen level width. = TriggerWidth if not frozen */

        public SpreadStatus(History histDB)
        {
            PositionMgmt = new OrderExecution();
            priceComponent = PriceComponent.Median;
            historyDB = histDB;

            lastUpdateTime = DateTime.MinValue;
            CurrentMean = 0;
            StdDeviation = 0;
            TriggerWidth = 0;
            FrozenMean = 0;
            FrozenStdDeviation = 0;
            FrozenTriggerWidth = 0;
            LevelsFrozen = false;
        }

        /*====================================================================================
         * Find the level index (0 -> STDEV levels + 1) corresponding to a spread value, 
         * taking into account if the levels are frozen
         * 
         * Entry:   spreadVal   = spread value to return its level
         *          stdevLevels = standard deviation levels (1--)
         *          onBoundary  = receives true if spreadVal is on returned level boundary
         * 
         * Return:  Level starting at 0
         *              Level 0 = between mean and 1st STDEV
         *              Level 1 = between 1st STDEV and 2nd STDEV
         *          if onBoundary is true then:
         *              if spreadVal > Mean, then spreadVal is on level lower boundary
         *              if spreadVal > Mean, then spreadVal is on level higher boundary
         * 
         * Notes:   Example
         *
         *      Level                Returned        
         *      Index                Level       OnBoundary
         *      
         *      3                 
         *          -------*-----      3            true
         *      2                 
         *          ------*------      3            true
         *      1                 
         *          ------------- 
         *      0       *              0
         *          ===*=========      0            true
         *      0       *              0            true
         *          -------------          
         *      1         *            1
         *          -------*-----      2            true
         *      2           *          2
         *          ---------*---      3            true
         *      3                 
         *  
         *===================================================================================*/
        public int FindCurrentLevel(double spreadVal, int stdDevLevels, out bool onBoundary)
        {
            int currLevelIdx = 0;                       /* start with mean level */
            double currLevelMin;                        /* start with mean value */
            double currLevelMax;

            onBoundary = false;

            if (spreadVal >= Mean)
            {
                currLevelMin = Mean;
                currLevelMax = Mean + TriggerWidth;
                while (currLevelIdx <= stdDevLevels)        /* levels 0 - 2 */
                {
                    if (spreadVal < currLevelMax)
                    {
                        if (spreadVal == currLevelMin)
                        {
                            onBoundary = true;
                        }
                        break;
                    }
                    currLevelMin = currLevelMax;
                    currLevelMax += TrigWidth;
                    currLevelIdx++;
                }
            }
            else
            {
                currLevelMin = Mean - TriggerWidth;
                currLevelMax = Mean;
                while (currLevelIdx <= stdDevLevels)        /* level 0 - 2 */
                {
                    if (spreadVal > currLevelMin)
                    {
                        if (spreadVal == currLevelMax)
                        {
                            onBoundary = true;
                        }
                        break;
                    }
                    currLevelMax = currLevelMin;
                    currLevelMin -= TriggerWidth;
                    currLevelIdx++;
                }
            }
            return (currLevelIdx);
        }

        /*====================================================================================
         * Should be called at the end of every trading day (or start of a new trading day)
         * to calculate the mean and STDEV for the day that ended
         * 
         * Entry:   lastDate = last day date
         *          groupId  = group month/year
         * 
         * Return:  
         * 
         * Notes:   
         *
         *===================================================================================*/
        public void DateChanged(DateTime lastDate, DateTime groupId)
        {
            if (lastDate > DateTime.MinValue)
            {
                HistoryRecord hData = historyDB.GetRecord(lastDate, groupId.Month, groupId.Year);
                if (hData == null)
                {
                    meanErrors++;
                    if (historyDB.RecordDateValid(lastDate, groupId.Month, groupId.Year))
                    {
                        Tools.WarningMessage($"History record for H2 group {groupId.ToString("MM/yyyy")} on {lastDate.ToString("dd/MM/yyyy")} is missing");
                    }
                    else
                    {
                        Tools.ErrorMessage($"********* History request for H2 group {groupId.ToString("MM/yyyy")} on {lastDate.ToString("dd/MM/yyyy")} is invalid *********");
                    }
                    if ((CurrentMean == 0) || (meanErrors > 3))     /* if 3 consecutive days with errors, generate an exception */
                    {
                        //Tools.FatalError($"Failed to obtain or calculate mean value for H2 group {groupId.ToString("MM/yyyy")} on " + lastDate.ToString("dd/MM/yyyy"));
                    }
                }
                else
                {
                    meanErrors = 0;
                    CurrentMean = hData.mean;
                    StdDeviation = hData.stdDev;
                    TriggerWidth = StdDev * 0.85;
                }
            }
        }

        /*====================================================================================
         * Should be called at the start of a new trading day
         * to get the database spread value for the day that ended
         *
         * Entry:   date = new day date
         *          groupId  = SpreadGroup month/year
         *===================================================================================*/
        public double GetDatabaseSpread(DateTime date, DateTime groupId)
        {
            if (date > DateTime.MinValue)
            {
                HistoryRecord hData = historyDB.GetRecord(date, groupId.Month, groupId.Year);
                if (hData == null)
                {
                    if (historyDB.RecordDateValid(date, groupId.Month, groupId.Year))
                    {
                        Tools.WarningMessage($"History record for H2 group {groupId.ToString("MM/yyyy")} on {date.ToString("dd/MM/yyyy")} is missing");
                    }
                    else
                    {
                        Tools.ErrorMessage($"********* History request for H2 group {groupId.ToString("MM/yyyy")} on {date.ToString("dd/MM/yyyy")} is invalid *********");
                    }
                }
                else
                {
                    return hData.spread;
                }
            }
            return 0;
        }
    }

    /*===================================================================================
     * A spread group defines a specific month and year of H2, for example JAN 2022
     * 
     * 
     * 
     * 
     *==================================================================================*/
    public class SpreadGroup
    {
        public DateTime DateTag { get; }                        /* H2 month this group is for (e.g., 5/2024), the date is always 1 */
        public string Name { get { return(DateTag.ToString("MMM_yy")); } }
        public readonly ContractSpec ContrSpec;                 /* the specifications of contracts belonging to this spread */
        public DateTime LastSampleDate { get; private set; }    /* date & time of the last price recorded in _contractPrices array. Init to DateTime.MinValue */
        public double Value { get; private set; }               /* current h2 spread value for tis group, calculated on every tick received for the group */
        public SpreadStatus Status;                             /* status of this group spread */

        private double[] LastBidPrices;                         /* stores last known bid price of each asset */
        private double[] LastAskPrices;                         /* stores last known ask price of each asset */
        private List<Symbol> ContractSymbols;                   /* list of underlying contracts symbols */
        private DateTime minExpiryDate;                         /* earliest expiry date of contract symbols */
        private Resolution dataRes;                             /* resolution of incoming data */
        private int _availablePrices;                           /* number of available contract prices in _contractPrices */
        private double[] _contractPrices;                       /* double.MinValue indicate empty entry */
        private bool heAvailable;                               /* true if hogs (HE) contract received any ticks */
        private double databaseValue;                          /* h2 spread value on LastSampleDate taken from the database */

        public SpreadGroup(ContractSpec cSpec, DateTime name, Resolution reqRes, History histDB)
        {
        }

        public override string ToString()
        {
            return ($"SpreadGroup{{name:{Name}, minExpiry:{minExpiryDate.ToShortDateString()}, currDate:{LastSampleDate.ToShortDateString()}, prices:[{string.Join(",", _contractPrices.Select(n => n == double.MinValue ? "---" : n.ToString()))}]}}");
        }

        public bool IsIndexPriceReady(int index)
        {
            return true;
        }

        public bool IsIndexSymbolReady(int index)
        {
            return true;
        }

        public void UpdateSymbol(Symbol symbol, int index, DateTime symbolExpiry)
        {
        }

        public bool GetContractPrice(string ticker, ref double bid, ref double ask)
        {
            return true;
        }

        public Symbol GetContractSymbol(string ticker)
        {
            return (Symbol.None);
        }

        public bool UpdatePrice(double price, int index, DateTime date)
        {
            return (false);
        }
    }

    public class ContractSpec
    {
        public string Name { get; }                                         /* group name. arbitrary */
        public readonly List<string> SymbolStrings;                         /* names of contracts in the group (e.g., HE, ZC, ZM) */
        public readonly Dictionary<string, int> SymbolStringToIndex;        /* dictionary of Symbol name to index */
        public readonly List<Dictionary<int, List<int>>> ContractToGroup;   /* list of dictionaries <int, List<int>> */
        public readonly List<int> Multipliers;
        public readonly List<int> PriceToDollarMultipliers;                 /* convert price to $ value */
        // public readonly List<Symbol> FutureSymbols;                      /* continuous future symbols */

        public ContractSpec(string name)
        {
            
        }

        public int GetIndexOfTicker(string ticker)
        {
            return 0;
        }
    }
}
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion

namespace FirenzeTech
{
    public enum LogFilter
    {
        All = 0xFF,
        None = 0,
        Log = 1,
        Debug = 2,
        Warning = 4,
        Error = 8
    }

    public class TradeHours
    {
        public TimeSpan startMin;
        public TimeSpan endMin;
        public TimeSpan startHour;
        public TimeSpan endHour;
        public TradeHours(int hrStart, int minStart, int hrEnd, int minEnd)
        {
            startMin = new TimeSpan(hrStart, minStart, 0);
            endMin = new TimeSpan(hrEnd, minEnd, 0);
            startHour = new TimeSpan(hrStart + (minStart == 0 ? 0 : 1), 0, 0);
            endHour = new TimeSpan(hrEnd + (minEnd == 0 ? 0 : 1), 0, 0);
        }
    }

    public static class Tools
    {
        public static QCAlgorithm BrokerIF { get; private set; }                                  /* broker interface */
        private static int[] heValidMonths = new int[] { 2, 4, 5, 6, 7, 8, 10, 12 };
        private static int[] heListMonth = new int[] { 0, 8, 0, 10, 12, 12, 2, 4, 0, 5, 0, 6 };   /* for contract month 1-2, */

        private static readonly TradeHours tradeHoursEST = new TradeHours(9, 30, 14, 5);
        private static readonly TradeHours tradeHoursCT = new TradeHours(8, 30, 13, 5);
        private static readonly TradeHours tradeHoursUTC = new TradeHours(14, 30, 19, 5);

        private static readonly TimeZoneInfo estZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
        private static readonly Symbol hogSym = Symbol.Create(Futures.Meats.LeanHogs, SecurityType.Future, Market.CME);
        private static readonly Symbol cornSym = Symbol.Create(Futures.Grains.Corn, SecurityType.Future, Market.CME);
        private static readonly Symbol soyMealSym = Symbol.Create(Futures.Grains.SoybeanMeal, SecurityType.Future, Market.CME);
                                                                                            /* Dictionary to map contract month code to their corresponding month numbers */
        private static LogFilter logFilter = LogFilter.All;
        public static int logLevel = 1;

        /*====================================================================================
         * Set the broker interface
         * 
         * Entry:   broker = broker interface
         * 
         * Return:  none
         * 
         * Notes:   
         *
         *===================================================================================*/
        public static void Init(QCAlgorithm broker)
        {
            if (BrokerIF == null)
            {
                BrokerIF = broker;
            }
        }

        public static void LogMessage(string msg, int level = 0)
        {
            if (((logFilter & LogFilter.Log) == LogFilter.Log) && (level <= logLevel))
            {
                BrokerIF.Log(msg);
            }
        }
        public static void DebugMessage(string msg)
        {
            if ((logFilter & LogFilter.Debug) == LogFilter.Debug)
            {
                BrokerIF.Debug(msg);
            }
        }
        public static void WarningMessage(string msg)
        {
            if ((logFilter & LogFilter.Warning) == LogFilter.Warning)
            {
                BrokerIF.Debug(msg);
            }
        }
        public static void ErrorMessage(string msg)
        {
            if ((logFilter & LogFilter.Error) == LogFilter.Error)
            {
                BrokerIF.Error(msg);
            }
        }
        
        public static void SetLogFlags(string levels)
        {
            LogFilter reqLev = LogFilter.None;
            levels = levels.Trim().ToLower();
            foreach (char c in levels)
            {
                if (c == 'l') reqLev |= LogFilter.Log;
                else if (c == 'd') reqLev |= LogFilter.Debug;
                else if (c == 'w') reqLev |= LogFilter.Warning;
                else if (c == 'e') reqLev |= LogFilter.Error;
            }
            logFilter = reqLev;
        }

        public static void FatalError(string msg)
        {
            const string frame = "\n=========================================================\n";
            throw new ArgumentException(frame + msg + frame);
        }

        public static uint ElapsedMs(DateTime startTime)
        {
            return (0);
        }

        public static bool InsideTradingHoursCT(DateTime ctDate, Resolution res)
        {
            return(InsideTradingHours(ctDate, res, tradeHoursCT));
        }
        public static bool InsideTradingHoursEST(DateTime estDate, Resolution res)
        {
            return (InsideTradingHours(estDate, res, tradeHoursEST));
        }
        public static bool InsideTradingHoursUTC(DateTime utcDate, Resolution res)
        {
            if (DateTime.Now.IsDaylightSavingTime())
            {
                utcDate = utcDate.AddHours(1);
            }
            return (InsideTradingHours(utcDate, res, tradeHoursUTC));
        }
        private static bool InsideTradingHours(DateTime utcDate, Resolution res, TradeHours timeLimit)
        {
            switch (res)
            {
                case Resolution.Hour:
                    return ((utcDate.TimeOfDay >= timeLimit.startHour) && (utcDate.TimeOfDay <= timeLimit.endHour));
                case Resolution.Daily:
                    return (true);
                default:
                    return ((utcDate.TimeOfDay >= timeLimit.startMin) && (utcDate.TimeOfDay <= timeLimit.endMin));
            }
        }

        public static bool HEValidMonth(int month)
        {
            return (heValidMonths.Contains(month));
        }

        public static DateTime? HEFirstTradingDate(DateTime dt)
        {
            int listMonth = heListMonth[dt.Month];
            if (listMonth == 0)
            {
                return (null);
            }
            int totMonths = 12 - listMonth + dt.Month;
            int listYear = (dt.Year - 1) - (totMonths <= 12 ? 1 : 0);
            return (new DateTime(listYear, listMonth, 1));
        }

        public static List<Symbol> GetFuturesContractsList(string futuresCode, DateTime startDate, DateTime endDate)
        {
            Symbol futuresSym;

            if (futuresCode == Futures.Meats.LeanHogs) futuresSym = hogSym;
            else if (futuresCode == Futures.Grains.Corn) futuresSym = cornSym;
            else if (futuresCode == Futures.Grains.SoybeanMeal) futuresSym = soyMealSym;
            else return (null);

            List<Symbol> symList = new List<Symbol>();
            while (startDate <= endDate)
            {
                var contractList = BrokerIF.FutureChainProvider.GetFutureContractList(futuresSym, startDate);
                foreach (var contractX in contractList)
                {
                    if (!symList.Contains(contractX))
                    {
                        if ((contractX.ID.Date >= startDate) && (contractX.ID.Date <= endDate))
                        {
                            symList.Add(contractX);
                        }
                    }
                }
                if (startDate == endDate)
                {
                    break;
                }
                startDate += new TimeSpan(6 * 30, 0, 0, 0);
                if (startDate > endDate)
                {
                    startDate = endDate;
                }
            }
            return (symList);
        }

        public static List<DateTime> ParseDateRanges(string datesStrg)
        {
            string[] ranges = datesStrg.Split(',');
            List<DateTime> rangePairs = new List<DateTime>();
            for (int i = 0; i < ranges.Length; i++)
            {
                string[] range = ranges[i].Trim().Split('-');
                if ((range.Length <= 0) || (range.Length > 2)) return (null);
                DateTime? rStart = ParseDateStrg(range[0]);
                if (rStart == null) return (null);
                /*=======================================================
                 * if a single month, add full chain
                 *======================================================*/
                if (range.Length == 1)
                {
                    DateTime chainYear = (DateTime)rStart;
                    while (chainYear.Year < DateTime.Now.Year)
                    {
                        rangePairs.Add(chainYear);
                        rangePairs.Add(chainYear);
                        chainYear = chainYear.AddYears(1);
                    }
                }
                else
                {
                    DateTime? rEnd = String.IsNullOrEmpty(range[1]) ? DateTime.Now : ParseDateStrg(range[1]);
                    if ((rEnd == null) || (rStart > rEnd)) return (null);
                    rangePairs.Add((DateTime)rStart);
                    rangePairs.Add((DateTime)rEnd);
                }
            }
            return (rangePairs.Count == 0 ? null : rangePairs);
        }

        public static List<DateTime> ParseContractDatesParam(string datesStrg)
        {
            List<DateTime> monthList = null;
            List<DateTime> rangePairs = ParseDateRanges(datesStrg);
            if (rangePairs != null)
            {
                monthList = new List<DateTime>();
                for (int i = 0; i < rangePairs.Count; i += 2)
                {
                    DateTime rStart = rangePairs[i];
                    DateTime rEnd = rangePairs[i + 1];
                    for (int yr = rStart.Year; yr <= rEnd.Year; yr++)
                    {
                        int strMonth = (yr == rStart.Year ? rStart.Month : 1);
                        int endMonth = (yr == rEnd.Year ? rEnd.Month : 12);
                        for (int mon = strMonth; mon <= endMonth; mon++)
                        {
                            DateTime newMon = new DateTime(yr, mon, 1);
                            if (monthList.Contains(newMon))
                            {
                                return (null);
                            }
                            monthList.Add(newMon);
                        }
                    }
                }
                if (monthList.Count == 0)
                {
                    monthList = null;
                }
            }
            if (monthList != null)
            {
                monthList.Sort();
            }
            return (monthList);
        }

        private static DateTime? ParseDateStrg(string dateStrg)
        {
            try
            {
                string[] yrMon = dateStrg.Trim().Split('/');
                if (yrMon.Length != 2) return (null);
                int month = int.Parse(yrMon[0]);
                int year = int.Parse(yrMon[1]);
                if ((month < 1) || (month > 12)) return (null);
                if (year < 100)
                {
                    year += (year >= 70 ? 1900 : 2000);
                }
                return (new DateTime(year, month, 1));
            }
            catch
            {
                return (null);
            }
        }

        public static List<object> CsvToColList(string csvStrg, string colsTypes, int rowsToIgnore)
        {
            try
            {
                string[] rows = csvStrg.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
                colsTypes = colsTypes.Trim().Replace(" ", "");

                int nofRows = rows.Length;
                int nofCols = colsTypes.Length;

                List<int[]> intArrList = new List<int[]>();
                List<double[]> doubleArrList = new List<double[]>();
                List<string[]> stringArrList = new List<string[]>();
                List<DateTime[]> dateArrList = new List<DateTime[]>();

                /*=======================================================
                 * create columns arrays based on requested types
                 *======================================================*/
                int reqRows = nofRows - rowsToIgnore;
                if (reqRows <= 0)
                {
                    return(null);
                }

                string genType = colsTypes.ToLower();
                for (int colIdx = 0; colIdx < nofCols; colIdx++)
                {
                    if (genType[colIdx] == 'i') intArrList.Add(new int[reqRows]);
                    else if (genType[colIdx] == 'f') doubleArrList.Add(new double[reqRows]);
                    else if (genType[colIdx] == 'd') dateArrList.Add(new DateTime[reqRows]);
                    else if (genType[colIdx] == 's') stringArrList.Add(new string[reqRows]);
                    else if (genType[colIdx] != '-') return (null);
                }

                int intColIdx;
                int doubleColIdx;
                int strgColIdx;
                int dateColIdx;

                /*=======================================================
                 * for each row in the csv file
                 *======================================================*/
                for (int rowIdx = 0; rowIdx < reqRows; rowIdx++)
                {
                    /*=======================================================
                     * get row contents as an array of strings
                     *======================================================*/
                    string[] rowCols = rows[rowIdx + rowsToIgnore].Split(',');

                    int intRes;
                    double doubleRes;
                    DateTime dtRes;

                    intColIdx = doubleColIdx = strgColIdx = dateColIdx = 0;

                    /*=======================================================
                     * for each column in the row
                     *======================================================*/
                    for (int colIdx = 0; colIdx < nofCols; colIdx++)
                    {
                        string colStrg = rowCols[colIdx].Trim();

                        /*==================================================================
                         * Determine the column type from colStrg and parse accordingly
                         *=================================================================*/
                        switch (colsTypes[colIdx])
                        {
                            case 'i':
                            case 'I':
                                if (int.TryParse(colStrg, out intRes))
                                {
                                    intArrList[intColIdx][rowIdx] = intRes;
                                }
                                else if (string.IsNullOrEmpty(colStrg) && (colsTypes[colIdx] == 'i'))
                                {
                                    intArrList[intColIdx][rowIdx] = int.MinValue;
                                }
                                else
                                {
                                    return (null);
                                }
                                intColIdx++;
                                break;

                            case 'f':
                            case 'F':
                                if (double.TryParse(colStrg, NumberStyles.Float, CultureInfo.InvariantCulture, out doubleRes))
                                {
                                    doubleArrList[doubleColIdx][rowIdx] = doubleRes;
                                }
                                else if (string.IsNullOrEmpty(colStrg) && (colsTypes[colIdx] == 'f'))
                                {
                                    doubleArrList[doubleColIdx][rowIdx] = double.MinValue;
                                }
                                else
                                {
                                    return (null);
                                }
                                doubleColIdx++;
                                break;

                            case 's':
                            case 'S':
                                if (string.IsNullOrEmpty(colStrg) && (colsTypes[colIdx] == 's'))
                                {
                                    stringArrList[strgColIdx][rowIdx] = colStrg;
                                }
                                else
                                {
                                    return (null);
                                }
                                strgColIdx++;
                                break;

                            case 'd':
                            case 'D':
                                if (DateTime.TryParse(colStrg, out dtRes))
                                {
                                    dateArrList[dateColIdx][rowIdx] = dtRes;
                                }
                                else if (string.IsNullOrEmpty(colStrg) && (colsTypes[colIdx] == 'd'))
                                {
                                    dateArrList[dateColIdx][rowIdx] = DateTime.MinValue;
                                }
                                else
                                {
                                    return (null);
                                }
                                dateColIdx++;
                                break;

                            case '-':
                                break;

                            default:
                                return (null);
                        }
                    }
                }
                /*=======================================================
                 * create output list
                 *======================================================*/
                List<object> result = new List<object>();
                intColIdx = doubleColIdx = strgColIdx = dateColIdx = 0;
                for (int colIdx = 0; colIdx < nofCols; colIdx++)
                {
                    switch (colsTypes[colIdx])
                    {
                        case 'i':
                        case 'I':
                            result.Add((object)intArrList[intColIdx++]);
                            break;
                        case 'f':
                        case 'F':
                            result.Add((object)doubleArrList[doubleColIdx++]);
                            break;
                        case 's':
                        case 'S':
                            result.Add((object)stringArrList[strgColIdx++]);
                            break;
                        case 'd':
                        case 'D':
                            result.Add((object)dateArrList[dateColIdx++]);
                            break;
                        default:
                            break;
                    }
                }
                return (result);
            }
            catch
            {
                return (null);
            }
        }
    }
}