Overall Statistics
Total Trades
5
Average Win
0%
Average Loss
-0.61%
Compounding Annual Return
-71.035%
Drawdown
1.500%
Expectancy
-1
Net Profit
-1.013%
Sharpe Ratio
-15.494
Probabilistic Sharpe Ratio
0%
Loss Rate
100%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
-0.145
Beta
-0.655
Annual Standard Deviation
0.019
Annual Variance
0
Information Ratio
-15.616
Tracking Error
0.033
Treynor Ratio
0.448
Total Fees
$33.49
Estimated Strategy Capacity
$1100000.00
Lowest Capacity Asset
IDXX R735QTJ8XC9X
namespace QuantConnect
{

    public class PerfMetricsCalculator
    {

        // ====================================================
        // Get Current Drawdown
        // ====================================================
        public static decimal GetCurrentDrawdownPercent(IEnumerable<TradeBar> equity_curve)
        {
            /*
            var hwm = max(equity_curve);
            var current = equity_curve[-1];
            return (hwm - current) / hwm * 100;
            */
            return 0.0m;
        }

        // ====================================================
        // Get Max Drawdown
        // ====================================================
        public static decimal GetMaxDrawdownPercent(IEnumerable<TradeBar> equity_curve)
        {
            /*
            var i = np.argmax(np.maximum.accumulate(equity_curve) - equity_curve);
            if (equity_curve[::i].size == 0) {
                return np.nan;
            }
            var j = np.argmax(equity_curve[::i]);
            return abs(equity_curve[i] / equity_curve[j] - 1) * 100;
            */
            return 0.0m;
        }

        // ====================================================
        // Calculates the return series of a given time series.
        // The first value will always be NaN.
        // ====================================================
        public static decimal calculate_return_series(IEnumerable<TradeBar> series)
        {
            /*
            var shifted_series = series.shift(1, axis: 0);
            return series / shifted_series - 1;
            */
            return 0.0m;
        }

        // ====================================================
        // Sortino ratio
        // ====================================================
        public static decimal GetSortinoRatio(object pdseries)
        {
            // returns_series   = PerfMetricsCalculator.calculate_return_series(pdseries)
            // returns_mean = returns_series.mean()  # calculate the mean  M1 of returns
            // downside_returns = returns_series.where(returns_series < 0) #... # select all the negative returns
            // downside_std = downside_returns.std()   # calculate S1 (std) of these negative returns
            // Sortino = M1/S1 * sqrt(of number of bars)
            // number of bars is the total number of bars
            // -------------------------------------------
            // sortino_ratio = returns_mean/downside_std * np.sqrt(len(pdseries))  
            // algo.Log(f"{algo.Time} -- {symbol} Returns \n---------\n{str(returns_series)}")
            // algo.Log(f"{algo.Time} -- {symbol} Mean of Returns \n---------\n{str(returns_mean)}")
            // algo.Log(f"{algo.Time} -- {symbol} Downside Returns \n---------\n{str(downside_returns)}")
            // algo.Log(f"{algo.Time} -- {symbol} Downside Std. Dev :{str(downside_std)}")
            // algo.Log(f"{algo.Time} -- {symbol} \n -------------- \n Sortino Ratio:  {str(sortino_ratio)}\n#############################")
            // -----------------------------------------------------------------------------                
            // return sortino_ratio
            return 0.0m;
        }
    }
}
using QuantConnect.Algorithm;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using System;
using System.Collections.Generic;

namespace QuantConnect
{

    public enum PerfMetricTypeEnum
    {
        DAILY_RATIO,
        ROC_MOMENTUM,
        SHARPE,
        DRAWDOWN,
        RET_DD_RATIO,
        THARP_EXP,
        SORTINO,
    }

    public class PerfMetricsManager
    {

        public QCAlgorithm algo;
        public PerfMetricTypeEnum PreferredRankingMetric;
        public PerfMetricTypeEnum PreferredSelectionMetric;
        public Dictionary<PortfolioSelectionTypeEnum, SimulatedPosition> SimulatedPositions;

        public PerfMetricsManager(QCAlgorithm algorithm, PerfMetricTypeEnum preferredRankingMetric, PerfMetricTypeEnum preferredSelectionMetric)
        {
            this.algo = algorithm;
            this.PreferredRankingMetric = preferredRankingMetric;
            this.PreferredSelectionMetric = preferredSelectionMetric;
            this.SimulatedPositions = new Dictionary<PortfolioSelectionTypeEnum, SimulatedPosition>
            {
            };
            return;
        }

