Overall Statistics
Total Orders
910
Average Win
2.69%
Average Loss
-2.22%
Compounding Annual Return
21.322%
Drawdown
58.000%
Expectancy
0.338
Start Equity
100000
End Equity
1882418.98
Net Profit
1782.419%
Sharpe Ratio
0.592
Sortino Ratio
0.731
Probabilistic Sharpe Ratio
2.441%
Loss Rate
39%
Win Rate
61%
Profit-Loss Ratio
1.21
Alpha
0.081
Beta
1.073
Annual Standard Deviation
0.291
Annual Variance
0.084
Information Ratio
0.353
Tracking Error
0.246
Treynor Ratio
0.16
Total Fees
$67321.52
Estimated Strategy Capacity
$7000.00
Lowest Capacity Asset
AHC U02G802FESTH
Portfolio Turnover
2.80%
# region imports
from AlgorithmImports import *
# endregion
from QuantConnect import Resolution
from QuantConnect.Algorithm import QCAlgorithm
import numpy as np

class NCAVsimple(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2010, 5, 1)  
        #self.SetEndDate(2025,1,1)
        self.SetCash(100000) #약 1.3억 = 100K
        self.UniverseSettings.Resolution = Resolution.Daily
        
        self.settings.MinimumOrderMarginPortfolioPercentage = 0.01
        self.filtered_fine = None
        self.filtered_coarse = None
        self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
        self.settings.daily_precise_end_time = False
        self.last_month = 0
        self.average_valuation_ratios = []
        self.should_rebalance = False
        self.AddUniverse(self.CoarseSelectionFunction,self.FineSelectionFunction)
        self.Schedule.On(self.DateRules.EveryDay(self.symbol),
                 self.TimeRules.AfterMarketOpen(self.symbol, 10),
                 self.rebalance)

    def CoarseSelectionFunction(self, coarse):
        if self.should_rebalance:
            self.filtered_coarse = [x.Symbol for x in coarse if (x.HasFundamentalData)
                                                            and x.Market == 'usa' 
                                                            and x.Price > 0.1
                                                            and x.DollarVolume>100000]
            return self.filtered_coarse
        else: 
            return []      
    
    def FineSelectionFunction(self, fine):
        if self.should_rebalance:
            sorted_by_market_cap = sorted(fine, key = lambda x:x.MarketCap)
            fine = [x for x in sorted_by_market_cap[int(len(sorted_by_market_cap)*0.0):int(len(sorted_by_market_cap)*1)]]
            fine = [x for x in fine if (float(x.FinancialStatements.BalanceSheet.CurrentAssets.Value) > 0) 
                                    and x.FinancialStatements.IncomeStatement.TotalRevenue.Value > 0
                                    # N=Normal (Manufacturing), M=Mining, U=Utility, T=Transportation, B=Bank, I=Insurance 
                                    and (x.CompanyReference.IndustryTemplateCode!="B") #은행업 제외
                                    and (x.CompanyReference.IndustryTemplateCode!="I") #보험업 제외
                                    and x.CompanyProfile.HeadquarterCountry  == 'USA'
                                    and (float(x.EarningReports.BasicAverageShares.Value) > 0)
                                    and (float(x.FinancialStatements.BalanceSheet.CurrentLiabilities.Value) > 0)
                                    and (float(x.MarketCap) > 0)
                                    and x.OperationRatios.ROIC.ThreeMonths > 0.01 #수익성
                                    and x.OperationRatios.DebtToAssets.Value < 1.0 #재무 건전성
                                    and x.ValuationRatios.FCFRatio > 0.01 #영업 정상?
                                    and x.MarketCap/x.CompanyProfile.EnterpriseValue < 5 #고평가 제외 
                             
            ]            
            
            for x in fine:
                gross_profits = [ 
                    +4*x.FinancialStatements.IncomeStatement.NetIncome.NineMonths, 
                    +4*x.FinancialStatements.IncomeStatement.NetIncome.SixMonths,
                    +4*x.FinancialStatements.IncomeStatement.NetIncome.ThreeMonths,
                ]
                
                market_cap = int(x.MarketCap)
                averaged_gross_profit = sum(gross_profits)
                averaged_net_worth = x.FinancialStatements.BalanceSheet.TotalEquity.Value
                # Ensure market_cap is not zero to avoid division by zero
                if market_cap > 0:
                    #DIY valuation formula 10:0.6 = 50:3 ▷ 20:3
                    ValuationRatio = (3 * averaged_gross_profit + 1 * averaged_net_worth)/ market_cap #+ (np.random.random()-0.5)/3
                    x.ValuationRatio = ValuationRatio 
                    
                else:
                    x.ValuationRatio = -100

            fine = [x for x in fine if x.ValuationRatio > 0]
            fine = sorted(fine, key=lambda x: x.ValuationRatio, reverse=True)[:5]

            self.filtered_fine = [x.Symbol for x in fine]
            return self.filtered_fine
        else:
            return []
    
    def rebalance(self):
        # self.should_rebalance = True
        # return 

        if (self.Time.month - self.last_month)%12 == 1 and self.Time.day > 10:
            self.should_rebalance = True
            self.last_month = self.Time.month
        else:
            self.should_rebalance = False

    def W(self,k_th,length,weight):
        n,a,k = length,weight,k_th
        return 1/n - a*(k-(n+1)/2)*(2/(n*(n+1)))

    def OnData(self, data):
        if not self.should_rebalance: 
            return 

        stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
        for symbol in stocks_invested:
            if symbol not in self.filtered_fine:
                self.Liquidate(symbol)
                
        if self.filtered_fine:
            #weights = [self.W(k,len(self.filtered_fine),0) for k in range(1,len(self.filtered_fine)+1)]
            # weights = [round(w,12) for w in weights]
            self.Debug(f'{[x.Value for x in self.filtered_fine]}')
            for symbol in self.filtered_fine:
                self.SetHoldings(symbol, 1/len(self.filtered_fine))
        else:
            pass
        self.should_rebalance = False