| Overall Statistics |
|
Total Trades 24 Average Win 0.22% Average Loss -1.28% Compounding Annual Return 19.926% Drawdown 8.800% Expectancy -0.033 Net Profit 6.242% Sharpe Ratio 1.434 Probabilistic Sharpe Ratio 57.239% Loss Rate 18% Win Rate 82% Profit-Loss Ratio 0.17 Alpha 0.175 Beta -0.04 Annual Standard Deviation 0.117 Annual Variance 0.014 Information Ratio -0.068 Tracking Error 0.21 Treynor Ratio -4.145 Total Fees $24.00 |
import numpy as np
import pandas as pd
from datetime import datetime, date
from datetime import datetime, timedelta
from PortfolioOptimizerClass import PortfolioOptimizer
from clr import AddReference
AddReference("QuantConnect.Indicators")
from QuantConnect.Indicators import *
# TODO :
# fix buying daily
# Universe selection
# short selling model
# selling hourly
# rebalance weekly (weight based on RS?)
# look into small stocks large moves 35.65->33.51 which is 6% ; control via draw down?
# self.SetBrokerageModel(AlphaStreamsBrokerageModel()) # learn more about this
# fix if dt >9 and dt<18
# DONE: fix hourlyHouseKeeping
# 10 days daily STD for ROKU on 7 Jan 21 is 12.15529, mine (based on open) is 12.3907448
from System.Drawing import Color
class ModelA(AlphaModel):
def __init__(self, param):
self.param = param
self.symbolDataBySymbol = {}
self.modelResolution = param.resolution
self.insightsTimeDelta = param.timedelta
self.objectiveFunction = param.pcmObjectiveFunction
self.lookbackOptimization = param.pcmLookbackOptimization
self.portOpt = PortfolioOptimizer(minWeight = 0, maxWeight = 1)
self.startingMaxHoldingLimit = param.startingMaxHoldingLimit
def OnSecuritiesChanged(self, algorithm, changes):
for added in changes.AddedSecurities:
symbolData = self.symbolDataBySymbol.get(added.Symbol)
if symbolData is None:
symbolData = SymbolData(added.Symbol, algorithm, self.param)
self.symbolDataBySymbol[added.Symbol] = symbolData
def Update(self, algorithm, data):
#if self.Portfolio.Invested: return
liquidate_now=[]
invested = [ x.Symbol.Value for x in algorithm.Portfolio.Values if x.Invested ] # can we make this easier via key query?
for symbol, symbolData in self.symbolDataBySymbol.items():
isInvested= str(symbol) in invested
if symbol != self.param.benchmark:
symbolData.getInsight(algorithm.Securities[symbol].Price, isInvested) # Latest known price; we are at 12:00 and the last trade at 10.57
if symbolData.trade:
if symbolData.liquidate:
invested.remove(str(symbol))
liquidate_now.append(str(symbol))
else:
invested.append(str(symbol))
# calculate optimal weights
if invested:
weights = self.CalculateOptimalWeights(algorithm, invested, self.objectiveFunction, self.lookbackOptimization)
for symbol in invested:
weight = weights[str(symbol)]
if weight>self.startingMaxHoldingLimit and len(invested)<1/self.startingMaxHoldingLimit: weight=self.startingMaxHoldingLimit
#self.algorithm.MarketOrder(symbol, 1)
algorithm.SetHoldings(symbol, weight);
if liquidate_now:
for symbol in liquidate_now:
weight=self.startingMaxHoldingLimit
algorithm.Liquidate(symbol)
return []
def OnOrderEvent(self, orderEvent):
order = self.Transactions.GetOrderById(orderEvent.OrderId)
if orderEvent.Status == OrderStatus.Filled:
self.algorithm.Debug("{0}: {1}: {2}".format(self.Time, order.Type, orderEvent))
def CalculateOptimalWeights(self, algorithm, symbols, objectiveFunction, lookbackOptimization):
# get historical close prices
historyClosePrices = algorithm.History(symbols, lookbackOptimization, Resolution.Daily)['close'].unstack(level = 0)
# calculate daily returns
returnsDf = historyClosePrices.pct_change().dropna()
# rename the columns in the dataframe in order to have tickers and not symbol strings
columnsList = list(returnsDf.columns)
returnsDf.rename(columns = {columnsList[i]: algorithm.ActiveSecurities[columnsList[i]].Symbol.Value for i in range(len(columnsList))}, inplace = True)
# calculate optimal weights
weights = self.portOpt.Optimize(objectiveFunction, returnsDf)
# convert the weights to a pandas Series
weights = pd.Series(weights, index = returnsDf.columns, name = 'weights')
return weights
class FrameworkAlgorithm(QCAlgorithm):
def Initialize(self):
param=paramData()
symbols = [Symbol.Create(x, SecurityType.Equity, Market.USA) for x in param.tickers]
self.SetStartDate(param.dateFrom[0],param.dateFrom[1],param.dateFrom[2])
self.SetEndDate(param.dateTo[0],param.dateTo[1],param.dateTo[2])
self.SetCash(param.cash)
self.liquidationBarrier=param.cash*param.stopLoss*-1
self.SetBenchmark(param.benchmarkTicker)
param.setBenchmark(self.AddEquity(param.benchmarkTicker,param.resolution).Symbol)
self.UniverseSettings.Resolution = param.resolution
self.SetWarmUp(timedelta(param.warmup))
self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))
self.SetBrokerageModel(AlphaStreamsBrokerageModel()) # learn more about this
self.SetAlpha(ModelA(param))
#myPCM = InsightWeightingPortfolioConstructionModel(rebalance = timedelta(days=252), portfolioBias = PortfolioBias.Long)
#myPCM.RebalanceOnInsightChanges = False
#myPCM.RebalanceOnSecurityChanges = False
#self.SetPortfolioConstruction(myPCM)
#self.SetRiskManagement(MaximumDrawdownPercentPerSecurity(param.maxDrawDown)) # NullRiskManagementModel() or MaximumDrawdownPercentPerSecurity(param.maxDrawDown) > drop in profit from the max >> done daily / TODO: redo hourly? or
#self.SetExecution(ImmediateExecutionModel())
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(TimeSpan.FromMinutes(param.runEveryXminutes)), self.hourlyHouseKeeping)
def hourlyHouseKeeping(self):
# Fail Safe - If our strategy is losing than acceptable (something is wrong)
# Strategy suddenly losing money or logic problem/bug we did't catch when testing
pnl= sum([self.Portfolio[symbol].NetProfit for symbol in self.Portfolio.Keys])
#if self.LiveMode:
if pnl < self.liquidationBarrier:
self.Debug(f"Fallback event triggered, liquidating with total portfolio loss of {pnl}")
self.Liquidate()
self.Quit()
dt=int(self.Time.hour)
if dt >9 and dt<18: # if not set still prints out of hours for self.IsMarketOpen("SPY")
if (self.IsMarketOpen("SPY") and self.Portfolio.Invested):
#self.Log("\n\nPortfolio")
summary = {}
invested = [ x.Symbol.Value for x in self.Portfolio.Values if x.Invested ]
for symbol in invested:
hold_val = round(self.Portfolio[symbol].HoldingsValue, 2)
abs_val = round(self.Portfolio[symbol].AbsoluteHoldingsValue, 2)
pnl = round(self.Portfolio[symbol].UnrealizedProfit, 2)
qty = self.Portfolio[symbol].Quantity
price = self.Portfolio[symbol].Price
summary[symbol]=[hold_val,abs_val,pnl,qty,price]
df=pd.DataFrame(summary)
df.index = ['hold_val', 'abs_val', 'pnl', 'qty','price']
df=df.T
hold_val_total= abs(df['hold_val']).sum()
df = df.assign(weight=abs(df['hold_val'])/hold_val_total)
#self.Log(df)
#self.Log("\n\n")
class paramData:
def __init__(self):
self.dateFrom = (2020,9,1)
self.dateTo = (2021,1,1)
self.cash = 50000 # how to top this up after going live?
self.warmup = 28 # starts from self.dateFrom
#self.resolution = Resolution.Hour # 10-11, etc Daily data is midnight to mifnight, 12AM EST
self.tickers = ["MSFT","ROKU","ANET","FSLY"] # how do I change this on request?
self.resolution = Resolution.Daily # 10-11, etc Daily data is midnight to mifnight, 12AM EST
self.tickers_len = len(self.tickers)
self.timedelta = timedelta(hours=240)
self.maxDrawDown = 0.05
self.runEveryXminutes = 60 # Schedule frequency
self.benchmarkTicker = 'SPY' # can be ticker as a part of the dictionary ["MSFT:SPY"]
self.pcmObjectiveFunction = 'equalWeighting' #'equalWeighting' 'maxReturn' 'riskParity'
self.pcmLookbackOptimization = 63
self.stopLoss = 0.15 # % of the total cash invested
self.startingMaxHoldingLimit = 0.17 # we do not allocate more than this % for each security
def setBenchmark(self, symbol):
self.benchmark = symbol
class SymbolData:
def __init__(self, symbol, algorithm, param):
self.symbol = symbol
self.algorithm = algorithm
self.param = param
self.resolution = param.resolution
self.price = 0.00 # last trading price
self.lastPricePaidRef = 0.00 # last purchase price reference; update with an actual price
self.kama = algorithm.KAMA(symbol, 10,2,30, self.resolution)
self.variationRate = 0.95 # tolerance level to avoid buy and immediate sell scenario
self.mom = algorithm.MOM(symbol, 14, self.resolution)
self.roc = algorithm.ROC(symbol, 9, self.resolution)
self.ema13 = algorithm.EMA(symbol, 13, self.resolution)
self.ema63 = algorithm.EMA(symbol, 63, self.resolution)
self.ema150 = algorithm.EMA(symbol, 150, self.resolution)
self.fkama = False
self.fmom = False
self.froc = False
self.fema = False
self.rsStock = False
self.rsIdx = False
self.fbenchmark = False
self.lookback = 10
self.std = algorithm.STD(symbol, self.lookback,self.resolution)
self.magnitude = 0.025#algorithm.IndicatorExtensions.SMA(RateOfChangePercent(1),self.lookback).Current.Value
self.lastDateTraded = self.algorithm.Time.date()
# Chart Plotting
self.kama.Updated += self.getRSL
self.kama.Updated += self.OnSymbolDataUpdate
self.dataPlot = Chart('Detail'+str(self.symbol))
self.dataPlot.AddSeries(Series('Price', SeriesType.Line, '$'))
self.dataPlot.AddSeries(Series('Kama', SeriesType.Line, '$'))
self.dataPlot.AddSeries(Series('MOM', SeriesType.Line, ''))
self.dataPlot.AddSeries(Series('EMA13', SeriesType.Line, '$'))
self.dataPlot.AddSeries(Series('EMA63', SeriesType.Line, '$'))
self.dataPlot.AddSeries(Series('EMA150', SeriesType.Line, '$'))
self.dataPlot.AddSeries(Series('ROC', SeriesType.Line, ''))
self.dataPlot.AddSeries(Series('RS-idx', SeriesType.Line, ''))
self.dataPlot.AddSeries(Series('Std', SeriesType.Line, '$'))
self.dataPlot.AddSeries(Series('Buy', SeriesType.Scatter, '$', Color.Green,ScatterMarkerSymbol.Circle))
self.dataPlot.AddSeries(Series('Sell', SeriesType.Scatter, '$', Color.Red,ScatterMarkerSymbol.Circle))
self.algorithm.AddChart(self.dataPlot)
def getInsight(self, price, isInvested):
self.price = price
self.fkama_buy = self.price>self.kama.Current.Value
self.fkama_sell = self.price<self.kama.Current.Value*self.variationRate
self.fmom = self.mom.Current.Value>0
self.froc = self.roc.Current.Value>0
self.fema = self.ema13.Current.Value>self.ema63.Current.Value>self.ema150.Current.Value
self.trade = False
self.liquidate = False
self.fbenchmark = self.rsStock>self.rsIdx
self.dateTradedDelta = (self.algorithm.Time.date()-self.lastDateTraded).days
# and self.froc self.fmom and
self.algorithm.Debug(f"{str(self.symbol)}\t{str(self.algorithm.Time.date())}\tTraded\t{str(self.lastDateTraded)}\tDt\t{str(self.dateTradedDelta)}\tstd\t{self.std}\tclose\t{self.price}")
if not isInvested and self.fkama_buy and self.fema and self.fbenchmark:
self.trade = True
self.lastDateTraded = self.algorithm.Time.date()
self.algorithm.Plot('Detail'+str(self.symbol),'Buy', self.price)
self.algorithm.Debug(f"\n>>>>>> Buy\t{str(self.symbol)}\tPrice\t{self.price}[{self.lastPricePaidRef}]\tMOM:{self.fmom}\trKAMA\t{self.price}\t \
\nKAMA:{self.kama.Current.Value}\tFEMA:{self.fema}\tRS:{self.fbenchmark}\tSTD\t{self.std}")
self.lastPricePaidRef = self.price
# or not self.froc not self.fmom or
if isInvested and (self.fkama_sell or not self.fema or not self.fbenchmark \
or (self.dateTradedDelta<3 and self.price<self.lastPricePaidRef-float(str(self.std)))): # we avoid selling on the same/next day if move less than x std
self.trade = True
self.liquidate = True
self.algorithm.Plot('Detail'+str(self.symbol),'Sell',self.price)
self.algorithm.Debug(f"\n<<<<<<< Sell\t{str(self.symbol)}\tMOM\t{self.fmom}\tPrice\t{self.price}[{self.lastPricePaidRef}]\trKAMA\t{self.price}\t \
\nKAMA\t{self.kama.Current.Value}\tFEMA\t{self.fema}\tStock\t{self.rsStock}\tIdx\t{self.rsIdx}\tSTD\t{self.std}\tPriceDrop{str(self.lastPricePaidRef-float(str(self.std)))}")
def OnSymbolDataUpdate(self, sender, updated):
self.algorithm.Plot('Detail'+str(self.symbol),'Price', self.price)
self.algorithm.Plot('Detail'+str(self.symbol),'Kama', self.kama.Current.Value)
self.algorithm.Plot('Detail'+str(self.symbol),'ROC', self.roc.Current.Value)
self.algorithm.Plot('Detail'+str(self.symbol),'MOM', self.mom.Current.Value)
self.algorithm.Plot('Detail'+str(self.symbol),'EMA13', self.ema13.Current.Value)
self.algorithm.Plot('Detail'+str(self.symbol),'EMA63', self.ema63.Current.Value)
self.algorithm.Plot('Detail'+str(self.symbol),'EMA150', self.ema150.Current.Value)
self.algorithm.Plot('Detail'+str(self.symbol),'Std', self.std.Current.Value)
def getRSL(self, sender, updated):
# lookback days : algo weight
days = {40:0.5,80:0.25,160:0.25}
rs = {}
for symbol in [self.symbol,self.param.benchmark]:
result =[]
df=pd.DataFrame(self.algorithm.History(symbol, 300, Resolution.Daily))
df=df.iloc[::-1]
df=df.reset_index(level=0, drop=True)
symbol = str(symbol)
for x in days:
result.append([symbol, x, df.iloc[0]['close'], df.iloc[x-1]['close'],days[x]])
df = pd.DataFrame(result,columns=['Symbol','Days','Ref_Price','Close_Price','Weight'],dtype=float)
df = df.assign(Rsl=(df['Ref_Price'])/df['Close_Price']*df['Weight'])
rs[symbol] = (abs(df['Rsl']).sum()*1000)-1000
self.rsStock = rs[str(self.symbol)]
self.rsIdx = rs[str(self.param.benchmark)]
self.algorithm.Plot('Detail'+str(self.symbol),'RS-idx', self.rsStock/self.rsIdx)from clr import AddReference
AddReference("QuantConnect.Research")
#clr.AddReference('QuantConnect.Research')
from QuantConnect.Research import QuantBook
class RelativeStrengthLineCalc():
def getRSL(self, ref_date, symbols):
self.rsl_target_days = [40,80,160]
self.rsl_target_weights = [0.5,0.25,0.25]
qb = QuantBook()
date_end = datetime(ref_date)
date_start = date_end - timedelta(days=300)
for symbol in symbols:
smbl = qb.AddEquity(symbol) # add equity data
result =[]
history = qb.History(smbl.Symbol, date_start, date_end, Resolution.Daily)
df=pd.DataFrame(history)
df=df.iloc[::-1]
df=df.reset_index(level=0, drop=True)
i=0
for x in rsl_target_days:
result.append([symbol, x, df.iloc[0]['close'], df.iloc[x-1]['close'],rsl_target_weights[i]])
i=i+1
df = pd.DataFrame(result,columns=['Symbol','Days','Ref_Price','Close_Price','Weight'],dtype=float)
df = df.assign(Rsl=(df['Ref_Price'])/df['Close_Price']*df['Weight'])
rsl=(abs(df['Rsl']).sum()*1000)-1000
return rslclass RelativeStrengthLineCalc():
def getRSL():
rsl_target_days = [40,80,160]
rsl_target_weights = [0.5,0.25,0.25]
return 1import pandas as pd
import numpy as np
from scipy.optimize import minimize
class PortfolioOptimizer:
'''
Description:
Implementation of a custom optimizer that calculates the weights for each asset to optimize a given objective function
Details:
Optimization can be:
- Equal Weighting
- Maximize Portfolio Return
- Minimize Portfolio Standard Deviation
- Mean-Variance (minimize Standard Deviation given a target return)
- Maximize Portfolio Sharpe Ratio
- Maximize Portfolio Sortino Ratio
- Risk Parity Portfolio
Constraints:
- Weights must be between some given boundaries
- Weights must sum to 1
'''
def __init__(self,
minWeight = 0,
maxWeight = 1):
'''
Description:
Initialize the CustomPortfolioOptimizer
Args:
minWeight(float): The lower bound on portfolio weights
maxWeight(float): The upper bound on portfolio weights
'''
self.minWeight = minWeight
self.maxWeight = maxWeight
def Optimize(self, objFunction, dailyReturnsDf, targetReturn = None):
'''
Description:
Perform portfolio optimization given a series of returns
Args:
objFunction: The objective function to optimize (equalWeighting, maxReturn, minVariance, meanVariance, maxSharpe, maxSortino, riskParity)
dailyReturnsDf: DataFrame of historical daily arithmetic returns
Returns:
Array of double with the portfolio weights (size: K x 1)
'''
# initial weights: equally weighted
size = dailyReturnsDf.columns.size # K x 1
self.initWeights = np.array(size * [1. / size])
# get sample covariance matrix
covariance = dailyReturnsDf.cov()
# get the sample covariance matrix of only negative returns for sortino ratio
negativeReturnsDf = dailyReturnsDf[dailyReturnsDf < 0]
covarianceNegativeReturns = negativeReturnsDf.cov()
if objFunction == 'equalWeighting':
return self.initWeights
bounds = tuple((self.minWeight, self.maxWeight) for x in range(size))
constraints = [{'type': 'eq', 'fun': lambda x: np.sum(x) - 1.0}]
if objFunction == 'meanVariance':
# if no target return is provided, use the resulting from equal weighting
if targetReturn is None:
targetReturn = self.CalculateAnnualizedPortfolioReturn(dailyReturnsDf, self.initWeights)
constraints.append( {'type': 'eq', 'fun': lambda weights:
self.CalculateAnnualizedPortfolioReturn(dailyReturnsDf, weights) - targetReturn} )
opt = minimize(lambda weights: self.ObjectiveFunction(objFunction, dailyReturnsDf,
covariance, covarianceNegativeReturns,
weights),
x0 = self.initWeights,
bounds = bounds,
constraints = constraints,
method = 'SLSQP')
return opt['x']
def ObjectiveFunction(self, objFunction, dailyReturnsDf, covariance, covarianceNegativeReturns, weights):
'''
Description:
Compute the objective function
Args:
objFunction: The objective function to optimize (equalWeighting, maxReturn, minVariance, meanVariance,
maxSharpe, maxSortino, riskParity)
dailyReturnsDf: DataFrame of historical daily returns
covariance: Sample covariance
covarianceNegativeReturns: Sample covariance matrix of only negative returns
weights: Portfolio weights
'''
if objFunction == 'maxReturn':
f = self.CalculateAnnualizedPortfolioReturn(dailyReturnsDf, weights)
return -f # convert to negative to be minimized
elif objFunction == 'minVariance':
f = self.CalculateAnnualizedPortfolioStd(covariance, weights)
return f
elif objFunction == 'meanVariance':
f = self.CalculateAnnualizedPortfolioStd(covariance, weights)
return f
elif objFunction == 'maxSharpe':
f = self.CalculateAnnualizedPortfolioSharpeRatio(dailyReturnsDf, covariance, weights)
return -f # convert to negative to be minimized
elif objFunction == 'maxSortino':
f = self.CalculateAnnualizedPortfolioSortinoRatio(dailyReturnsDf, covarianceNegativeReturns, weights)
return -f # convert to negative to be minimized
elif objFunction == 'riskParity':
f = self.CalculateRiskParityFunction(covariance, weights)
return f
else:
raise ValueError(f'PortfolioOptimizer.ObjectiveFunction: objFunction input has to be one of equalWeighting,'
+ ' maxReturn, minVariance, meanVariance, maxSharpe, maxSortino or riskParity')
def CalculateAnnualizedPortfolioReturn(self, dailyReturnsDf, weights):
annualizedPortfolioReturns = np.sum( ((1 + dailyReturnsDf.mean())**252 - 1) * weights )
return annualizedPortfolioReturns
def CalculateAnnualizedPortfolioStd(self, covariance, weights):
annualizedPortfolioStd = np.sqrt( np.dot(weights.T, np.dot(covariance * 252, weights)) )
if annualizedPortfolioStd == 0:
raise ValueError(f'PortfolioOptimizer.CalculateAnnualizedPortfolioStd: annualizedPortfolioStd cannot be zero. Weights: {weights}')
return annualizedPortfolioStd
def CalculateAnnualizedPortfolioNegativeStd(self, covarianceNegativeReturns, weights):
annualizedPortfolioNegativeStd = np.sqrt( np.dot(weights.T, np.dot(covarianceNegativeReturns * 252, weights)) )
if annualizedPortfolioNegativeStd == 0:
raise ValueError(f'PortfolioOptimizer.CalculateAnnualizedPortfolioNegativeStd: annualizedPortfolioNegativeStd cannot be zero. Weights: {weights}')
return annualizedPortfolioNegativeStd
def CalculateAnnualizedPortfolioSharpeRatio(self, dailyReturnsDf, covariance, weights):
annualizedPortfolioReturn = self.CalculateAnnualizedPortfolioReturn(dailyReturnsDf, weights)
annualizedPortfolioStd = self.CalculateAnnualizedPortfolioStd(covariance, weights)
annualizedPortfolioSharpeRatio = annualizedPortfolioReturn / annualizedPortfolioStd
return annualizedPortfolioSharpeRatio
def CalculateAnnualizedPortfolioSortinoRatio(self, dailyReturnsDf, covarianceNegativeReturns, weights):
annualizedPortfolioReturn = self.CalculateAnnualizedPortfolioReturn(dailyReturnsDf, weights)
annualizedPortfolioNegativeStd = self.CalculateAnnualizedPortfolioNegativeStd(covarianceNegativeReturns, weights)
annualizedPortfolioSortinoRatio = annualizedPortfolioReturn / annualizedPortfolioNegativeStd
return annualizedPortfolioSortinoRatio
def CalculateRiskParityFunction(self, covariance, weights):
''' Spinu formulation for risk parity portfolio '''
assetsRiskBudget = self.initWeights
portfolioVolatility = self.CalculateAnnualizedPortfolioStd(covariance, weights)
x = weights / portfolioVolatility
riskParity = (np.dot(x.T, np.dot(covariance, x)) / 2) - np.dot(assetsRiskBudget.T, np.log(x))
return riskParity#insights.append(Insight(symbol, self.insightsTimeDelta, InsightType.Price, symbolData.InsightDirection, None,None, None,0.1))
#algorithm.Log(f"{symbol}\tMOM\t[{symbolData.fmom}]\t{round(symbolData.mom.Current.Value,2)}\tKAMA\t[{symbolData.fkama}]\t{round(symbolData.kama.Current.Value,2)}\
# \tPrice\t{symbolData.price}\tROC\t[{symbolData.froc}]\t{round(symbolData.roc.Current.Value,4)}\tEMA\t[{symbolData.fema}]\tEMA-13\t{round(symbolData.ema13.Current.Value,2)}\
# \tEMA-63\t{round(symbolData.ema63.Current.Value,2)}\tEMA-150\t{round(symbolData.ema150.Current.Value,2)}\taction\t{symbolData.InsightDirection}")
#self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
#self.SetPortfolioConstruction(MeanVarianceOptimizationPortfolioConstructionModel(param.resolution,PortfolioBias.LongShort,1,63,param.resolution,0.02,MaximumSharpeRatioPortfolioOptimizer(0,1,0)))
# self.rebalancingPeriod = Expiry.EndOfMonth
#pcm = InsightWeightingPortfolioConstructionModel(lambda time: param.rebalancingPeriod(time))
#self.InsightDirection = InsightDirection.Up
#self.InsightDirection = InsightDirection.Flat # liqudates position - work around InsightDirection.Down which may sell and then short