| Overall Statistics |
|
Total Trades 114 Average Win 0% Average Loss 0% Compounding Annual Return 341.578% Drawdown 1.200% Expectancy 0 Net Profit 2.055% Sharpe Ratio 7.965 Probabilistic Sharpe Ratio 69.419% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha -8.567 Beta 4.548 Annual Standard Deviation 0.161 Annual Variance 0.026 Information Ratio -5.797 Tracking Error 0.152 Treynor Ratio 0.282 Total Fees $118.89 Estimated Strategy Capacity $600000.00 Lowest Capacity Asset OFLX TASN5M2GPOIT |
# 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("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Common")
from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm.Framework.Alphas import *
from datetime import timedelta
from enum import Enum
class PPOAlphaModel(AlphaModel):
'''Uses PPO to create insights.
Using default settings, slow/fast is 12/26. Above/below -.5 will trigger a new insight.'''
rebalance_date = None
rebalance_complete = False
def __init__(self,
fastPeriod = 12*5,
slowPeriod = 26*5,
trigger = 0.0,
consolidationPeriod = 7,
resolution = Resolution.Daily):
self.lookback = (slowPeriod * 3)
self.fastPeriod = fastPeriod
self.slowPeriod = slowPeriod
self.resolution = resolution
self.consolidationPeriod = consolidationPeriod
self.trigger = trigger
self.predictionInterval = Extensions.ToTimeSpan(self.resolution)
self.symbolDataBySymbol = {}
resolutionString = Extensions.GetEnumString(resolution, Resolution)
self.Name = '{}({},{},{})'.format(self.__class__.__name__, fastPeriod, slowPeriod, resolutionString)
def Update(self, algorithm, data):
'''Updates this alpha model with the latest data from the algorithm.
This is called each time the algorithm receives data for subscribed securities
Args:
algorithm: The algorithm instance
data: The new data available
Returns:
The new insights generated'''
#algorithm.Debug("Update Called on PPOAlphaModel")
# generate insights when scheduled
insights = []
#and algorithm.Time.hour == 8 and algorithm.Time.minute == 59
if self.rebalance_complete or not (algorithm.Time.date() == self.rebalance_date):
return insights
# only get here to build new insights once on the scheduled trading date
self.rebalance_complete = True
#algorithm.Debug("Now Generating Insights in PPOAlpha")
for symbol, symbolData in self.symbolDataBySymbol.items():
if symbolData.CanEmit:
direction = InsightDirection.Flat
magnitude = symbolData.Return
#algorithm.Debug("got a positive CanEmit with symbol and magnitude: " + str(symbol) + str(magnitude))
if magnitude > self.trigger: direction = InsightDirection.Up
if magnitude < self.trigger: direction = InsightDirection.Flat
insights.append(Insight.Price(symbol, self.predictionInterval, direction))
algorithm.Log(str(symbol) + ": " + str(magnitude) + ": " + str(direction))
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'''
#algorithm.Debug("starting OnSecuritiesChanged")
# clean up data for removed securities
for removed in changes.RemovedSecurities:
symbolData = self.symbolDataBySymbol.pop(removed.Symbol, None)
if symbolData is not None:
symbolData.RemoveConsolidators(algorithm)
#algorithm.Debug("in OnSecuritiesChanged past removed list")
# initialize data for added securities
symbols = [ x.Symbol for x in changes.AddedSecurities ]
#algorithm.Debug("before history retrieval")
history = algorithm.History(symbols, self.lookback, self.resolution)
#algorithm.Debug("after history retrieval")
if history.empty: return
tickers = history.index.levels[0]
for ticker in tickers:
symbol = SymbolCache.GetSymbol(ticker)
if symbol not in self.symbolDataBySymbol:
symbolData = SymbolData(symbol, self.fastPeriod, self.slowPeriod, self.consolidationPeriod)
self.symbolDataBySymbol[symbol] = symbolData
symbolData.RegisterIndicators(algorithm, self.resolution)
symbolData.WarmUpIndicators(history.loc[ticker])
class SymbolData:
'''Contains data specific to a symbol required by this model'''
def __init__(self, symbol, fastPeriod, slowPeriod, consolidationPeriod):
self.Symbol = symbol
self.Fast = fastPeriod
self.Slow = slowPeriod
self.PPO = PercentagePriceOscillator(fastPeriod, slowPeriod, MovingAverageType.Exponential)
#self.Consolidator = TradeBarConsolidator(TimeSpan.FromDays(consolidationPeriod))
self.Consolidator = None
self.Previous = None
# True if the fast is above the slow, otherwise false.
# This is used to prevent emitting the same signal repeatedly
#self.FastIsOverSlow = False
#self.abovePPOTrigger = False
#def setPPOTrigger (self, value):
#self.abovePPOTrigger = value
@property
def belowPPOTrigger(self):
return (not self.abovePPOTrigger)
@property
def CanEmit(self):
if self.Previous == self.PPO.Samples:
return False
self.Previous = self.PPO.Samples
return self.PPO.IsReady
@property
def Return(self):
return float(self.PPO.Current.Value)
#def OnWeeklyData(self, sender, bar):
#self.PPO.Update(bar.EndTime, bar.Close)
def RegisterIndicators(self, algorithm, resolution):
#self.Consolidator = algorithm.ResolveConsolidator(self.Symbol, resolution)
algorithm.RegisterIndicator(self.Symbol, self.PPO, self.Consolidator)
def RemoveConsolidators(self, algorithm):
if self.Consolidator is not None:
algorithm.SubscriptionManager.RemoveConsolidator(self.Symbol, self.Consolidator)
def WarmUpIndicators(self, history):
for tuple in history.itertuples():
self.PPO.Update(tuple.Index, tuple.close)
#self.Consolidator.Update(tuple)class StockSelectionStrategyBasedOnFundamentalFactorsAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2021, 11, 1) # Set Start Date
#self.SetEndDate(2017, 5, 2) # Set End Date
self.SetCash(100000) # Set Strategy Cash
self.current_month = -1
self.coarse_count = 300
self.fine_count = 10
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.SetAlpha(ConstantAlphaModel(InsightType.Price, InsightDirection.Up, timedelta(30)))
self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel(lambda time:None))
#self.AddEquity("SPY", Resolution.Daily)
self._list = [ ]
def CoarseSelectionFunction(self, coarse):
if self.current_month == self.Time.month:
return Universe.Unchanged
self.current_month = self.Time.month
selected = [x for x in coarse if (x.HasFundamentalData) and x.DollarVolume > 1000000]
return [x.Symbol for x in selected]
def FineSelectionFunction(self, fine):
fine = [x for x in fine if x.FinancialStatements.CashFlowStatement.FreeCashFlow.ThreeMonths > 0
#and ((x.OperationRatios.RevenueGrowth.OneYear) - (x.FinancialStatements.IncomeStatement.TotalRevenue.OneMonth)) / (x.FinancialStatements.IncomeStatement.TotalRevenue.OneMonth) > 0.25
and x.OperationRatios.NormalizedNetProfitMargin.OneYear > 0.07
and x.FinancialStatements.IncomeStatement.TotalRevenue.TwelveMonths < 900000000
and x.FinancialStatements.CashFlowStatement.OperatingCashFlow.TwelveMonths > 0
and x.OperationRatios.ROE.OneYear > 0.15
and x.SecurityReference.IsPrimaryShare
and x.SecurityReference.SecurityType == "ST00000001"
and x.SecurityReference.IsDepositaryReceipt == 0
and x.CompanyReference.IsLimitedPartnership == 0]
#and x.AssetClassification.MorningstarSectorCode == MorningstarSectorCode.Technology]
return [x.Symbol for x in fine]