Overall Statistics
Total Orders
2236
Average Win
0.19%
Average Loss
-0.17%
Compounding Annual Return
0.511%
Drawdown
13.600%
Expectancy
0.038
Start Equity
1000000
End Equity
1058604.54
Net Profit
5.860%
Sharpe Ratio
-0.236
Sortino Ratio
-0.256
Probabilistic Sharpe Ratio
0.016%
Loss Rate
51%
Win Rate
49%
Profit-Loss Ratio
1.14
Alpha
-0.009
Beta
-0.009
Annual Standard Deviation
0.038
Annual Variance
0.001
Information Ratio
-0.379
Tracking Error
0.169
Treynor Ratio
0.997
Total Fees
$6163.04
Estimated Strategy Capacity
$12000.00
Lowest Capacity Asset
SSNT WJTX1OBQ409X
Portfolio Turnover
0.26%
#region imports
from AlgorithmImports import *
#endregion


class NetCurrentAssetValue(QCAlgorithm):

    def initialize(self):
        #rebalancing should occur in July
        self.set_start_date(2007, 5, 15)  
        self.set_end_date(2018, 7, 15)
        self.set_cash(1_000_000)
        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)
        spy = Symbol.create("SPY", SecurityType.EQUITY, Market.USA)
        #monthly scheduled event but will only rebalance once a year
        self.schedule.on(self.date_rules.month_start(spy), self.time_rules.at(23, 0), 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]
        return []      
    
    def _fine_selection_function(self, fine):
        if self._yearly_rebalance:
            #filters out the companies that don't contain the necessary data
            fine = [x for x in fine if (float(x.financial_statements.balance_sheet.current_assets.value) > 0) 
                                    and (float(x.financial_statements.balance_sheet.cash_and_cash_equivalents.value) > 0)
                                    and (float(x.financial_statements.balance_sheet.current_liabilities.value) > 0)
                                    and (float(x.financial_statements.balance_sheet.current_debt.value) > 0)
                                    and (float(x.financial_statements.balance_sheet.income_tax_payable.value) > 0)
                                    and (float(x.financial_statements.income_statement.depreciation_and_amortization.value) > 0)]
            
            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 balance sheet accruals
                self._filtered_fine = self._calculate_accruals(fine, self._previous_fine)
                sorted_filter = sorted(self._filtered_fine, key=lambda x: x.bs_acc)
                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_accruals(self, current, previous):
        accruals = []
        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
                
                #calculates the balance sheet accruals and adds the property to the fine fundamental object
                delta_assets = float(stock_data.financial_statements.balance_sheet.current_assets.value)-float(prev_data.financial_statements.balance_sheet.current_assets.value)
                delta_cash = float(stock_data.financial_statements.balance_sheet.cash_and_cash_equivalents.value)-float(prev_data.financial_statements.balance_sheet.cash_and_cash_equivalents.value)
                delta_liabilities = float(stock_data.financial_statements.balance_sheet.current_liabilities.value)-float(prev_data.financial_statements.balance_sheet.current_liabilities.value)
                delta_debt = float(stock_data.financial_statements.balance_sheet.current_debt.value)-float(prev_data.financial_statements.balance_sheet.current_debt.value)
                delta_tax = float(stock_data.financial_statements.balance_sheet.income_tax_payable.value)-float(prev_data.financial_statements.balance_sheet.income_tax_payable.value)
                dep = float(stock_data.financial_statements.income_statement.depreciation_and_amortization.value)
                avg_total = (float(stock_data.financial_statements.balance_sheet.total_assets.value)+float(prev_data.financial_statements.balance_sheet.total_assets.value))/2
                #accounts for the size difference
                stock_data.bs_acc = ((delta_assets-delta_cash)-(delta_liabilities-delta_debt-delta_tax)-dep)/avg_total
                accruals.append(stock_data)
            except:
                #value in current universe does not exist in the previous universe
                pass
        return accruals
    
    def _rebalance(self):
        #yearly rebalance
        self._months += 1
        if self._months % 12 == 0:
            self._yearly_rebalance = True

    def on_data(self, data):
        if not self._yearly_rebalance or self.is_warming_up: 
            return 
        if self._filtered_fine:
            filtered_fine = [s for s in self._filtered_fine if s in data]
            portfolio_size = int(len(filtered_fine)/10)
            #pick the upper decile to short and the lower decile to long
            targets = []

            longs = filtered_fine[:portfolio_size]
            long_weight = 0.25/len(longs)
            for symbol in longs:
                targets.append(PortfolioTarget(symbol, long_weight))

            shorts = filtered_fine[-portfolio_size:]
            short_weight = -0.25/len(shorts)
            for symbol in shorts:
                targets.append(PortfolioTarget(symbol, short_weight))
            
            self.set_holdings(targets, True)
            self._yearly_rebalance = False
            self._filtered_fine = False