        // ------------------------------
        // GetBestSelectionMethod
        // Called by PortfolioManager
        // ------------------------------
        // Responsible for determining which is the best selection method.
        // It Returns 'top' or 'bottom'. 
        // It determines which to send based on how the corresponding stocks performed.
        // ie: It measures the performance of the currently top ranked stock, and 
        // compares it with the the performance of the bottom ranked stock. 
        // if the bottommost stock performed better, then recommend the bottommost 
        // =========================================================================
        public PortfolioSelectionTypeEnum GetBestSelectionMethod()
        {
            PortfolioSelectionTypeEnum bestSelectionMethod = PortfolioSelectionTypeEnum.SELECT_TOP;
            decimal bestSimulatedPerfValue = 0;
            var comparisonLookbackPeriod = Convert.ToInt32(algo.GetParameter("selCompareLookback"));
            // Check all simulated positions and see which performed best 
            // 'SimulatedPositions' is a dictionary of trades we could have made.
            // each one corresponds to a selection method. So, for example: 
            //    SimulatedPositions['top']    = simulatedPosition // object for AAPL (best perf) 
            //    SimulatedPositions['bottom'] = simulatedPosition // object for MSFT (worst perf) 
            //        
            // For each of these, we compare the performance of the stock. 
            // the performance metric we use here is determined by an external param 
            // -------------------------------------------------------------------
            foreach (var selectionMethodKey in this.SimulatedPositions.Keys)
            {
                var simulatedPosition = this.SimulatedPositions[selectionMethodKey];
                var symbol = simulatedPosition.Symbol;
                var perfMetricLabel = this.PreferredSelectionMetric;
                var simulatedPerfValue = this.GetPerfMetricValue(symbol, perfMetricLabel);
                // todo: if no perf value, handle this. should never happen
                if (simulatedPerfValue == null)
                {
                    simulatedPerfValue = 0;
                }
                this.algo.Debug("[INFO " + algo.Time + "] \t\t| --> Method '" + selectionMethodKey + "' scored " + Math.Round(simulatedPerfValue,3) + " for " + perfMetricLabel);
                // Keep track of which was best performer
                if (simulatedPerfValue > bestSimulatedPerfValue)
                {
                    bestSimulatedPerfValue = simulatedPerfValue;
                    bestSelectionMethod = selectionMethodKey;
                }
            }
            this.algo.Debug("[INFO " + algo.Time + "] \t| --> Using best selection method: " + bestSelectionMethod);
            return bestSelectionMethod;
        }

        // ==============================================================================
        // Store data for simulated position.  
        // ------------------------------
        // Here, we keep track of a 'simulated' position. 
        // We store the symbol, the entry price, and the entry time
        // These will be used later when comparing how the simulated position performed. 
        // ---------------------------
        // Note: This is called by the portfolio manager, after it has decided 
        // which components to trade.
        // ==============================================================================
        public void AddSimulatedPosition(string symbol, PortfolioSelectionTypeEnum selectionMethod)
        {
            var entryPrice = this.algo.Securities[symbol].Price;
            var entryTime = this.algo.Time;
            // todo: try using a rollingwindo
            this.SimulatedPositions[selectionMethod] = new SimulatedPosition(symbol, entryPrice, entryTime);
        }

        // ======================================================================
        // Reset simulated positions. 
        // Called by portfolio manager after it determined what stock to buy.
        // (it uses information from the previous simulation to do so, via the get best selection method)
        // Its at this point that we start a new simulation. 
        // ======================================================================
        public void ResetSimulatedPositions()
        {
            SimulatedPositions.Clear();
        }

