Back to Community
DailyWeeklyMonthlyQuarterlyYearly

Hi Quantopian,
I built this with optimize api but then took it out and was happy to get similar dollar neutral performance with my simple architecture in this model. I also think this is cool because it autosizes off of capital. Just try running with different dollar amounts 10k, 100k, 1m, 10m, 100m, 500m etc... you will notice position counts change for each backtest but leverage and long/short stay within bounds.

Dan Whitnable I'm curious to get your feedback as well as any Q staff. The notable features in this post IMO:

  • despite not using optimize api, this seems to be fairly risk neutral when referring to backtest risk metrics
  • the simple architecture lends itself well to autoscaling positions based on capital
  • Using only 5 simple USEquityPricing data points (daily, weekly, monthly, quarterly, annually) provides remarkable hedge ability during GFC and Covid downturns

Please reply Dan, as there are more concerning matters that I'd like to discuss, but not worth getting into until this gains some traction.
Please review the code, I'm proud of what it does in 140 lines, and also skeptical of the alpha demonstrated.
Thanks,
Stephan

Clone Algorithm
11
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from quantopian.pipeline.factors import CustomFactor, SimpleBeta,   AverageDollarVolume, Returns, SimpleMovingAverage
from quantopian.pipeline.experimental import risk_loading_pipeline
from quantopian.pipeline.data import Fundamentals
import quantopian.algorithm as algo
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS
import quantopian.optimize as opt
from sklearn import preprocessing
import talib 
import pandas as pd
import numpy as np
import math
def round_up_to_even(f):
    return math.ceil(f / 2.) * 2
class Factor_N_Days_Ago(CustomFactor):  
    def compute(self, today, assets, out, input_factor):  
        out[:] = input_factor[0]

        
def initialize(context):
    context.init = True  
    algo.schedule_function(func=recording_statements,
                      date_rule=date_rules.every_day(),
                      time_rule=time_rules.market_close(),
                      half_days=True)   
    algo.schedule_function(
        rebalance,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(minutes=60))    
    algo.schedule_function(  
        print_positions,  
        algo.date_rules.every_day(),  
        time_rules.market_close()) 
    
    algo.attach_pipeline(make_pipeline(), 'pipeline')

    
   
    
def print_positions(context, data):
    differential = 0
    print("******************************")
    
    for ea in context.portfolio.positions:
        
        symbol = context.portfolio.positions[ea].asset.symbol
        total = context.portfolio.portfolio_value
        currentPrice = context.portfolio.positions[ea].last_sale_price
        shares = context.portfolio.positions[ea].amount
        percent = currentPrice*shares/total 
        if percent >= 0:           
            dollarAmount = '$'+str(round(percent*total,2))
        else:
            dollarAmount = '-$'+str(round(abs(percent*total)))
        print(str(symbol)+": "+str(round(percent*100))+"% : "+dollarAmount)
        
def recording_statements(context, data):
 
    record(num_positions=len(context.portfolio.positions))
    record(leverage=context.account.leverage)        

def convertWeights(series, inc):
    
    arrayLength = len(series)
    halfWay = arrayLength/2
    startingWeight = 1/arrayLength
    if arrayLength%2 == 0 and arrayLength*inc<1:
        i=0
        d = 0
        e = arrayLength/2
        for index, value in series.items():
          if i > halfWay:
            series[index] =   1 -d*inc
            d = d+1
          else:
            series[index] = 1 +e*inc
            e = e-1
          i = i+1
       
    return series
    
def make_pipeline():
   # market_cap = Fundamentals.market_cap.latest > 300000000 
    # Base universe set to the QTradableStocksUS
    base_universe = QTradableStocksUS()
    yesterday_close = USEquityPricing.close.latest
    weekClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 6)
    monthClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 20)
    quarterClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 60)
    yearClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 252)
    pipe = Pipeline(
        columns={
            'delta1':weekClose/yearClose*3,
            'delta2':yearClose/yesterday_close,
            'delta3':quarterClose/weekClose*-1,
            'delta4':monthClose/yesterday_close # tweak 1
        },
        screen=base_universe
    )
    return pipe


def before_trading_start(context, data):
    df = algo.pipeline_output('pipeline')
    context.output = df.sum(axis=1)
    

