Overall Statistics
Total Trades
963
Average Win
0.74%
Average Loss
-0.83%
Compounding Annual Return
-0.183%
Drawdown
32.600%
Expectancy
-0.016
Net Profit
-3.555%
Sharpe Ratio
-0.001
Probabilistic Sharpe Ratio
0.000%
Loss Rate
48%
Win Rate
52%
Profit-Loss Ratio
0.90
Alpha
-0.001
Beta
0.017
Annual Standard Deviation
0.058
Annual Variance
0.003
Information Ratio
-0.367
Tracking Error
0.192
Treynor Ratio
-0.002
Total Fees
$164.96
# https://quantpedia.com/strategies/advertising-effect-within-stocks/
#
# The investment universe consists of firms from NYSE, Amex and Nasdaq with a market capitalization greater than $20 million in the prior year. 
# Investor defines year t as the advertising year, year t − 1 as the year prior to the advertising year, and year t + 1 as the year subsequent 
# to the advertising year. Change in advertising in year t (∆Advt) is measured as the change in the log values of advertising expenditures 
# from year t − 1 to year t.
# Investor ranks stocks into ten deciles every year based on ∆Advt. He then forms a zero-investment portfolio that shorts the stocks in decile
# 10 (high advertising stocks) and longs the stocks in decile 1 (low advertising stocks). Stocks are bought in the 7th month of the year 
# subsequent to the advertising year (to ensure that investor has all financial data from previous year) and are held for subsequent 6 months. 
# Stocks in the portfolio are equally weighted.

from collections import deque
import numpy as np
import fk_tools

class Advertising_Effect(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetEndDate(2019, 10, 1)
        self.SetCash(100000)

        self.course_count = 1000
        
        self.long = []
        self.short = []
        self.adv_expenses = {}
        
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
        
        self.symbol = 'SPY'
        self.AddEquity(self.symbol, Resolution.Daily)
        
        self.record_adv_expenses_flag = False
        self.selection_flag = False
        self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Rebalance)
    
    def OnSecuritiesChanged(self, changes):
        for security in changes.AddedSecurities:
            security.SetFeeModel(fk_tools.CustomFeeModel(self))
        
    def CoarseSelectionFunction(self, coarse):
        if not self.selection_flag and not self.record_adv_expenses_flag:
            return Universe.Unchanged
        
        selected = sorted([x for x in coarse if x.HasFundamentalData and x.Price > 5 and x.Market == 'usa'],
            key=lambda x: x.DollarVolume, reverse=True)
        
        return [x.Symbol for x in selected[:self.course_count]]

    def FineSelectionFunction(self, fine):
        fine = [x for x in fine if x.FinancialStatements.IncomeStatement.SellingAndMarketingExpense.ThreeMonths > 0]
        
        if self.record_adv_expenses_flag:
            for stock in fine:
                symbol = stock.Symbol
            
                if symbol not in self.adv_expenses:
                    self.adv_expenses[symbol] = deque(maxlen=2)
                
                adv_expenses = stock.FinancialStatements.IncomeStatement.SellingAndMarketingExpense.ThreeMonths
                self.adv_expenses[symbol].append(adv_expenses)
            
            # NOTE: Get rid of old advertisment records so we work with latest values.
            del_symbols = []
            for symbol in self.adv_expenses:
                if symbol not in [x.Symbol for x in fine]:
                    del_symbols.append(symbol)
            for symbol in del_symbols:
                self.adv_expenses.pop(symbol)
            
            self.record_adv_expenses_flag = False
            
        elif self.selection_flag:
            d_adv = {}
            
            for stock in fine:
                symbol = stock.Symbol
                
                if symbol in self.adv_expenses and len(self.adv_expenses[symbol]) == self.adv_expenses[symbol].maxlen:
                    expanses_values = [x for x in self.adv_expenses[symbol]]
                    d_adv[symbol] = fk_tools.Return(expanses_values)
                
            self.selection_flag = False
            if len(d_adv) == 0: return []
    
            sorted_by_adv = sorted(d_adv.items(), key = lambda x: x[1], reverse = True)
            decile = int(len(sorted_by_adv)/10)
            self.long = [x[0] for x in sorted_by_adv[-decile:]]
            self.short = [x[0] for x in sorted_by_adv[:decile]]
        
            return self.long + self.short
        
        return []
        
    def Rebalance(self):
        month = self.Time.month
        if month == 12:
            self.Liquidate()
            self.record_adv_expenses_flag = True
        elif month == 5:
            self.selection_flag = True

        # Trade execution and rebalance
        count = len(self.long + self.short)
        if count == 0: return

        for symbol in self.long:
            self.SetHoldings(symbol, 1/count)
        for symbol in self.short:
            self.SetHoldings(symbol, -1/count)
            
        self.long.clear()
        self.short.clear()
