| Overall Statistics |
|
Total Trades 5 Average Win 0.58% Average Loss 0% Compounding Annual Return 4.744% Drawdown 1.000% Expectancy 0 Net Profit 1.162% Sharpe Ratio 0.992 Probabilistic Sharpe Ratio 49.822% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha 0.073 Beta -0.073 Annual Standard Deviation 0.039 Annual Variance 0.002 Information Ratio -3.111 Tracking Error 0.137 Treynor Ratio -0.538 Total Fees $5.00 |
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Common")
from System import *
from QuantConnect import *
from QuantConnect.Orders import *
from QuantConnect.Algorithm import *
from QuantConnect.Securities import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Alphas import *
from QuantConnect.Algorithm.Framework.Execution import *
from QuantConnect.Algorithm.Framework.Portfolio import *
from QuantConnect.Algorithm.Framework.Selection import *
from datetime import timedelta
from alpha_custom import custom_alpha_model
from port_insight_wt import port_insight_wt
### <summary>
### Regression algorithm testing portfolio construction model control over rebalancing,
### specifying a custom rebalance function that returns null in some cases, see GH 4075.
### </summary>
class PortfolioRebalanceOnCustomFuncRegressionAlgorithm(QCAlgorithm):
def Initialize(self):
''' Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''
self.UniverseSettings.Resolution = Resolution.Daily
self.SetStartDate(2019, 1, 1)
self.SetEndDate(2019, 4, 1)
# does this refer to its own insight, or insight of other symbols?
# no orders generated
#self.Settings.RebalancePortfolioOnInsightChanges = False;
# if this is true no orders based on insights are generated!
self.Settings.RebalancePortfolioOnInsightChanges = True;
self.Settings.RebalancePortfolioOnSecurityChanges = False;
#self.SetUniverseSelection(CustomUniverseSelectionModel("CustomUniverseSelectionModel", lambda time: [ "AAPL", "IBM", "FB", "SPY", "AIG", "BAC", "BNO" ]))
self.SetUniverseSelection(CustomUniverseSelectionModel("CustomUniverseSelectionModel", lambda time: [ "AAPL", "AMZN"]))
self.SetAlpha(custom_alpha_model());
# no rebalancing of already bought stocks
self.SetPortfolioConstruction(port_insight_wt(self.RebalanceFunction))
self.SetExecution(ImmediateExecutionModel())
self.lastRebalanceTime = self.StartDate
# no rebalancing based on time
def RebalanceFunction(self, time):
None
# def OnOrderEvent(self, orderEvent):
# if orderEvent.Status == OrderStatus.Submitted:
# if self.UtcTime != self.lastRebalanceTime or self.UtcTime.weekday() != 0:
# raise ValueError(f"{self.UtcTime} {orderEvent.Symbol}")
# ENDfrom clr import AddReference
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm.Framework")
from QuantConnect import Resolution
from QuantConnect.Algorithm.Framework.Alphas import *
#from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel, PortfolioBias
class port_insight_wt(PortfolioConstructionModel):
'''Provides an implementation of IPortfolioConstructionModel that generates percent targets based on the
Insight.Weight.
The target percent holdings of each Symbol is given by the Insight.Weight from the last
active Insight for that symbol.
For insights of direction InsightDirection.Up, long targets are returned and for insights of direction
InsightDirection.Down, short targets are returned.
If the sum of all the last active Insight per symbol is bigger than 1, it will factor down each target
percent holdings proportionally so the sum is 1.
It will ignore Insight that have no Insight.Weight value.'''
def __init__(self, rebalancingParam = Resolution.Daily, portfolioBias = PortfolioBias.LongShort):
'''Initialize a new instance of InsightWeightingPortfolioConstructionModel
Args:
rebalancingParam: Rebalancing parameter. If it is a timedelta, date rules or Resolution, it will be converted into a function.
If None will be ignored.
The function returns the next expected rebalance time for a given algorithm UTC DateTime.
The function returns null if unknown, in which case the function will be called again in the
next loop. Returning current time will trigger rebalance.
portfolioBias: Specifies the bias of the portfolio (Short, Long/Short, Long)'''
#super().__init__(rebalancingParam, portfolioBias)
self.portfolioBias = portfolioBias
# If the argument is an instance of Resolution or Timedelta
# Redefine rebalancingFunc
rebalancingFunc = rebalancingParam
if isinstance(rebalancingParam, int):
rebalancingParam = Extensions.ToTimeSpan(rebalancingParam)
if isinstance(rebalancingParam, timedelta):
rebalancingFunc = lambda dt: dt + rebalancingParam
if rebalancingFunc:
self.SetRebalancingFunc(rebalancingFunc)
def ShouldCreateTargetForInsight(self, insight):
'''Method that will determine if the portfolio construction model should create a
target for this insight
Args:
insight: The insight to create a target for'''
# Ignore insights that don't have Weight value
return insight.Weight is not None
def DetermineTargetPercent(self, activeInsights):
'''Will determine the target percent for each insight
Args:
activeInsights: The active insights to generate a target for'''
result = {}
# We will adjust weights proportionally in case the sum is > 1 so it sums to 1.
weightSums = sum(self.GetValue(insight) for insight in activeInsights if self.RespectPortfolioBias(insight))
weightFactor = 1.0
if weightSums > 1:
weightFactor = 1 / weightSums
for insight in activeInsights:
result[insight] = (insight.Direction if self.RespectPortfolioBias(insight) else InsightDirection.Flat) * self.GetValue(insight) * weightFactor
return result
def GetValue(self, insight):
'''Method that will determine which member will be used to compute the weights and gets its value
Args:
insight: The insight to create a target for
Returns:
The value of the selected insight member'''
return insight.Weight
def RespectPortfolioBias(self, insight):
'''Method that will determine if a given insight respects the portfolio bias
Args:
insight: The insight to create a target for
'''
return self.portfolioBias == PortfolioBias.LongShort or insight.Direction == self.portfolioBiasfrom clr import AddReference
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Alphas import AlphaModel, Insight, InsightType, InsightDirection
import datetime
class custom_alpha_model(AlphaModel):
''' Provides an implementation of IAlphaModel that always returns the same insight for each security'''
def __init__(self):
'''Initializes a new instance of the ConstantAlphaModel class
Args:
type: The type of insight
direction: The direction of the insight
period: The period over which the insight with come to fruition
magnitude: The predicted change in magnitude as a +- percentage
confidence: The confidence in the insight'''
self.securities = []
self.insightsTimeBySymbol = {}
def Update(self, algorithm, data):
''' Creates a constant insight for each security as specified via the constructor
Args:
algorithm: The algorithm instance
data: The new data available
Returns:
The new insights generated
Jan 1st: UP for AAPL for 30 days, at 10% portfolio
Jan 15th: UP for AMZN for 30 days, at 10% portfolio
Expectation:
Don't sell any AAPL on Jan15th (its % might be higher than 10%, that's OK)
Sell APPL on Jan 31st (after expiration of the Alpha signal)
'''
insights = []
dt1 = datetime.datetime(2019,1,1)
dt2 = datetime.datetime(2019,1,15)
dt = algorithm.Time
wt = 0.1
for security in self.securities:
# security price could be zero until we get the first data point. e.g. this could happen
# when adding both forex and equities, we will first get a forex data point
# https://www.geeksforgeeks.org/comparing-dates-python/
if security.Symbol.Value == "AAPL" and dt == dt1:
period = TimeSpan.FromDays(30)
#if security.Price != 0 and self.ShouldEmitInsight(algorithm.UtcTime, security.Symbol):
ins_type = InsightType.Price
direction = InsightDirection.Up
# Insight(symbol, timedelta, type, direction, magnitude=None, confidence=None, sourceModel=None, weight=None)
insights.append(Insight(security.Symbol, period, ins_type, direction, 0.025, 1, "custom", wt))
if security.Symbol.Value == "AMZN" and dt == dt2:
period = TimeSpan.FromDays(30)
#if security.Price != 0 and self.ShouldEmitInsight(algorithm.UtcTime, security.Symbol):
ins_type = InsightType.Price
direction = InsightDirection.Up
insights.append(Insight(security.Symbol, period, ins_type, direction, 0.025, 1, "custom", wt))
return insights
def OnSecuritiesChanged(self, algorithm, changes):
''' Event fired each time the we add/remove securities from the data feed
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
for added in changes.AddedSecurities:
self.securities.append(added)
# this will allow the insight to be re-sent when the security re-joins the universe
for removed in changes.RemovedSecurities:
if removed in self.securities:
self.securities.remove(removed)
if removed.Symbol in self.insightsTimeBySymbol:
self.insightsTimeBySymbol.pop(removed.Symbol)
def ShouldEmitInsight(self, utcTime, symbol):
generatedTimeUtc = self.insightsTimeBySymbol.get(symbol)
if generatedTimeUtc is not None:
# we previously emitted a insight for this symbol, check it's period to see
# if we should emit another insight
if utcTime - generatedTimeUtc < self.period:
return False
# we either haven't emitted a insight for this symbol or the previous
# insight's period has expired, so emit a new insight now for this symbol
self.insightsTimeBySymbol[symbol] = utcTime
return True
def strfdelta(tdelta):
d = tdelta.days
h, rem = divmod(tdelta.seconds, 3600)
m, s = divmod(rem, 60)
return "{}.{:02d}:{:02d}:{:02d}".format(d,h,m,s)