def rebalance(context, data):
    total = context.portfolio.portfolio_value
    log = math.log10(total)
    if total < 10000:
        log = log
    elif total >= 10000 and total <= 1000000:
        log = log*2
    elif total < 50000000:
        log = log*3
    elif total < 100000000:
        log = log*5
    elif total < 150000000:
        log = log*6
    elif total < 200000000:
        log = log*7
    elif total < 300000000:
        log = log*9
    elif total < 500000000:
        log = log*16
    else:
        log = log*20
    
    positionCount = round_up_to_even(log)
    maxFraction = 1/positionCount/2
    
    combined_alpha = context.output.dropna()
    combined_alpha = combined_alpha.sort_values(ascending=True)
    short = combined_alpha.head(positionCount)
    long = combined_alpha.tail(positionCount).sort_values(ascending=False)
   
    short = convertWeights(short,maxFraction)
    for index, value in long.items():
        order_target_percent(index, value/long.sum()/2)
  
    for index, value in short.items():
        order_target_percent(index, -1/positionCount/2*value)
    for stock in context.portfolio.positions:
        if stock not in short.index and stock not in long.index:
            order_target_percent(stock, 0)
There was a runtime error.
4 responses

100 m

Clone Algorithm
11
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from quantopian.pipeline.factors import CustomFactor, SimpleBeta,   AverageDollarVolume, Returns, SimpleMovingAverage
from quantopian.pipeline.experimental import risk_loading_pipeline
from quantopian.pipeline.data import Fundamentals
import quantopian.algorithm as algo
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS
import quantopian.optimize as opt
from sklearn import preprocessing
import talib 
import pandas as pd
import numpy as np
import math
def round_up_to_even(f):
    return math.ceil(f / 2.) * 2
class Factor_N_Days_Ago(CustomFactor):  
    def compute(self, today, assets, out, input_factor):  
        out[:] = input_factor[0]

        
def initialize(context):
    context.init = True  
    algo.schedule_function(func=recording_statements,
                      date_rule=date_rules.every_day(),
                      time_rule=time_rules.market_close(),
                      half_days=True)   
    algo.schedule_function(
        rebalance,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(minutes=60))    
    algo.schedule_function(  
        print_positions,  
        algo.date_rules.every_day(),  
        time_rules.market_close()) 
    
    algo.attach_pipeline(make_pipeline(), 'pipeline')

    
   
    
def print_positions(context, data):
    differential = 0
    print("******************************")
    
    for ea in context.portfolio.positions:
        
        symbol = context.portfolio.positions[ea].asset.symbol
        total = context.portfolio.portfolio_value
        currentPrice = context.portfolio.positions[ea].last_sale_price
        shares = context.portfolio.positions[ea].amount
        percent = currentPrice*shares/total 
        if percent >= 0:           
            dollarAmount = '$'+str(round(percent*total,2))
        else:
            dollarAmount = '-$'+str(round(abs(percent*total)))
        print(str(symbol)+": "+str(round(percent*100))+"% : "+dollarAmount)
        
def recording_statements(context, data):
 
    record(num_positions=len(context.portfolio.positions))
    record(leverage=context.account.leverage)        

def convertWeights(series, inc):
    
    arrayLength = len(series)
    halfWay = arrayLength/2
    startingWeight = 1/arrayLength
    if arrayLength%2 == 0 and arrayLength*inc<1:
        i=0
        d = 0
        e = arrayLength/2
        for index, value in series.items():
          if i > halfWay:
            series[index] =   1 -d*inc
            d = d+1
          else:
            series[index] = 1 +e*inc
            e = e-1
          i = i+1
       
    return series
    
def make_pipeline():
   # market_cap = Fundamentals.market_cap.latest > 300000000 
    # Base universe set to the QTradableStocksUS
    base_universe = QTradableStocksUS()
    yesterday_close = USEquityPricing.close.latest
    weekClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 6)
    monthClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 20)
    quarterClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 60)
    yearClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 252)
    pipe = Pipeline(
        columns={
            'delta1':weekClose/yearClose*3,
            'delta2':yearClose/yesterday_close,
            'delta3':quarterClose/weekClose*-1,
            'delta4':monthClose/yesterday_close # tweak 1
        },
        screen=base_universe
    )
    return pipe


