Overall Statistics |
Total Trades 1511 Average Win 1.60% Average Loss -1.23% Compounding Annual Return 240.629% Drawdown 18.000% Expectancy 0.413 Net Profit 3892.278% Sharpe Ratio 6.232 Probabilistic Sharpe Ratio 100.000% Loss Rate 38% Win Rate 62% Profit-Loss Ratio 1.29 Alpha 1.718 Beta 1.044 Annual Standard Deviation 0.297 Annual Variance 0.088 Information Ratio 6.355 Tracking Error 0.271 Treynor Ratio 1.773 Total Fees $91189.11 Estimated Strategy Capacity $3200000.00 Lowest Capacity Asset IDXX R735QTJ8XC9X |
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(2016, 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 // ----------------------------------------- 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) } } } }
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 -1.0m; } 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 -1.0m; } 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 -1.0m; } 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); if(String.Equals(symbol.Split(' ')[0], "CMCSA")) algo.Debug(bar.Time + " " + bar.Close + " " + 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) { // use dask to multi-thread this logic // and run this function for each simbol simultaneously // ------------------------------------------------------- // // * ATTN: Conor * // // Just replace the below 3 dask lines with this one line: // self.MeasureAndAddPortfolioComponent)(universeSymbol) // // --------------------------------------------------------- MeasureAndAddPortfolioComponent(universeSymbol); } // Rank components based on value of preferred metric // --------------------------------------------------- var list = (from x in PortfolioComponents orderby x.GetPerfMetricValue(PreferredRankingMetric) descending select x); List<PortfolioComponent> copy = new List<PortfolioComponent>(); foreach(var obj in list) { //algo.Debug(obj.Symbol + " " + obj.GetPerfMetricValue(PreferredRankingMetric)); 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<object, object> 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<object, object> { }; } // ================================================ public void AddPerfMetricValue(object perfMetricValue, object perfMetricLabel) { this.PerfMetricValues[perfMetricLabel] = perfMetricValue; } // ================================================ public object GetPerfMetricValue(object perfMetricLabel) { return this.PerfMetricValues[perfMetricLabel]; } // ================================================ public string GetAllPerfMetricsAsStringList() { string o = "["; foreach(object key in PerfMetricValues.Keys) o += "(" + key + ", " + PerfMetricValues[key] + " ), "; return o.Substring(0, o.Count() - 2) + "]"; } } }