Overall Statistics
Total Orders
9090
Average Win
0.11%
Average Loss
-0.08%
Compounding Annual Return
7.419%
Drawdown
17.000%
Expectancy
0.203
Start Equity
1000000
End Equity
2058091.14
Net Profit
105.809%
Sharpe Ratio
0.556
Sortino Ratio
0.52
Probabilistic Sharpe Ratio
12.706%
Loss Rate
48%
Win Rate
52%
Profit-Loss Ratio
1.33
Alpha
0.044
Beta
0.001
Annual Standard Deviation
0.08
Annual Variance
0.006
Information Ratio
-0.144
Tracking Error
0.181
Treynor Ratio
35.759
Total Fees
$20357.95
Estimated Strategy Capacity
$0
Lowest Capacity Asset
CAFD W1I7J18MUIQT
Portfolio Turnover
0.52%
#region imports
from AlgorithmImports import *
#endregion


class AssetGrowth(QCAlgorithm):

    _blacklisted_assets = ["TOPT T0KDYN9C3IHX"]  # /datasets/issue/15180

    def initialize(self):
        #rebalancing should occur in July
        self.set_start_date(2008, 6, 15) 
        self.set_end_date(2018, 7, 15)   
        self.set_cash(1000000)         
        self.set_security_initializer(BrokerageModelSecurityInitializer(
            self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices)))
        self.settings.minimum_order_margin_portfolio_percentage = 0
        self.universe_settings.resolution = Resolution.DAILY
        self._previous_fine = None
        self._filtered_fine = None
        self.add_universe(self._coarse_selection_function, self._fine_selection_function)
        self.add_equity("SPY", Resolution.DAILY)
        #monthly scheduled event but will only rebalance once a year
        self.schedule.on(self.date_rules.month_end("SPY"), self.time_rules.midnight, self.rebalance)
        self._months = -1
        self._yearly_rebalance = False
        self.set_warmup(timedelta(365))
        
    def _coarse_selection_function(self, coarse):
        if self._yearly_rebalance:
            # drop stocks which have no fundamental data
            return [x.symbol for x in coarse if x.has_fundamental_data and x.market == Market.USA]
        else: 
            return []

    def _fine_selection_function(self, fine):
        if self._yearly_rebalance:
            fine = [
                x for x in fine 
                if (x.financial_statements.balance_sheet.total_assets.value > 0 and
                    x.security_reference.exchange_id in ['NYS', 'NAS', 'ASE'] and 
                    x.company_reference.industry_template_code != "B" and
                    x.company_reference.industry_template_code != "I" and
                    str(x.symbol.id) not in self._blacklisted_assets)
            ]
            if not self._previous_fine:
                #will wait one year in order to have the historical fundamental data
                self._previous_fine = fine
                self._yearly_rebalance = False
                return []
            else:
                #will calculate and sort the stocks on asset growth
                self._filtered_fine = self._calculate(fine, self._previous_fine)
                sorted_filter = sorted(self._filtered_fine, key=lambda x: x.delta_assets)
                self._filtered_fine = [i.symbol for i in sorted_filter]
                #we save the fine data for the next year's analysis
                self._previous_fine = fine
                return self._filtered_fine
        else:
            return []
    
    def _calculate(self, current, previous):
        growth = []
        for stock_data in current:
            #compares this and last year's fine fundamental objects
            try:
                prev_data = None
                for x in previous:
                    if x.symbol == stock_data.symbol:
                        prev_data = x
                        break
                #growth = (tota_assets(t)-total_assets(t-1))/total_assets(t-1)
                assets = float(stock_data.financial_statements.balance_sheet.total_assets.value)
                previous_assets = float(prev_data.financial_statements.balance_sheet.total_assets.value)
                stock_data.delta_assets = (assets - previous_assets) / previous_assets
                growth.append(stock_data)
            except:
                #value in current universe does not exist in the previous universe
                pass
        return growth
    
    def rebalance(self):
        #yearly rebalance
        self._months+=1
        if self._months%12 == 0:
            self._yearly_rebalance = True

    def _get_tradable_assets(self, symbols):
        tradable_assets = []
        for symbol in symbols:
            security = self.securities[symbol]
            if security.price and security.is_tradable:
                tradable_assets.append(symbol)
        return tradable_assets

    def on_data(self, data):
        if not (self._yearly_rebalance and self._filtered_fine): 
            return 
        if not self.is_warming_up:
            filtered_fine = self._get_tradable_assets(self._filtered_fine)
            portfolio_size = int(len(filtered_fine)/10)
            #pick the upper decile to short and the lower decile to long
            targets = []
            weight = 0.5 / portfolio_size
            for symbol in filtered_fine[-portfolio_size:]:
                targets.append(PortfolioTarget(symbol, -weight))
            for symbol in filtered_fine[:portfolio_size]:
                targets.append(PortfolioTarget(symbol, weight))
            self.set_holdings(targets, True)

        self._yearly_rebalance = False
        self._filtered_fine = False