| 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) + "]";
}
}
}