def before_trading_start(context, data):
    df = algo.pipeline_output('pipeline')
    context.output = df.sum(axis=1)
    

def rebalance(context, data):
    total = context.portfolio.portfolio_value
    log = math.log10(total)
    if total < 10000:
        log = log
    elif total >= 10000 and total <= 1000000:
        log = log*2
    elif total < 50000000:
        log = log*3
    elif total < 100000000:
        log = log*5
    elif total < 150000000:
        log = log*6
    elif total < 200000000:
        log = log*7
    elif total < 300000000:
        log = log*9
    elif total < 500000000:
        log = log*16
    else:
        log = log*20
    
    positionCount = round_up_to_even(log)
    maxFraction = 1/positionCount/2
    
    combined_alpha = context.output.dropna()
    combined_alpha = combined_alpha.sort_values(ascending=True)
    short = combined_alpha.head(positionCount)
    long = combined_alpha.tail(positionCount).sort_values(ascending=False)
   
    short = convertWeights(short,maxFraction)
    for index, value in long.items():
        order_target_percent(index, value/long.sum()/2)
  
    for index, value in short.items():
        order_target_percent(index, -1/positionCount/2*value)
    for stock in context.portfolio.positions:
        if stock not in short.index and stock not in long.index:
            order_target_percent(stock, 0)
There was a runtime error.

200 m

Clone Algorithm
11
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from quantopian.pipeline.factors import CustomFactor, SimpleBeta,   AverageDollarVolume, Returns, SimpleMovingAverage
from quantopian.pipeline.experimental import risk_loading_pipeline
from quantopian.pipeline.data import Fundamentals
import quantopian.algorithm as algo
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS
import quantopian.optimize as opt
from sklearn import preprocessing
import talib 
import pandas as pd
import numpy as np

class Factor_N_Days_Ago(CustomFactor):  
    def compute(self, today, assets, out, input_factor):  
        out[:] = input_factor[0]

        
def initialize(context):
    context.init = True  
    algo.schedule_function(func=recording_statements,
                      date_rule=date_rules.every_day(),
                      time_rule=time_rules.market_close(),
                      half_days=True)   
    algo.schedule_function(
        rebalance,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(minutes=60))    
    algo.schedule_function(  
        print_positions,  
        algo.date_rules.every_day(),  
        time_rules.market_close()) 
    
    algo.attach_pipeline(make_pipeline(), 'pipeline')

    
   
    
def print_positions(context, data):
    differential = 0
    print("******************************")
    for ea in context.portfolio.positions:
        total = context.portfolio.portfolio_value
        currentPrice = context.portfolio.positions[ea].last_sale_price
        shares = context.portfolio.positions[ea].amount
        differential = currentPrice*shares/total  + differential    
       
    if differential == 0:
        differential = 1
    for ea in context.portfolio.positions:
        
        symbol = context.portfolio.positions[ea].asset.symbol
        total = context.portfolio.portfolio_value
        currentPrice = context.portfolio.positions[ea].last_sale_price
        shares = context.portfolio.positions[ea].amount
        percent = currentPrice*shares/total 
        if percent >= 0:           
            dollarAmount = '$'+str(round(percent*total,2))
        else:
            dollarAmount = '-$'+str(round(abs(percent*total)))
        print(str(symbol)+": "+str(round(percent*100))+"% : "+dollarAmount)
        
def recording_statements(context, data):
 
    record(num_positions=len(context.portfolio.positions))
    record(leverage=context.account.leverage)        

def make_pipeline():
   # market_cap = Fundamentals.market_cap.latest > 300000000 
    # Base universe set to the QTradableStocksUS
    base_universe = QTradableStocksUS()
    yesterday_close = USEquityPricing.close.latest
    weekClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 6)
    monthClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 20)
    quarterClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 60)
    yearClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 252)
    pipe = Pipeline(
        columns={
            'delta1':weekClose/yearClose*3,
            'delta2':yearClose/yesterday_close,
            'delta3':quarterClose/weekClose*-1,
            'delta4':monthClose/yesterday_close # tweak 1
        },
        screen=base_universe
    )
    return pipe