        // ======================================================================
        // GetPerfMetricValue
        // Called by PortfolioManager when evaluating universe stocks
        // Called by self.GetBestSelectionMethod, when determining whether we are going with 'top' or 'bottom' 
        // ------------------------------
        // Calculates the specified performance metric for the stock symbol supplied.  
        // ======================================================================
        public decimal GetPerfMetricValue(string symbol, PerfMetricTypeEnum perfMetric)
        {
            object row;
            object index;
            // Get current price
            // ------------------
            var currPrice = this.algo.Securities[symbol].Price;
            var resolutions = new List<Resolution>();
            resolutions.Add(Resolution.Minute);
            resolutions.Add(Resolution.Hour);
            resolutions.Add(Resolution.Daily);
            var period = Convert.ToInt32(this.algo.GetParameter("rankingMetricOptPeriod"));
            Resolution resolution = resolutions[Convert.ToInt32(this.algo.GetParameter("rankingMetricResolution"))];
            // --------------------
            // Measure DAILY RATIO 
            // --------------------
            if (perfMetric == PerfMetricTypeEnum.DAILY_RATIO)
            {
                // Get History used to calculate performnance
                var history_raw = this.algo.History(symbol, 2, Resolution.Daily);
                List<TradeBar> history = new List<TradeBar>();
                foreach (TradeBar bar in history_raw)
                    history.Add(bar);

                if (history.Count == 0)
                {
                    this.algo.Debug(algo.Time + " [ERROR]][GetPerfMetricValue] " + symbol + " History is empty");
                    return Decimal.MinValue;
                }
                else
                {
                    var lastClosePrice = history[1].Close;
                    var prevLastClosePrice = history[0].Close;
                    var dailyPriceRatio = lastClosePrice / prevLastClosePrice;
                    return dailyPriceRatio;
                }
            }
            // -----------------------
            // Measure ROC / MOMENTUM
            // -----------------------
            if (perfMetric == PerfMetricTypeEnum.ROC_MOMENTUM)
            {
                // Get History used to calculate performnance
                var history_raw = this.algo.History(symbol, 2, Resolution.Daily);
                List<TradeBar> history = new List<TradeBar>();
                foreach (TradeBar bar in history_raw)
                    history.Add(bar);
                if (history.Count == 0)
                {
                    this.algo.Debug(algo.Time + " [ERROR]][GetPerfMetricValue] " + symbol + " History is empty");
                    return Decimal.MinValue;
                }
                else
                {
                    var rocIndicator = new RateOfChange(2);
                    foreach (var bar in history)
                    {
                        rocIndicator.Update(bar.Time, bar.Close);
                    }
                    return rocIndicator.Current.Value;
                }
            }
            else if (perfMetric == PerfMetricTypeEnum.SHARPE)
            {
                // -----------------------
                // Measure SHARPE
                // -----------------------
                // Get 1 min bars, used to calculate Sharpe
                var history_raw = this.algo.History(symbol, period, resolution);
                List<TradeBar> history = new List<TradeBar>();
                foreach (TradeBar bar in history_raw)
                    history.Add(bar);
                if (history.Count == 0)
                {
                    this.algo.Debug(algo.Time + " [ERROR][GetPerfMetricValue] " + symbol + " History is empty");
                    return Decimal.MinValue;
                }
                else
                {
                    var sharpeIndicator = new SharpeRatio(period);
                    foreach (var bar in history)
                    {
                        decimal typicalPrice = (bar.High + bar.Low + bar.Close) / 3;
                        sharpeIndicator.Update(bar.Time, typicalPrice);
                    }
                    return sharpeIndicator.Current.Value;
                }
            }
            else if (perfMetric == PerfMetricTypeEnum.DRAWDOWN)
            {
                // ----------------------------
                // Measure DRAWDOWN PERCENTAGE
                // ----------------------------
                // # Get past bars, used to calculate Drawdown
                // history = self.algo.History(symbol, 390, Resolution.Minute)
                // if history.empty or 'close' not in history.columns:
                //     self.algo.Debug(f"{self.algo.Time} [ERROR] {symbol} History is empty")
                //     return None
                // else:
                //     historySeries = pd.Series()
                //     for index, row in history.loc[symbol].iterrows():
                //         typicalPrice = (row['high']+row['low']+row['close'])/3
                //         historySeries = historySeries.append(pd.Series(typicalPrice, index=[index]))
                //     ddownPct = PerfMetricsCalculator.GetMaxDrawdownPercent(historySeries)
                //     ddownPctScore = 100 - ddownPct 
                //     return ddownPctScore
                return 0;
            }
            else if (perfMetric == PerfMetricTypeEnum.RET_DD_RATIO)
            {
                // --------------------------
                // RETURN / MAXDRAWDOWN RATIO
                // --------------------------
                // # Calculate Return
                // # --------------------
                // # Get History used to calculate performnance
                // history = self.algo.History(symbol, 2, Resolution.Daily)
                // if history.empty or 'close' not in history.columns:
                //     self.algo.Debug(f"{self.algo.Time} [ERROR] {symbol} History is empty")
                //     return None
                // else:
                //     rocIndicator = RateOfChange(2)
                //     for index, row in history.loc[symbol].iterrows():
                //         # todo: stop creating tradebars where we dont have to
                //         tradeBar = TradeBar(index, symbol, row['open'], row['high'], row['low'], row['close'], row['volume'], timedelta(1))
                //         rocIndicator.Update(tradeBar.Time, tradeBar.Close)  
                //     returnPct = rocIndicator.Current.Value
                // # Calculate Draw Down
                // # ------------------------
                // # calc drawdown over minute time frame 
                // history = self.algo.History(symbol, 390, Resolution.Minute)
                // if history.empty or 'close' not in history.columns:
                //     self.algo.Debug(f"{self.algo.Time} [ERROR] {symbol} History is empty")
                //     return None
                // else:
                //     historySeries = pd.Series()
                //     for index, row in history.loc[symbol].iterrows():
                //         typicalPrice = (row['high']+row['low']+row['close'])/3
                //         historySeries = historySeries.append(pd.Series(typicalPrice, index=[index]))
                //     ddownPct = PerfMetricsCalculator.GetMaxDrawdownPercent(historySeries)
                // return returnPct / ddownPct
                return 0;
            }
            else if (perfMetric == PerfMetricTypeEnum.THARP_EXP)
            {
                // --------------------------
                // THARP EXPECTANCY
                // --------------------------
                // To calculate expectancy, treat each bar as a trade
                // open: buy price, close: sell price
                // -------------------------------------------------------------
                // barCount  = 6 # since looking at hourly, go back one day (~6 hours)
                // history = self.algo.History(symbol, barCount, Resolution.Hour)
                //
                // if history.empty or 'close' not in history.columns:
                //     self.algo.Debug(f"{self.algo.Time} [ERROR] {symbol} History is empty")
                //     return None
                //                
                // else:
                //     winsArr   = []
                //     lossArr   = []
                //     winCount  = 0.0
                //     lossCount = 0.0
                //     for index, row in history.loc[symbol].iterrows():
                //         # Treat each bar like a trade
                //         # ----------------------------
                //         barReturn   = row['close'] - row['high']
                //         returnRatio = abs( barReturn / row['close'] )
                //         if( barReturn > 0 ):
                //             winsArr.append( returnRatio ) 
                //             winCount = winCount + 1 
                //         else:
                //             lossArr.append( returnRatio )
                //             lossCount = lossCount + 1 
                //
                //     probWin  = winCount / barCount 
                //     probLoss = lossCount / barCount 
                //
                //     avgWin  =  (sum(winsArr) / len(winsArr)) if (len(winsArr) > 0) else 0  
                //     avgLoss =  (sum(lossArr) / len(lossArr)) if (len(lossArr) > 0) else 0
                //
                //                
                //     tharpExp = (probWin * avgWin) - (probLoss * avgLoss)
                //                
                //     return tharpExp
                return 0;
            }
            else if (perfMetric == PerfMetricTypeEnum.SORTINO)
            {
                // --------------------------
                // SORTINO RATIO
                // --------------------------
                // https://towardsdatascience.com/sharpe-ratio-sorino-ratio-and-calmar-ratio-252b0cddc328
                // https://github.com/chrisconlan/algorithmic-trading-with-python/blob/master/src/pypm/metrics.py
                // -------------------------------------------------------------
                // # Get past bars, used to calculate sortino
                // # ---------------------------------------------
                // history = self.algo.History(symbol, period, resolution)
                //
                // if history.empty or 'close' not in history.columns:
                //     self.algo.Debug(f"{self.algo.Time} [ERROR] {symbol} History is empty")
                //     return None
                // 
                // else:
                // 
                //     historySeries = pd.Series()
                //     for index, row in history.loc[symbol].iterrows():
                //         typicalPrice = (row['high']+row['low']+row['close'])/3
                //         historySeries = historySeries.append(pd.Series(typicalPrice, index=[index]))
                //     sortinoRatio = PerfMetricsCalculator.GetSortinoRatio(historySeries)
                //     return sortinoRatio
                return 0;
            }
            else
            {
                return 0;
            }
        }
    }