import numpy as np

def Return(values):
    return (values[-1] - values[0]) / values[0]
    
def Volatility(values):
    values = np.array(values)
    returns = (values[1:]-values[:-1])/values[:-1]
    return np.std(returns)  

# Custom fee model
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))
        
# Quantpedia data
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
    def GetSource(self, config, date, isLiveMode):
        return SubscriptionDataSource("https://quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)

    def Reader(self, config, line, date, isLiveMode):
        data = QuantpediaFutures()
        data.Symbol = config.Symbol
        
        try:
            if not line[0].isdigit(): return None
            split = line.split(';')
            
            data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
            data['settle'] = float(split[1])
            data.Value = float(split[1])
        except:
            return None
            
        return data
# https://quantpedia.com/strategies/momentum-stock-picking-strategy-using-rsi-indicator/
#
# The investment universe consists of stocks from the S&P 500. “N” is set to be 25 days. Firstly, we use 14 – day RSI to assess
# if stocks are in a bull range (RSI fluctuates between 40 and 100 over 25 days). Then we use the momentum signal – we check the 
# highest high value of 14 – day RSI. It has to be greater than 70 over 25 days to select stocks into the trading portfolio. Stocks
# are weighted equally, and the portfolio is rebalanced on a daily basis.

import fk_tools
from collections import deque

class Momentum_Stock_Picking_RSI_Indicator(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetEndDate(2019, 9, 1)
        self.SetCash(100000)

        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.Universe.Index.QC500)
        
        self.rsi = {}
        self.rsi_history = {}
        self.period = 25
        self.SetWarmUp(self.period)
        
        symbol = 'SPY'
        self.AddEquity(symbol, Resolution.Daily)
        
        self.Schedule.On(self.DateRules.EveryDay(symbol), self.TimeRules.AfterMarketOpen(symbol), self.Rebalance)

    def OnSecuritiesChanged(self, changes):
        for security in changes.AddedSecurities:
            security.SetFeeModel(fk_tools.CustomFeeModel(self))

    def Rebalance(self):
        long = []
        for symbol in self.Securities.Keys:
            if not symbol in self.rsi:
                self.rsi[symbol] = self.RSI(symbol, 14, MovingAverageType.Simple, Resolution.Daily)
                self.rsi_history[symbol] = deque(maxlen = self.period)
        
            if not self.rsi[symbol].IsReady: continue
        
            self.rsi_history[symbol].append(self.rsi[symbol].Current.Value)
            
            if len(self.rsi_history[symbol]) == self.rsi_history[symbol].maxlen:
                rsi_values = [x for x in self.rsi_history[symbol]]
                max_rsi = max(rsi_values)
                bull_range = sum([x > 40 for x in rsi_values]) == self.period
                
                if max_rsi > 70 and bull_range:
                    long.append(symbol)
                else:
                    if self.Portfolio[symbol].Invested:
                        self.Liquidate(symbol)
                
        # Open new trades
        count = len(long)
        if count == 0: return
        
        for symbol in long:
            self.SetHoldings(symbol, 1/count)