def before_trading_start(context, data):
    df = algo.pipeline_output('pipeline')
    context.output = df.sum(axis=1)

positionCount = 100
def rebalance(context, data):
    combined_alpha = context.output.dropna()
    combined_alpha = combined_alpha.sort_values(ascending=True)
    short = combined_alpha.head(positionCount)
    long = combined_alpha.tail(positionCount).sort_values(ascending=False)
    
    for index, value in long.items():
        order_target_percent(index, 1/positionCount/2)
    for index, value in short.items():
        order_target_percent(index, -1/positionCount/2)
    for stock in context.portfolio.positions:
        if stock not in short.index and stock not in long.index:
            order_target_percent(stock, 0)
            
There was a runtime error.

1 m

Clone Algorithm
11
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from quantopian.pipeline.factors import CustomFactor, SimpleBeta,   AverageDollarVolume, Returns, SimpleMovingAverage
from quantopian.pipeline.experimental import risk_loading_pipeline
from quantopian.pipeline.data import Fundamentals
import quantopian.algorithm as algo
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS
import quantopian.optimize as opt
from sklearn import preprocessing
import talib 
import pandas as pd
import numpy as np

class Factor_N_Days_Ago(CustomFactor):  
    def compute(self, today, assets, out, input_factor):  
        out[:] = input_factor[0]

        
def initialize(context):
    context.init = True  
    algo.schedule_function(func=recording_statements,
                      date_rule=date_rules.every_day(),
                      time_rule=time_rules.market_close(),
                      half_days=True)   
    algo.schedule_function(
        rebalance,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(minutes=60))    
    algo.schedule_function(  
        print_positions,  
        algo.date_rules.every_day(),  
        time_rules.market_close()) 
    
    algo.attach_pipeline(make_pipeline(), 'pipeline')

    
   
    
def print_positions(context, data):
    differential = 0
    print("******************************")
    for ea in context.portfolio.positions:
        total = context.portfolio.portfolio_value
        currentPrice = context.portfolio.positions[ea].last_sale_price
        shares = context.portfolio.positions[ea].amount
        differential = currentPrice*shares/total  + differential    
       
    if differential == 0:
        differential = 1
    for ea in context.portfolio.positions:
        
        symbol = context.portfolio.positions[ea].asset.symbol
        total = context.portfolio.portfolio_value
        currentPrice = context.portfolio.positions[ea].last_sale_price
        shares = context.portfolio.positions[ea].amount
        percent = currentPrice*shares/total 
        if percent >= 0:           
            dollarAmount = '$'+str(round(percent*total,2))
        else:
            dollarAmount = '-$'+str(round(abs(percent*total)))
        print(str(symbol)+": "+str(round(percent*100))+"% : "+dollarAmount)
        
def recording_statements(context, data):
 
    record(num_positions=len(context.portfolio.positions))
    record(leverage=context.account.leverage)        

def make_pipeline():
   # market_cap = Fundamentals.market_cap.latest > 300000000 
    # Base universe set to the QTradableStocksUS
    base_universe = QTradableStocksUS()
    yesterday_close = USEquityPricing.close.latest
    weekClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 6)
    monthClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 20)
    quarterClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 60)
    yearClose = Factor_N_Days_Ago(inputs = [USEquityPricing.close], window_length = 252)
    pipe = Pipeline(
        columns={
            'delta1':weekClose/yearClose*3,
            'delta2':yearClose/yesterday_close,
            'delta3':quarterClose/weekClose*-1,
            'delta4':monthClose/yesterday_close # tweak 1
        },
        screen=base_universe
    )
    return pipe


def before_trading_start(context, data):
    df = algo.pipeline_output('pipeline')
    context.output = df.sum(axis=1)

positionCount = 8
def rebalance(context, data):
    combined_alpha = context.output.dropna()
    combined_alpha = combined_alpha.sort_values(ascending=True)
    short = combined_alpha.head(positionCount)
    long = combined_alpha.tail(positionCount)
    
    for index, value in long.items():
        order_target_percent(index, 1/positionCount/2)
    for index, value in short.items():
        order_target_percent(index, -1/positionCount/2)
    for stock in context.portfolio.positions:
        if stock not in short.index and stock not in long.index:
            order_target_percent(stock, 0)
There was a runtime error.