    public enum PortfolioSelectionTypeEnum
    {
        SELECT_TOP,
        SELECT_BOT
    }

    public class SimulatedPosition
    {

        public string Symbol;
        public object EntryPrice;
        public object EntryTime;

        public SimulatedPosition(string symbol, object entryPrice, object entryTime)
        {
            this.Symbol = symbol;
            this.EntryPrice = entryPrice;
            this.EntryTime = entryTime;
        }
    }
}
using QuantConnect.Algorithm;
using QuantConnect.Data.UniverseSelection;
using System.Collections.Generic;
using System.Linq;

namespace QuantConnect
{

    public class PortfolioManager
    {

        public QCAlgorithm algo;
        public List<PortfolioComponent> PortfolioComponents;
        public PerfMetricTypeEnum PreferredRankingMetric;
        public PerfMetricTypeEnum PreferredSelectionMetric;
        public PerfMetricsManager PerfMetricsManager;
        public PortfolioSelectionTypeEnum SelectionMethodUsed;

        public object lastEntryDate;
        public object lastRebalanceDate;

        public List<Symbol> UniverseSymbols;

        public PortfolioManager(QCAlgorithm algo, PerfMetricTypeEnum preferredRankingMetric = PerfMetricTypeEnum.ROC_MOMENTUM, PerfMetricTypeEnum preferredSelectionMetric = PerfMetricTypeEnum.ROC_MOMENTUM)
        {
            this.algo = algo;
            // Portfolio Compoments List
            // Note: This is where the actual ranked 'portfolio' is stored.
            // ------------------------------
            this.PortfolioComponents = new List<PortfolioComponent>();
            // Initialize Performance tracking vars
            // -------------------------------------
            this.PreferredRankingMetric = preferredRankingMetric;
            this.PreferredSelectionMetric = preferredSelectionMetric;
            this.PerfMetricsManager = new PerfMetricsManager(algo, preferredRankingMetric, preferredSelectionMetric);
            this.SelectionMethodUsed = PortfolioSelectionTypeEnum.SELECT_TOP;
            // Keep track of dates    
            // -------------------    
            this.lastEntryDate = null;
            this.lastRebalanceDate = null;
            // Keep track of universe symbols
            // ----------------------------------
            this.UniverseSymbols = new List<Symbol>();
        }

