Overall Statistics
Total Orders
1315
Average Win
0.58%
Average Loss
-0.59%
Compounding Annual Return
-5.124%
Drawdown
29.500%
Expectancy
-0.037
Start Equity
100000
End Equity
83914.93
Net Profit
-16.085%
Sharpe Ratio
-0.36
Sortino Ratio
-0.444
Probabilistic Sharpe Ratio
0.286%
Loss Rate
51%
Win Rate
49%
Profit-Loss Ratio
0.98
Alpha
-0.045
Beta
0.057
Annual Standard Deviation
0.115
Annual Variance
0.013
Information Ratio
-0.674
Tracking Error
0.154
Treynor Ratio
-0.724
Total Fees
$1695.84
Estimated Strategy Capacity
$0
Lowest Capacity Asset
WYNN SJ56738TCX9H
Portfolio Turnover
8.50%
#region imports
from AlgorithmImports import *
#endregion
#This is a Template of dynamic stock selection.
#You can try your own fundamental factor and ranking method by editing the CoarseSelectionFunction and FineSelectionFunction


class BasicTemplateAlgorithm(QCAlgorithm):
    
    def __init__(self):
        # set the flag for rebalance
        self._reb = 1
        # Number of stocks to pass CoarseSelection process
        self._num_coarse = 250
        # Number of stocks to long/short
        self._num_fine = 10
        self._symbols = None

    def initialize(self):
        self.set_cash(100000)
        self.set_start_date(2015,1,1)
        self.set_end_date(2018,5,1)
        
        self.set_security_initializer(
            BrokerageModelSecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices))
        )
        self._spy = self.add_equity("SPY", Resolution.DAILY).symbol
        
        self.universe_settings.resolution = Resolution.DAILY
        self.add_universe(self._coarse_selection_function,self._fine_selection_function)
        
        # Schedule the rebalance function to execute at the begining of each month
        self.schedule.on(
            self.date_rules.month_start(self._spy), 
            self.time_rules.after_market_open(self._spy,5), 
            self._rebalance
        )
    
    def _coarse_selection_function(self, coarse):
        # if the rebalance flag is not 1, return null list to save time.
        if self._reb != 1:
            return self._long + self._short
            
        # make universe selection once a month
        # drop stocks which have no fundamental data or have too low prices
        selected = [x for x in coarse if (x.has_fundamental_data) 
                    and (float(x.price) > 5)]
        
        sorted_by_dollar_volume = sorted(selected, key=lambda x: x.dollar_volume, reverse=True) 
        top = sorted_by_dollar_volume[:self._num_coarse]
        return [i.symbol for i in top]

    def _fine_selection_function(self, fine):
        # return null list if it's not time to rebalance
        if self._reb != 1:
            return self._long + self._short
            
        self._reb = 0
            
        # drop stocks which don't have the information we need.
        # you can try replacing those factor with your own factors here
        filtered_fine = [x for x in fine if x.operation_ratios.operation_margin.value
                                        and x.valuation_ratios.price_change_1m 
                                        and x.valuation_ratios.book_value_per_share]
                                        
        self.log('remained to select %d'%(len(filtered_fine)))
        
        # rank stocks by three factor.
        sorted_by_factor1 = sorted(filtered_fine, key=lambda x: x.operation_ratios.operation_margin.value, reverse=True)
        sorted_by_factor2 = sorted(filtered_fine, key=lambda x: x.valuation_ratios.price_change_1m, reverse=True)
        sorted_by_factor3 = sorted(filtered_fine, key=lambda x: x.valuation_ratios.book_value_per_share, reverse=True)
        
        stock_dict = {}
        
        # assign a score to each stock, you can also change the rule of scoring here.
        for i,ele in enumerate(sorted_by_factor1):
            rank1 = i
            rank2 = sorted_by_factor2.index(ele)
            rank3 = sorted_by_factor3.index(ele)
            score = sum([rank1*0.2,rank2*0.4,rank3*0.4])
            stock_dict[ele] = score
        
        # sort the stocks by their scores
        sorted_stock = sorted(stock_dict.items(), key=lambda d:d[1],reverse=False)
        sorted_symbol = [x[0] for x in sorted_stock]

        # sotre the top stocks into the long_list and the bottom ones into the short_list
        self._long = [x.symbol for x in sorted_symbol[:self._num_fine]]
        self._short = [x.symbol for x in sorted_symbol[-self._num_fine:]]
    
        return self._long + self._short
    
    def _rebalance(self):
        # if this month the stock are not going to be long/short, liquidate it.
        long_short_list = self._long + self._short
        for symbol, security_holding in self.portfolio.items():
            if (security_holding.invested) and (symbol not in long_short_list):
                self.liquidate(symbol)
                
        # Alternatively, you can liquidate all the stocks at the end of each month.
        # Which method to choose depends on your investment philosiphy 
        # if you prefer to realized the gain/loss each month, you can choose this method.
    
        #self.liquidate()
        
        # Assign each stock equally. Alternatively you can design your own portfolio construction method
        for i in self._long:
            self.set_holdings(i, 0.9/self._num_fine)
        
        for i in self._short:
            self.set_holdings(i, -0.9/self._num_fine)

        self._reb = 1