Back to Community
Attempt to Recreate Piotroski Score and Trouble with Backtesting Time

Hi guys,

I am still very new to Quantopian and to programming as well. From these posts (https://www.quantopian.com/posts/modified-piotroski-score-on-ev-slash-ebitda-sort, https://www.quantopian.com/posts/piotroski-score-plus-aroon-indicator), I attempted to recreate a simple long-only Piotroksi score algorithm, and the performance seemed to be pretty good (despite terrible metrics). However, it took an insane amount of time to backtest. Is there any way to speed the process up?

Moreover, the return plummeted during the 2008 recession. In this post, https://www.quantopian.com/posts/modified-piotroski-score-on-ev-slash-ebitda-sort, Johnny Wu made a stop loss to minimize the impact, but I just can't understand how it worked. I am still very new to all this, so I really hope that you guys can help me know how to make a stop-loss.

Thanks all,

Thanh Duong

Clone Algorithm
14
Loading...
Backtest from to with initial capital
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.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline.classifiers.fundamentals import Sector 
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.data import Fundamentals
from quantopian.pipeline import CustomFactor  
from quantopian.pipeline.experimental import QTradableStocksUS

def initialize(context):
    context.long_leverage = 1.0

    # Rebalance on the first trading day of each month at 11AM.
    schedule_function(rebalance,
                      date_rules.month_start(days_offset=0),
                      time_rules.market_open(hours=1, minutes=30))

    # Create and attach our pipeline (dynamic stock selector), defined below.
    attach_pipeline(make_pipeline(context), 'pit')

def make_pipeline(context):
  # Get Latest Fundamentals 
  universe1 = Fundamentals.market_cap.latest > 5e8
  universe2 = Fundamentals.market_cap.latest < 1e10 
  small_mid_cap = universe1 & universe2
  OCF = Fundamentals.operating_cash_flow.latest
  debt_to_asset = Fundamentals.debtto_assets.latest
  roa = Fundamentals.roa.latest
  quick_ratio = Fundamentals.quick_ratio.latest
  outstanding_shares = Fundamentals.shares_outstanding.latest
  gross_margin = Fundamentals.gross_margin.latest
  assets_turnover = Fundamentals.assets_turnover.latest


  #Get Last Year Fundamentals
  class Previous(CustomFactor):  
    # Returns value of input x trading days ago where x is the window_length  
    # Both the inputs and window_length must be specified as there are no defaults
      def compute(self, today, assets, out, inputs):  
          out[:] = inputs[0]
  context.window_length = 252
  roa2 = Previous(inputs = [Fundamentals.roa], window_length = context.window_length)
  debt_to_asset2 = Previous(inputs = [Fundamentals.debtto_assets], window_length = context.window_length)
  quick_ratio2 = Previous(inputs = [Fundamentals.quick_ratio], window_length = context.window_length)
  outstanding_shares2 = Previous(inputs = [Fundamentals.shares_outstanding], window_length = context.window_length)
  gross_margin2 = Previous(inputs = [Fundamentals.gross_margin], window_length = context.window_length)
  assets_turnover2 = Previous(inputs = [Fundamentals.assets_turnover], window_length = context.window_length)

  result = Pipeline(
    columns={
        'OCF':OCF,
        'roa': roa,
        'roa2':roa2,
        'debt_to_asset':debt_to_asset,
        'debt_to_asset2':debt_to_asset2,
        'quick_ratio':quick_ratio,
        'quick_ratio2':quick_ratio2,
        'outstanding_shares':outstanding_shares,
        'outstanding_shares2':outstanding_shares2,    
        'gross_margin':gross_margin,
        'gross_margin2':gross_margin2,
        'assets_turnover':assets_turnover,
        'assets_turnover2':assets_turnover2,
        }, screen = small_mid_cap
  )
  return result

def before_trading_start(context, data):
  context.output = pipeline_output('pit')
  result = context.output.dropna(axis=0)


  #Output True if Change in Asset Turnover > 0, False if otherwise
  result.loc[:,('delta_assets_turnover')] = result.loc[:,('assets_turnover')] - result.loc[:,('assets_turnover2')] > 0

  #Output True if Change in ROA > 0, False if otherwise  
  result.loc[:,('delta_roa')] = result.loc[:,('roa')] - result.loc[:,('roa2')] > 0

  #Output True if Change in gross margin > 0, False if otherwise    
  result.loc[:,('delta_gross_margin')] = result.loc[:,('gross_margin')] - result.loc[:,('gross_margin2')] > 0

  #Output True if Change in quick ratio > 0, False if otherwise    
  result.loc[:,('delta_quick_ratio')] = result.loc[:,('quick_ratio')] - result.loc[:,('quick_ratio2')] > 0

  #Output True if Change in shares outstanding < 0, False if otherwise    
  result.loc[:,('delta_OS')] = result.loc[:,('outstanding_shares')] - result.loc[:,('outstanding_shares2')] <= 0

  #Output True if Change in LT Debt/Total Asset > 0, False if otherwise      
  result.loc[:,('delta_DA')] = result.loc[:,('debt_to_asset')] - result.loc[:,('debt_to_asset2')] >0
 
  #Output True if Operative Cash Flow is positive, False if otherwise    
  result.loc[:,('positive_cash_flow')] = result.loc[:,('OCF')] >0

  #Output True if ROA is positive, False if otherwise  
  result.loc[:,('positive_roa')] = result.loc[:,('roa')]>0

  #Output True if ROA < Operating Cash Flow, False if otherwise  
  result.loc[:,('accrual')] = result.loc[:,('roa')] < result.loc[:,('OCF')]
  result = result.drop(['OCF','assets_turnover','roa','assets_turnover2','debt_to_asset',
                      'debt_to_asset2','gross_margin','gross_margin2','outstanding_shares',
                      'outstanding_shares2','quick_ratio','quick_ratio2','roa2'], axis=1)
  #Change True/False Dataframe into 1 and 0 Dataframe
  result = result.astype(int)
  #Sum row to get the score
  result.loc[:,('score')] = result.sum(axis=1)
  result9 = result[result['score'] == 9] 
  context.long_group = result9.index.tolist()
def rebalance(context,data):
    for stock in context.portfolio.positions:
        if stock not in context.long_group and data.can_trade(stock):
            order_target_percent(stock, 0)
    for stock in context.long_group:
        if data.can_trade(stock):
           order_target_percent(stock, context.long_leverage/len(context.long_group))
There was a runtime error.