        // -------------------------------
        // Get the next stock(s) to buy
        // -------------------------------
        // Core function that answers the question "what should we buy today?"
        // Called from main, when we want to open new positions.
        // 
        // 1. Get the 'best seelction method' from the perf metrics manager
        //    This will either be 'top' or 'bottom'
        //
        // 2. Return the corresponding stock from our ranked  
        //   2.a If the best selection method is 'top', return the topmost stock
        //   2.b If the best selection method is 'bottom', return the bottom stock
        //
        // ======================================================================
        public List<PortfolioComponent> GetNextStocksToBuy()
        {
            // self.algo.Debug(f"[INFO {self.algo.Time}] \t| Get Portfolio Components to trade.")
            var selectedPortfolioComponents = new List<PortfolioComponent>();
            if (this.PortfolioComponents.Count == 0)
            {
                this.algo.Debug("[INFO " + algo.Time + "] \t| No Portfolio Components. Nothing To Trade.");
            }
            else
            {
                // Ask the Perf Metrics Mgr which selection method is the best
                // This will be 'top' or 'bottom' (of the ranked list)
                // ---------------------------------------------------------------------
                var bestSelectionMethod = this.PerfMetricsManager.GetBestSelectionMethod();
                this.SelectionMethodUsed = bestSelectionMethod;
                // Once we have the selction method, select the corresponding stock
                // currently we are returning an array, to future-proof this logic
                // (eventualluy we may return an array of stocks, not just a single one)
                // ---------------------------------------------------------------------
                if (bestSelectionMethod == PortfolioSelectionTypeEnum.SELECT_TOP)
                {
                    selectedPortfolioComponents = new List<PortfolioComponent> {
                        this.PortfolioComponents[0]
                    };
                }
                else if (bestSelectionMethod == PortfolioSelectionTypeEnum.SELECT_BOT)
                {
                    selectedPortfolioComponents = new List<PortfolioComponent> {
                        this.PortfolioComponents[this.PortfolioComponents.Count - 1]
                    };
                }
                else
                {
                    this.algo.Debug("[INFO " + algo.Time + "] \t| No 'Best selection Method'. Nothing To Trade");
                    this.algo.Quit();
                    return new List<PortfolioComponent>();
                }
                // Now that we've selected our assets to trade, lets keep track 
                // of whatever we did NOT select, so we can compare performance later
                // todo: try using rollingwindow in addSimulatedPosition, so we can measure avg 
                // -----------------------------------------------------------------------------
                this.PerfMetricsManager.ResetSimulatedPositions();
                this.PerfMetricsManager.AddSimulatedPosition(this.PortfolioComponents[0].Symbol, PortfolioSelectionTypeEnum.SELECT_TOP);
                this.PerfMetricsManager.AddSimulatedPosition(this.PortfolioComponents[this.PortfolioComponents.Count - 1].Symbol, PortfolioSelectionTypeEnum.SELECT_BOT);
            }
            return selectedPortfolioComponents;
        }

        // ======================================================================
        // Rebalancing logic
        // ------------------
        // Simple function to measure stocks' performance and rank them accordingly
        // 1. Get our portfolio universe stocks (current nasdaq 100 stocks)
        // 2. Measure 'performance' of each stock (according to the metric)
        // 3. Rank portfolio components based on performance
        // ======================================================================
        public void RebalancePortfolio()
        {
            // 1. Measure performance for each symbol in the universe
            // ---------------------------------------------------
            this.PortfolioComponents = new List<PortfolioComponent>();
            foreach (Symbol universeSymbol in this.UniverseSymbols)
            {
                MeasureAndAddPortfolioComponent(universeSymbol);
            }
            // Rank components based on value of preferred metric        
            // ---------------------------------------------------
            var list = (from x in PortfolioComponents
            			where x.GetPerfMetricValue(PreferredRankingMetric) != Decimal.MinValue
                    	orderby x.GetPerfMetricValue(PreferredRankingMetric) descending
                    	select x);
            
            List<PortfolioComponent> copy = new List<PortfolioComponent>();
            
            foreach(var obj in list)
            {
                copy.Add(obj);
            }
            
            this.PortfolioComponents.Clear();
            this.PortfolioComponents = copy;
        }

        // ======================================================================
        // Measure stock performance & save it
        // ------------------------------------
        // Measure performance for given stock then add it as a portfolio component to our portfolio
        // 1. Calculate performance metric for given stock. 
        // 2. Create a new 'PortfolioComponent' for this stock and, inside it, store value of perf metric  
        // 3. Add the portfolio Component to portfolio Component List
        // =============================================================================
        public void MeasureAndAddPortfolioComponent(string universeSymbol)
        {
            var perfVal = this.PerfMetricsManager.GetPerfMetricValue(universeSymbol, this.PreferredRankingMetric);
            if (perfVal != null)
            {
                // store the calculated performance in a folio component object
                var folioComponent = new PortfolioComponent(universeSymbol);
                folioComponent.AddPerfMetricValue(perfVal, this.PreferredRankingMetric);
                this.PortfolioComponents.Add(folioComponent);
            }
        }

        // ======================================================================
        // On Securities Changed Event Handler
        // ------------------------------------
        // Simply add security to our list of universe symbols
        // Invoked from Main
        // ======================================================================
        public void OnSecuritiesChanged(SecurityChanges changes)
        {
            // Remove Security from universe symbols
            // --------------------------------------
            foreach (var security in changes.RemovedSecurities)
            {
                this.UniverseSymbols.Remove(security.Symbol);
            }
            // Add Security to universe symbols
            // --------------------------------------
            // todo: need to make sure spy isnt included in the universe
            foreach (var security in changes.AddedSecurities)
            {
                if (!this.UniverseSymbols.Contains(security.Symbol))
                {
                    if (security.Symbol.Value != "SPY")
                    {
                        // make sure it's not spy
                        this.UniverseSymbols.Add(security.Symbol);
                    }
                }
            }
        }
    }

    public class PortfolioComponent
    {

        public string Symbol;
        public int Weight;
        public decimal EntryPrice;
        public decimal ExitPrice;
        public Dictionary<PerfMetricTypeEnum, decimal> PerfMetricValues;

        public PortfolioComponent(string holdingSymbol, int holdingWeight = 1)
        {
            //# Config w/ Initializing values 
            this.Symbol = holdingSymbol;
            this.Weight = holdingWeight;
            this.EntryPrice = 0;
            this.ExitPrice = 0;
            // A dictionary of metric values,
            // eg 'ROC', 'DDOWN', 'SHARPE', etc
            // -----------------------------------
            this.PerfMetricValues = new Dictionary<PerfMetricTypeEnum, decimal>();
        }

        // ================================================
        public void AddPerfMetricValue(decimal perfMetricValue, PerfMetricTypeEnum perfMetricLabel)
        {
            this.PerfMetricValues[perfMetricLabel] = perfMetricValue;
        }

        // ================================================
        public decimal GetPerfMetricValue(PerfMetricTypeEnum perfMetricLabel)
        {
            return this.PerfMetricValues[perfMetricLabel];
        }

        // ================================================
        public string GetAllPerfMetricsAsStringList()
        {		
        	string o = "[";
        	foreach(PerfMetricTypeEnum key in PerfMetricValues.Keys)
        		o += "(" + key + ", " + PerfMetricValues[key] + "), ";
        		
        	return o.Substring(0, o.Count() - 2) + "]";
        }
    }
}
/*
########################################################
# 
# OLPS Experiment #1 
# ---------------------
# Every day, buy the topmost or bottommost performing stock from the Nasdaq100, and hold it for 1 day or less.
# Determine whether to buy the topmost ranked or bottommost ranked, based on which performed best yesterday. 
# 
#
# Detailed Flow:
# ------------------------
# 1. Every night, populate portfolio with current Nasdaq 100 stocks
#    https://en.wikipedia.org/wiki/Nasdaq-100
#
# 2. Open: Every day at predetermined ( x 'mins after open' )
#    2.1 'Rebalance' Portfolio:
#       2.1.a Calculate performance metric for each stock
#             ROC, Sharpe, Sortino... (depends on external param) 
#       2.1.b Rank the portfolio according to that metric 
#
# 3. Sort stocks by this Metric
#
# 4. At designated time:
#  
# 
# Todo: Consider: 
# ------------------------------
#   Consider volume based metric
#   Consider metrics from 101 alphas : https://arxiv.org/ftp/arxiv/papers/1601/1601.00991.pdf
#   Consider NOT switching the selection method. Always pick highest sharpe, regardless
#   Consider NOT switching the selection method, unless return was negative
#   Consider adding 5% stop loss
#   Consider using fimancial hackers relative strength as a perf metric
#   Consider using tharp expectancy for past x bars  -  (imagine you buy at open, sell at close). Can try daily and 4hr bars
# 
# Notes from u/Econophysicist1
# ------------------------------
#    https://www.reddit.com/r/algotrading/comments/mtp8b5/beating_the_market_with_the_simple_possible/
#    I took the stocks in NASDAQ 100 and then sorted the stocks in terms of their price ratio 
#    (the price of the stock today vs yesterday). Then I used both a mean return and momentum following strategy.
#    Instead of weights, I selected the best performing and worst performing stock according to 
#    this simple-minded metric. By themselves, each of these strategies does not work very well (try it).
#    But then you can optimize (using the walk-forward optimization) between the two strategies 
#    (mean return and momentum). Basically test continuously on short time scales which one is doing better 
#    (mean return or momentum following) in recent market conditions and select the stock from the 
#    best performing strategy in that testing interval. Such a simple and almost parameterless strategy gives
#    surprisingly good results: a cool 5x in about 3 years, which is much better than most ETFs.
#    Not necessarily the best algo trading in the world but a decent Sharpe and gains and an exercise 
#    to demonstrate how a simple, robust approach can give a strong performance that outperforms easily the 
#    market (the fully market efficiency theory is clearly wrong in short time scales).
#    
#    Try this exercise yourself and I think you will gain a lot of intuition.
#    Let me know if you need help in setting up the algo.
#
#
########################################################
*/

using QuantConnect.Data.UniverseSelection;
using System;
using System.Collections.Generic;

namespace QuantConnect.Algorithm.CSharp
{

    public class OLPSExperimentOne : QCAlgorithm
    {

        public int perfMetricRankingIndex;
        public int perfMetricSelectionIndex;

        public PerfMetricTypeEnum PreferredRankingMetric;
        public PerfMetricTypeEnum PreferredSelectionMetric;

        public int entryAtMinsAfterOpen;
        public int exitAtMinsAfterOpen;
        private string file;

        public int MonthOfLastUniverseUpdate { get; private set; }
        public PortfolioManager PortfolioManager { get; private set; }

        public override void Initialize()
        {
            this.InitBacktestParams();
            this.InitManagers();
            this.InitUniverse();
            this.ScheduleRoutines();
        }

        // ======================================================================
        // Intiialize Backtest Parameters
        // ======================================================================
        public void InitBacktestParams()
        {
            this.SetStartDate(2019, 11, 11);
            this.SetEndDate(2019, 11, 13);
            this.SetCash(100000);
            // Get the perf metrics params
            // -----------------------------
            var perfMetricsArray = new List<PerfMetricTypeEnum> {
                PerfMetricTypeEnum.DAILY_RATIO,
                PerfMetricTypeEnum.ROC_MOMENTUM,
                PerfMetricTypeEnum.SHARPE,
                PerfMetricTypeEnum.DRAWDOWN,
                PerfMetricTypeEnum.RET_DD_RATIO,
                PerfMetricTypeEnum.THARP_EXP,
                PerfMetricTypeEnum.SORTINO
            };
            var perfMetricRankingIndex = Convert.ToInt32(this.GetParameter("perfMetric_ranking"));
            var perfMetricSelectionIndex = Convert.ToInt32(this.GetParameter("perfMetric_selection"));
            this.PreferredRankingMetric = perfMetricsArray[perfMetricRankingIndex];
            this.PreferredSelectionMetric = perfMetricsArray[perfMetricSelectionIndex];
            this.entryAtMinsAfterOpen = Convert.ToInt32(this.GetParameter("entryAtMinsAfterOpen"));
            this.exitAtMinsAfterOpen = Convert.ToInt32(this.GetParameter("exitAtMinsAfterOpen"));
        }

        // ======================================================================
        // Intiialize Managers
        // ---------------------
        // Currently only initializes portfolio manager. 
        // Will also use OptionsExecutionManager
        // ======================================================================
        public void InitManagers()
        {
            this.PortfolioManager = new PortfolioManager(this, this.PreferredRankingMetric, this.PreferredSelectionMetric);
        }

        // ======================================================================
        // Initialize the Universe
        // ------------------------
        // Called from initialzie method
        // Sets up external data source, and universe settings.
        // Sets the universe selection handler as well.
        // ======================================================================
        public void InitUniverse()
        {
            // External data source for nasdaq100 data   
            // -----------------------------------------
            // TODO: Move to SelectNasdaqHundred for live run
            this.file = this.Download("https://www.dropbox.com/s/10z31a18iwebk2z/NASDAQ_100_Component_Weightings_Workfile_NASDAQ.csv?dl=1");
            // Universe settings
            // ------------------------
            this.MonthOfLastUniverseUpdate = -1;
            this.UniverseSettings.Resolution = Resolution.Hour;
            this.AddUniverse(this.SelectNasdaqHundred);
        }

        // ======================================================================
        // Select the current NASDAQ 100 stocks (from the downloaded file)
        // ======================================================================
        public IEnumerable<Symbol> SelectNasdaqHundred(IEnumerable<CoarseFundamental> coarse)
        {
            if (this.MonthOfLastUniverseUpdate != this.Time.Month)
            {
                // define map structure
                Dictionary<string, List<string>> symbols = new Dictionary<string, List<string>>();
                // store list for location of date relations
                List<string> dates = new List<string>();
                
                // add all data from csv into map
                string[] lines = this.file.Split('\n');
                // init default values
                string[] header = lines[0].Split(',');
                foreach(string s in header) {
                	// if first container
                	if(String.Equals(s, "Ticker"))
                		continue;
                		
                	// define new list
                	symbols[s] = new List<string>();
                	// add to date storer
                	dates.Add(s);
                }
                
                // add all data to csv
                for(int i = 1; i < lines.Count(); i++) {
                	string[] line = lines[i].Split(",");
                	// symbol should be in location 0
                	string symbol = line[0];
                	
                	// check dates and add if above 0
                	for(int k = 1; k < line.Count(); k++) {
                		// if empty then we know value is 0
                		if(String.Equals(line[k], ""))
                			continue;
                		// otherwise add to list for month
                		symbols[dates[k - 1]].Add(symbol);
                	}
                }
                
                
                // get current month
                string month = Time.ToString("MM");
                string time = Time.Year.ToString() + month;
                
                return (from stock in coarse
                	where symbols[time].Contains(stock.Symbol.ToString().Split(' ')[0])
        			select stock.Symbol);
            }
            else
            {
                return Universe.Unchanged;
            }
        }

        // ==============================================================
        // On Securities Changed Event. Delegated to Portfolio Manager
        // ==============================================================
        public override void OnSecuritiesChanged(SecurityChanges changes)
        {
            this.PortfolioManager.OnSecuritiesChanged(changes);
        }

        // ======================================================================
        // Schedule Routines (similar to chron jobs). Called from Initialize().
        // ======================================================================
        public void ScheduleRoutines()
        {
            this.AddEquity("SPY", Resolution.Daily);
            // Schedule Entry routine to run every day X mins after market open
            // ------------------------------------------------------------------
            this.Schedule.On(this.DateRules.EveryDay(), this.TimeRules.AfterMarketOpen("SPY", this.entryAtMinsAfterOpen), this.RebalanceThenOpenPositions);
            // Schedule Exit routine to run every day Y mins after market open
            // ------------------------------------------------------------------
            this.Schedule.On(this.DateRules.EveryDay(), this.TimeRules.AfterMarketOpen("SPY", this.exitAtMinsAfterOpen), this.ClosePositions);
        }

        // ======================================================================
        // Rebalance and Open Positions                              
        // ======================================================================
        public void RebalanceThenOpenPositions()
        {
            this.PortfolioManager.RebalancePortfolio();
            this.OpenPositions();
        }

        // ======================================================================
        // Close all positions
        // ------------------
        // Invoked as a result of a scheduled routine
        // ======================================================================
        public void ClosePositions()
        {
            this.Liquidate();
        }

        // ======================================================================
        // Open a new position,
        // ------------------
        // Invoked as a result of a scheduled routine
        // ======================================================================
        public void OpenPositions()
        {
            // What shall we buy today? Ask the portfolio manager
            // ----------------------------------------------------
            var portfolioComponentArray = this.PortfolioManager.GetNextStocksToBuy();
            foreach (var portfolioComponent in portfolioComponentArray)
            {
                this.Debug("[INFO " + Time + "] \t| BUY " + portfolioComponent.Symbol + ", " + PreferredRankingMetric + " = " + portfolioComponent.GetPerfMetricValue(PreferredRankingMetric));
                var orderMsg = PortfolioManager.SelectionMethodUsed + " -minsAtOpen:" + entryAtMinsAfterOpen + " -" + portfolioComponent.GetAllPerfMetricsAsStringList();
                this.SetHoldings(portfolioComponent.Symbol, portfolioComponent.Weight, false, orderMsg);
                // ==============================================================================
                // Order event handler
                // ---------------------
                // For each filled BUY order, create a stop loss order, to prevent massive losses.  
                // ==============================================================================
                // def OnOrderEvent(self, orderEvent):
                //     if( ( orderEvent.Status == OrderStatus.Filled ) and \
                //         ( orderEvent.Direction == OrderDirection.Buy ) ):
                //         stopLossPercent = 0.7 # 70% stop loss for each position
                //         stopLossPrice   = orderEvent.FillPrice * (1 - stopLossPercent)
            }
        }
    }
}