Overall Statistics
Total Orders
9
Average Win
73.66%
Average Loss
-5.64%
Compounding Annual Return
31.221%
Drawdown
56.900%
Expectancy
9.545
Start Equity
100000
End Equity
511290.73
Net Profit
411.291%
Sharpe Ratio
0.706
Sortino Ratio
0.816
Probabilistic Sharpe Ratio
18.567%
Loss Rate
25%
Win Rate
75%
Profit-Loss Ratio
13.06
Alpha
0.162
Beta
1.124
Annual Standard Deviation
0.366
Annual Variance
0.134
Information Ratio
0.554
Tracking Error
0.312
Treynor Ratio
0.23
Total Fees
$250.33
Estimated Strategy Capacity
$61000.00
Lowest Capacity Asset
BIGT Y7JDXRV74ZMT
Portfolio Turnover
0.41%
Drawdown Recovery
1383
Avg. Lost% Per Losser
-5.61%
Avg. Win% Per Winner
73.69%
Max Win%
147.22%
Max Loss%
-5.61%
*Profit Ratio
23.93
'''
Usage: 
    def Initialize(self):
        self.log = Log(self)

    # code xxxxxx
    self.log.log("---->1")        
        
'''


from AlgorithmImports import *

import time

class Log():
    def __init__(self, algo):
        self.timer = round(time.time() * 1000)
        self.algo = algo
        self.maxLine = 400
        self.count = 0
        self.debug(f"Live mode={self.algo.live_mode}.....Log Initialized")

    def log(self, message):
        self.algo.Log(f"[LOG] {message}")

    def info(self, message):
        now = round(time.time() * 1000)
        timer = (now - self.timer) / 1000
        self.timer = now
        if (self.algo.Time <= self.algo.Time.replace(hour=9, minute=35)):
            self.algo.Log(f"[INFO] {message}")

    def debug(self, message):
        if (self.count < self.maxLine or self.algo.live_mode):
            self.algo.Log(f"[DEUBG] {message}")
            self.count += 1

    def live(self, message):
        if self.algo.live_mode:
            self.algo.Log(f"[DEUBG] {message}")
# ================================================================================
# DISCLAIMER
# ================================================================================
# This code is provided free of charge for EDUCATIONAL PURPOSES ONLY. Users are 
# granted permission to study, modify, and redistribute this script for non-
# commercial learning and research.
#
# The author and developers of this code assume NO LIABILITY for any financial 
# losses, trading damages, or missed opportunities resulting from the use or 
# misuse of this algorithm. Quantitative trading involves significant risk, 
# and past performance is not indicative of future results
#
# This code is provided "AS IS" without any warranties. The author does not 
# provide technical support, bug fixes, or maintenance for this script. 
# Users are responsible for their own due diligence and backtesting before 
# considering any live deployment

from AlgorithmImports import *
from datetime import timedelta, datetime
from security_initializer import CustomSecurityInitializer
from utils import Utils
from log import Log

'''
Scope: All in best performance ETF except cryto
'''
# lean project-create --language python "ALLIN03"
# lean cloud backtest "ALLIN03" --push --open
class ALLIN03(QCAlgorithm):
    def initialize(self):
        # Set backtest range and initial capital
        self.set_start_date(2020, 1, 1)
        self.set_end_date(2025, 12, 31)
        self.init_cash = 100000
        self.set_cash(self.init_cash)  # Set Strategy Cash

        # Universe settings: Resolution.HOUR is used for trading, though selection is annual
        self.universe_settings.resolution = Resolution.HOUR
        
        # Define the benchmark and the base ETF for constituent selection (SPY)
        self._symbol = self.add_equity("SPY", Resolution.HOUR).Symbol


        # Add a universe that tracks the constituents of the SPY ETF
        self.add_universe_selection(ETFTopPickUniverseSelectionModel(self))

        # Alpha Model: Emits a daily "UP" insight for all symbols currently in the universe
        self.add_alpha(NonBenchmarkConstantAlphaModel(self, InsightType.PRICE, InsightDirection.UP, timedelta(days=1)))

        # Security Initializer: Seeds new securities with historical price data to prevent trade delays
        self.set_security_initializer(CustomSecurityInitializer(InteractiveBrokersBrokerageModel(AccountType.CASH), FuncSecuritySeeder(self.get_last_known_prices)))
        
        # Use Interactive Brokers brokerage model with a Cash account (no margin)
        self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.CASH)
        
        # Portfolio settings: Fully invest, rebalance when insights change, and set margin buffer
        self.settings.free_portfolio_value_percentage = 0.00
        self.settings.rebalance_portfolio_on_insight_changes = True
        self.settings.minimum_order_margin_portfolio_percentage = 0.5

        # Construction Model: Distributes portfolio value equally among all symbols with active insights
        self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel())

        # Dictionary to cache the selected symbols per year to avoid redundant heavy calculations
        self.picked = {}
        self.log = Log(self)
        self.utils = Utils(self, self._symbol)

        self.schedule.on(self.date_rules.every_day(), self.time_rules.before_market_close(self._symbol, 0), self.utils.plot)

    
    def on_end_of_algorithm(self):
        self.utils.stats()

class ETFTopPickUniverseSelectionModel(FundamentalUniverseSelectionModel):
    def __init__(self, algo):
        self.whitelist_etfs = [
            'DAPP','HIBS','AAAU','AADR','AAXJ','ACES','ACIO','ACSI','ACWI','ACWV','ACWX','ADME','AESR','AFK','AFLG','AFMC','AFSM','AGG','AGGY','AGZD','AIA','AIEQ','AIRR','ALFA','ALTS','ALTY','AMOM','AMU','AMUB','ANGL','AOA','AOK','AOM','AOR','ARCM','ARGT','ARKQ','ARKW','ASEA','ASET','ASHR','ASHS','ATMP','AUSF','AVDE','AVDV','AVEM','AVUS','AVUV','BAB','BAPR','BAR','BATT','BAUG','BBAX','BBC','BBCA','BBEU','BBH','BBIN','BBJP','BBP','BBRE','BCD','BCI','BDCZ','BDEC','BFIT','BFOR','BIBL','BIL','BIV','BIZD','BJAN','BJK','BJUL','BJUN','BKF','BKLN','BLCN','BLES','BLOK','BNO','BNOV','BOCT','BOSS','BOTZ','BOUT','BRF','BSDE','BSEP','BTAL','BTEC','BUG','BUL','BUY','BWX','BWZ','CALF','CARZ','CATH','CCOR','CDC','CDL','CEFS','CEMB','CEW','CEZ','CFA','CFO','CGW','CHIQ','CIBR','CID','CIL','CLIX','CLOU','CMBS','CMDY','CN','CNBS','CNRG','CNXT','CNYA','COM','COMB','COMT','COPX','CORN','CORP','COW','COWZ','CPER','CPI','CQQQ','CRAK','CRBN','CSA','CSB','CSD','CSM','CUT','CVY','CWB','CWI','CWS','CXSE','CYB','CZA','DALI','DAUG','DAX','DBA','DBAW','DBB','DBC','DBE','DBEF','DBEM','DBEU','DBEZ','DBJP','DBMF','DBO','DBP','DBV','DDIV','DDLS','DDWM','DEEF','DEF','DEM','DES','DEUS','DEW','DFE','DFJ','DFNL','DGRE','DGRO','DGRS','DGRW','DGS','DGT','DGZ','DHS','DIA','DIAL','DIM','DINT','DIVB','DIVO','DJCI','DJD','DJP','DLN','DLS','DMRE','DNL','DNOV','DOG','DOL','DON','DOO','DRIP','DRIV','DRSK','DSI','DSTL','DTD','DTEC','DTH','DURA','DUSA','DVLU','DVOL','DVP','DVY','DVYA','DVYE','DWAS','DWAW','DWM','DWMF','DWSH','DWUS','DWX','DXJ','DYNF','EAGG','EASG','EBIZ','ECNS','ECON','ECOW','EDEN','EDIV','EDOG','EDOW','EDV','EELV','EEM','EEMA','EEMO','EEMS','EEMV','EEMX','EES','EET','EEV','EFA','EFAD','EFAS','EFAV','EFAX','EFG','EFNL','EFU','EFV','EFZ','EIDO','EINC','EIRL','EIS','EJUL','ELD','EMB','EMDV','EMGF','GFOF','EMHY','EMIF','EMLC','EMLP','EMMF','EMNT','EMQQ','EMTL','EMTY','EMXC','ENFR','ENOR','ENZL','EPHE','EPI','EPOL','EPP','EPRF','EPS','EPU','EQAL','EQL','EQWL','ERM','ESGD','ESGE','ESGU','ESGV','ESML','ESPO','EUDG','EUDV','EUFN','EUM','EUSA','SATO','EVX','EWA','EWC','EWD','EWG','EWH','EWI','EWJ','EWJV','EWK','EWL','EWM','EWN','EWO','EWP','EWQ','EWS','EWT','EWU','EWUS','EWV','EWW','EWX','EWY','EWZ','EWZS','EXI','EXT','EYLD','EZA','EZM','EZU','FAAR','FAB','FAD','FALN','FAN','FAUG','FBT','FCA','FCAL','FCEF','FCG','FCOM','FCPI','FCTR','FCVT','FDD','FDEM','FDEV','FDHY','FDIS','FDL','FDLO','FDM','FDMO','FDN','FDNI','FDRR','FDT','FDVV','FEM','FEMB','FEMS','FENY','FEP','FEUZ','FEX','FEZ','FFIU','FFR','FFTY','FGD','FGM','FHLC','FID','FIDI','FIDU','FILL','FINX','FISR','FITE','FIVA','FIW','FIXD','FJP','FKU','FLAU','FLAX','FLBL','FLBR','FLCA','FLCH','FLCO','FLDR','FLEE','FLGB','FLGR','FLHY','FLIA','FLIN','FLJH','FLJP','FLKR','FLLA','FLMB','FLMI','FLMX','FLN','FLQL','FLQM','FLQS','FLRN','FLRT','FLSA','FLSP','FLSW','FLTB','FLTR','FLTW','FM','FMAT','FMB','FMF','FMHI','FMK','FNCL','FNDA','FNDB','FNDC','FNDE','FNDF','FNDX','FNGS','FNK','FNOV','FNX','FNY','FPA','FPE','FPEI','FPX','FPXE','FPXI','FQAL','FRDM','FREL','FRI','FSMB','FSMD','FSTA','FSZ','FTA','FTAG','FTC','FTCS','FTEC','FTGC','FTHI','FTLS','FTRI','FTSD','FTSL','FTSM','FTXG','FTXH','FTXL','FTXN','FTXO','FUD','FUTY','FV','FVAL','FVC','FVD','FVL','FXA','FXB','FXC','FXD','FXE','FXF','FXG','FXH','FXI','FXL','FXN','FXO','FXR','FXU','FXY','FXZ','FYC','FYLD','FYT','FYX','GAA','GAL','GAMR','GBDV','GBF','GBIL','GCC','GDMA','GDX','GDXJ','GEM','GFIN','GHYB','GHYG','GIGB','GII','GLD','GLDM','GLTR','GMF','GMOM','GNOM','GNR','GOAU','GOEX','GQRE','GREK','GRID','GRN','GSEU','GSEW','GSG','GSIE','GSJY','GSLC','GSP','GSSC','GTIP','GUNR','GURU','GVAL','GVI','GVIP','GWX','GXC','GYLD','HACK','HAIL','HAP','HAUZ','HAWX','HDEF','HDG','HDMV','HDV','HEDJ','HEEM','HEFA','HERD','HERO','HEWJ','HEZU','HFXI','HIBL','HIPS','HLAL','HMOP','HNDL','HOMZ','HSCZ','HSPX','HTAB','HTEC','HTRB','HTUS','HUSV','HYD','HYDB','HYDW','HYEM','HYG','HYGH','HYGV','HYHG','HYLB','HYLD','HYLS','HYMB','HYUP','HYXU','HYZD','IAI','IAK','IAT','IAU','IBB','IBDQ','IBDR','IBDS','IBDT','IBDU','IBHE','IBMN','IBMO','IBMP','IBMQ','IBND','IBUY','ICF','ICLN','ICOW','IDEV','IDHQ','IDIV','IDLV','IDMO','IDNA','IDOG','IDRV','IDU','IDV','IDX','IEDI','IEF','IEFA','IEI','IEMG','IEO','IETC','IEUR','IEUS','IEV','IEZ','IFGL','IFRA','IFV','IG','IGBH','IGEB','IGF','IGHG','IGIB','IGLB','IGM','IGOV','IGSB','IGV','IHAK','IHDG','IHE','IHF','IHI','IIGD','IJH','IJJ','IJK','IJR','IJS','IJT','IJUL','ILF','ILTB','IMOM','IMTB','IMTM','INCO','INDA','INDL','INDS','INDY','INFR','INKM','INTF','IOO','IPAC','IPAY','IPKW','IPO','IPOS','IQDF','IQDG','IQDY','IQLT','IQSI','IQSU','ISCF','ISHG','ISMD','ISRA','ISTB','ITA','ITB','ITEQ','ITM','ITOT','IUS','IUSG','IUSV','IVAL','IVE','IVLU','IVOG','IVOL','IVOO','IVOV','IVV','IVW','IWB','IWC','IWD','IWF','IWL','IWM','IWN','IWO','IWP','IWR','IWS','IWV','IWX','IWY','IXC','IXG','IXJ','IXN','IXP','IXUS','IYC','IYE','IYF','IYG','IYH','IYJ','IYK','IYM','IYR','IYT','IYW','IYY','IYZ','IZRL','JETS','JHEM','JHMD','JHML','JHMM','JHSC','JMBS','JMIN','JMOM','JMUB','JOYY','JPEM','JPIN','JPMB','JPME','JPN','JPSE','JPUS','JPXN','JQUA','JSMD','JSML','JUST','JVAL','JXI','KARS','KBA','KBE','KBWB','KBWD','KBWP','KBWR','KBWY','KCE','KEMQ','KEMX','KGRN','KIE','KNG','KOCT','KOIN','KOMP','KORP','KRE','KRMA','KSA','KURE','KWEB','KXI','LDSF','LDUR','LEGR','LEMB','LFEQ','LGH','LGLV','LGOV','LIT','LKOR','LMBS','LOUP','LQD','LQDH','LQDI','LRGE','LRGF','LSAF','LTPZ','LVHD','LVHI','MAGA','MAGS','MBB','MBSD','MCHI','MDIV','MDY','MDYG','MDYV','MEAR','MFDX','MFEM','MFUS','MGC','MGK','MGV','MILN','MINT','MJ','MLN','MLPB','MLPX','MMIN','MMIT','MMTM','MNA','MOAT','MOM','MOO','MORT','MOTI','MOTO','MRGR','MUNI','MUST','MVIN','MXI','MYY','MZZ','NACP','NANR','NERD','NETL','NFLT','NFRA','NFTY','NIB','NLR','NOCT','NTSX','NUAG','NUDM','NUEM','NUHY','NULC','NULG','NULV','NUMG','NUMV','NURE','NUSA','NUSC','NXTG','OBOR','OCIO','OEF','OGIG','OIH','OIL','OILK','OLD','OMFL','OMFS','ONEO','ONEQ','ONEV','ONEY','ONLN','ORG','OSCV','OUNZ','OUSA','OUSM','OVF','OVL','OVM','OVS','PALL','PAPR','PAUG','PAWZ','PBD','PBE','PBJ','PBP','PBTP','PBUS','PBW','PCEF','PCY','PDBC','PDEC','PDN','PDP','PEJ','PEK','PEX','PEXL','PEY','PEZ','PFF','PFFD','PFFR','PFI','PFIG','PFLD','PFM','PFXF','PGF','PGHY','PGJ','PGM','PGX','PHB','PHDG','PHO','PICB','PICK','PID','PIE','PIN','PIO','PIZ','PJAN','PJP','PJUL','PJUN','PKB','PKW','PLW','PNOV','PNQI','POCT','PPA','PPH','PPLN','PPLT','PPTY','PREF','PRF','PRFZ','PRN','PRNT','PSCC','PSCD','PSCF','PSCH','PSCI','PSCT','PSCU','PSEP','PSET','PSI','PSK','PSL','PSM','PSP','PSQ','PSR','PTEU','PTF','PTH','PTIN','PTLC','PTMC','PTNQ','PUI','PVAL','PVI','PWB','PWC','PWS','PWV','PWZ','PXE','PXF','PXH','PXI','PYZ','PZA','PZT','QABA','QAI','QARP','QAT','QCLN','QDEF','QDF','QDIV','QED','QEFA','QEMM','QGRO','QINT','QLC','QLTA','QLV','QLVD','QLVE','QMOM','QQEW','QQH','QQQ','QQQE','QQXT','QRFT','QTEC','QTUM','QUAL','QUS','QVAL','QWLD','QYLD','RAAX','RAFE','RALS','RDVY','RECS','REET','REGL','REK','REMX','RESI','REVS','REZ','RFCI','RFDA','RFDI','RFEM','RFEU','RFFC','RFG','RFV','RIGS','RINF','RING','RISE','RLY','RNEM','ROAM','ROBO','ROBT','RODM','ROKT','ROSC','ROUS','RPAR','RPG','RPV','RSP','RSX','RTH','RVNU','RWJ','RWK','RWL','RWM','RWO','RWR','RWX','RXI','RYLD','RYU','RZG','RZV','SBB','SBIO','SBM','SCHA','SCHB','SCHC','SCHD','SCHE','SCHF','SCHG','SCHH','SCHI','SCHJ','SCHK','SCHM','SCHO','SCHP','SCHQ','SCHR','SCHV','SCHX','SCJ','SCZ','SDCI','SDEM','SDG','SDIV','SDOG','SDP','SDVY','SDY','SECT','SEF','SEIX','SFY','SFYF','SFYX','SGDJ','SGDM','SGG','SGOL','SH','SHAG','SHE','SHM','SHY','SHYD','SHYL','SIJ','SIL','SILJ','SIMS','SIVR','SIZE','SJB','SJNK','SKF','SKOR','SKYY','SLQD','SLV','SLVP','SLX','SLYG','SLYV','SMB','SMCP','SMDV','SMH','SMIN','SMLF','SMLV','SMMD','SMMU','SMMV','SMOG','SNPE','SNSR','SOCL','SOXX','SOYB','SPAB','SPBO','SPDV','SPDW','SPEM','SPEU','SPFF','SPGM','SPGP','SPHB','SPHD','SPHQ','SPHY','SPIP','SPLB','SPLG','SPLV','SPMB','SPMD','SPMO','SPMV','SPSB','SPSK','SPSM','SPTI','SPTL','SPTM','SPTS','SPUS','SPVM','SPVU','SPXE','SPXN','SPXT','SPXV','SPY','SPYD','SPYG','SPYV','SPYX','SQLV','SRET','SRLN','SRVR','SSPY','STOT','STPZ','SUB','SUSA','SUSB','SUSC','SUSL','SWAN','SYV','SZNE','TAGS','TAIL','TAN','TAO','TAXF','TBF','TBLU','TBX','TDIV','TDTF','TDTT','TDV','TFI','TFLO','THD','TILT','TIPX','TIPZ','TLH','TLT','TLTD','TLTE','TMDV','TMFC','TOK','TOKE','TOLZ','TOTL','TPHD','TPIF','TPLC','TPSC','TPYP','TRTY','UAE','UBOT','UCIB','UCON','UDN','UEVM','UFO','UGA','UITB','UIVM','ULVM','UNG','URA','URNM','URTH','USCI','USFR','USIG','USL','USMC','USMF','USMV','USO','USOI','USRT','USSG','USTB','USVM','UTES','UTRN','UUP','VALQ','VAMO','VAW','VB','VBK','VBR','VCIT','VCR','VDC','VDE','VEA','VEGA','VEGI','VEGN','VEU','VFH','VFMF','VFMO','VFMV','VFQY','VFVA','VGIT','VGK','VGLT','VGSH','VGT','VHT','VIDI','VIG','VIGI','VIOG','VIOO','VIOV','VIS','VIXM','VIXY','VLU','VMBS','VNLA','VNM','VNQ','VNQI','VO','VOE','VONE','VONG','VONV','VOO','VOOG','VOOV','VOT','VOX','VPC','VPL','VPU','VRAI','VRIG','VRP','VSDA','VSGX','VSL','VSMV','VSS','VT','VTC','VTHR','VTI','VTIP','VTV','VTWG','VTWO','VTWV','VUG','VUSE','VV','VWO','VWOB','VXF','VXUS','VXZ','VYM','VYMI','WBIY','WCLD','WDIV','WIL','WIP','WLDR','WOMN','WOOD','WPS','WTMF','XAR','XBI','XCEM','XDIV','XHB','XHE','XHS','XITK','XLB','XLC','XLE','XLF','XLG','XLI','XLK','XLP','XLRE','XLSR','XLU','XLV','XLY','XME','XMHQ','XMLV','XMMO','XMPT','XMVM','XNTK','XPH','XRLV','XRT','XSD','XSHD','XSHQ','XSLV','XSMO','XSOE','XSVM','XSW','XT','XTL','XTN','YLD','YOLO','YXI','YYY','ZIG','ZROZ','EJAN','IJAN','KJAN','NJAN','RDOG','JDST','GXG','PLTM','IBTE','AFIF','SMDD','DUST','XES','AMZA','MLPA','GLIN','GLDI','AMLP','JPIB','HYXF','TDSA','BTEK','TGIF','LIV','DOCT','INDF','VPN','MOON','DEFN','FEVR','BNE','QQD','QPT','OVT','EPRE','PVAL','CRUZ','UNL','SMI','CYA','HHH','INNO','WTAI','BKES','RJA','IUSA','CWC','CSH','WEAT','DAM','PSCE','NBCC','CANE','WEED','UAV','ORFN','SESG','EMCA','SLVO','INC','TGN','INTL','DIP','PBL','ENAV','BIGT','CETF','TUNE','PXJ','KEM','ALUM','INOV','SOF','CHAI','PSH','ISPY','RTRE','AUGM','LIAB','GLBL','TRSY','INFO','SCY','EDGE','EGLE','FLXN','XDIV','BITK','ESK',
            'EDGI','EDGH','EDGU','BDIV','SAWS','LODI','TRFM','FWD','ILOW','TAFM','TAFL','LRGC','LOWV','BCIM','ABEQ','XVOL','ADPV','AGOX','RULE','JSTC','SCAP','GK','PSIL','MSOS','QPX','ACT','LEXI','ATFV','ALAI','CNEQ','APLU','AINP','ABCS','CCNR','GRI','SMTH','DIVD','FUSI','MGNR','AHLT','MUSI','CATF','FDG','FLV','ESGA','ESGY','MID','ISWN','HCOW','COWS','QDVO','IDVO','SMAP','THNR','CARY','NDOW','AOTG','VSLU','UPSD','ARKG','ARKK','ARMR','ROE','PPI','GQQQ','AZNH','ASPY','RORO','USAF','AVGV','AVGE','AVNV','AVNM','AVEE','AVES','AVXC','AVIV','AVDS','AVMA','AVRE','AVSD','AVSE','AVSU','AVLV','AVLC','AVMC','AVMV','AVSC','CHGX','FOMO','NXTE','KNO','RINC','BGDV','BGIG','SMIG','SCDV','MGMT','BCIL','PCMM','BSR','LCTU','LCTD','TFPN','BKCI','BKDV','BKEM','BKGI','BKIV','BKIE','BKLC','BKMC','BKSE','BINV','BUSA','BSMC','DVAL','BWET','BWTG','BDGS','TBFC','BAMA','BAMD','BAMG','BAMO','BAMV','BAFE','BRNY','SMRI','CVIE','CPNQ','CPSN','CVRT','CANQ','CPNS','CPNJ','CPSO','CPST','CPSY','CPSJ','CPSM','CPSA','CPSL','SROI','CPRJ','CPRO','CVLC','CVMC','CVSE','CAMX','MFUT','BLDG','LYLD','CGCV','CGBL','CGUS','CGCP','CGDV','CGGE','CGGO','CGGR','CGIC','CGIE','CGXU','CGHM','CGMU','CGNG','CCSB','PUTD','CPRY','CPSD','CBLS','CBSE','ROPE','ESGW','ESGS','CGV','CAML','CSMD','CGRO','FUNL','CPAI','CCSO','SPC','DIVP','DWLD','SSUS','SSFI','XMAG','QQQT','QQQY','BIGY','USOY','IWMY','SPYT','HF','DFGR','DFLV','DFVX','DFCA','DEXC','DFSE','DFAE','DEHP','DFEM','DFEV','DFGP','DGCB','DFIP','DFSI','DFAI','DFIC','DIHP','DFIS','DISV','DFIV','DXIV','DFNM','DCOR','DFAC','DFAU','DFUS','DUHP','DFUV','DFAR','DFAS','DFSV','DFSU','DFAT','DXUV','DFAX','DFAW','KNOW','QQQD','HIPR','WFH','DSTX','DSMC','DCMT','DFVE','DMX','DRAI','GCAL','BBLU','BSVO','EAGL','EVLN','EVIM','EVTR','EMPB','LUXE','REC','CHRG','TERM','WUGI','ESN','SIXH','SIXL','SIXA','SXQG','SIXS','TDSB','RITA','AWAY','AMNA','ELCV','TDSC','CLSM','LFSC','TACK','FLCG','FLCV','FSCC','FTRB','FDV','FBCG','FBCV','FRNW','FCLD','FDHT','FBOT','FDFF','FMED','FDTX','FDCF','FDIF','FDRV','FEMR','FELC','FELG','FELV','FMDE','FESM','FEAC','FENI','FFDI','FFGX','FFLV','FHEQ','FSEC','FLDB','FMET','FSST','FLRG','FDWM','FIRI','FIRS','FEGE','FEOE','AGQI','FDIV','CAAA','FLM','FNI','FDTS','XPND','FTGS','HSMV','MISL','ILDR','MMLG','MMSC','FTXR','FMNY','EMOT','DEED','BNGE','BRIF','PRAY','FEDM','FEUS','FEIG','ESGG','ESG','FMCE','FMCX','FMQQ','GRNY','FORH','KONG','FPAG','YLDE','BUYZ','XDAT','FFOG','HELX','INCM','IQM','MCSE','MBOX','FLCE','FARX','FOPC','FINT','FGSM','FTIF','RND','FAI','EIPX','XIJN','XIMR','QCOC','QMAG','QMMY','QMNV','FTCE','QCJL','QCAP','RJMG','SCIO','XMAY','UXOC','DOGG','XFEB','BGLD','YJUN','YDEC','QSPT','RDVI','SDVD','TDVI','FSEP','FDEC','DFEB','DSEP','DDEC','XJUN','XSEP','GMAY','SMAY','NOVM','RSJN','RSSE','FJUN','XISE','XIDE','FFEB','FMAY','FMAR','DMAY','DJUN','DMAR','XMAR','XAUG','XNOV','GMAR','GJUN','GSEP','GNOV','GDEC','SFEB','SAUG','SNOV','YSEP','YMAR','WCMI','FHDG','FDND','TSEP','MARM','RSDE','KNOW','XRLX','XCOR','XFLX','GCAD','GABF','GMMA','EMM','IPAV','GENM','GENT','EMC','GPIX','ONOF','KROP','BRAZ','SHLD','DMAT','DYLG','DJIA','EDOC','HYDR','NDIA','NGE','QCLR','QYLG','QRMI','QTR','RYLG','RSSL','XCLR','XYLG','XRMI','XTR','CEFA','RAYS','FLOW','PAVE','WNDY','QLTI','GMOI','GMOV','YALL','GSIG','GCOR','GDOC','GINN','GSEE','GSID','GUSA','GSC','GGUS','GHTA','GVLU','GSPY','DARP','GLOV','GSC','GTEK','GPIQ','RCD','RHS','SEA','GFGF','ZAP','EBIT','HGER','GDIV','RENW','HAPY','HAPI','OSEA','WINN','MAPP','HDUS','HFGO','QUVU','SMCO','BCDF','NVIR','INFL','MEDX','HSBH','BDVG','OWNS','NDEC','RILA','QIDX','ICAP','SCAP','BALT','QTJL','XBJL','XTJL','KDEC','KNOV','ZDEK','IDEC','ZAUG','NAUG','IAUG','IJUN','IMAY','BSTP','JAJL','SFLR','NJUN','NNOV','IGTR','QTJA','QTOC','QHDG','IMAR','ISEP','QFLR','PSTP','BFEB','BMAR','BMAY','PFEB','PMAY','XUSP','ZALT','XBJA','XTJA','EALT','XDOC','XBOC','XBAP','XTOC','XDSQ','RFLR','INQQ','APOC','PTL','GLRY','RISN','KAUG','HAO','DGL','PMR','IEMD','QQMG','QQJG','RSPE','GBLD','EFAA','KLMT','KLMN','QOWZ','QQQM','IBBQ','QQQS','QQQJ','SOXQ','PBEE','QQA','IVRA','RSPA','QVML','QVMM','QVMS','PSCM','DIVG','QBIG','KJUN','ZNOV','CROP','IRET','CORO','IVVM','XJH','BRLN','BALI','ETEC','USCL','ICOP','IBAT','CSHP','ERET','EMXF','DMXF','USXF','EVUS','EAOK','EAOM','EAOR','EAOA','EGUS','LDEM','ESMV','XVV','IAUM','BMED','IBTM','AGRH','ISVL','BIDD','IGRO','BEMB','SMAX','IVVB','ITDD','DMAX','MAXJ','IRTR','ITDJ','ITDB','ITDC','ITDI','ITDE','ITDF','ITDG','ILIT','BGRO','BLCR','BLCV','IYLD','REM','ECH','TCHI','AQLT','KWT','MTUM','QNXT','QTOP','IWMW','IVVW','NEAR','ITDH','TMET','TEK','TOPT','BRTR','BELT','INRO','MADE','TECB','BPAY','XJR','EVLU','PABU','BAI','JRE','JGRW','JDVI','JSI','JIII','JHMB','JHMU','JHPI','JHDV','BBIB','BBMC','JIVE','JEPQ','JMEE','JMSI','JCHI','JGRO','JAVA','JPSV','BBEM','BBUS','BBSC','JCTR','JDIV','JPEF','JEPI','LCDS','JGLO','HELO','JPIE','JIRE','JIG','JPLD','JPRE','CIRC','JDIV','JTEK','KHPI','DVDN','EQTY','KLIP','AGIX','KCCA','KEUA','KLXY','KSPY','KMLM','KSTR','KVLE','KRBN','KQQQ','AAPY','TGLR','LGHT','SQEW','LCR','LGRO','LQAI','RMIF','LIFT','LCLG','LSVD','MSMR','EMEQ','LRGG','BILD','CVRD','DIVL','BUYW','FDIV','ADVE','MINV','MCH','MCHS','MEMX','MEM','EMSF','JPAN','MKOR','ASIA','MFSG','MFSI','MFSV','MVPA','MVPL','MFUL','CNAV','MAMB','MBCC','MDPL','MPRO','MSSS','MVFG','MVFD','MDLV','TMFE','TMFG','TMFM','TMFX','MSLC','MSSM','MUSQ','NSI','GQI','LSGR','NBCM','NBDS','CSHI','QQQI','IWMI','SPYI','EGGY','NBCR','NBGX','NBOS','NBSM','NBTR','GIAX','KOOL','QTPI','NCLO','NDVG','NUDV','NUGO','NSCR','IQSM','ROOF','IQRA','MMCA','SECR','IWLG','OAKM','OGSP','DUKZ','DUKQ','DUKX','FFND','OAEM','OAIM','OALC','OASC','DIVZ','ESGL','CRIT','OPTZ','OCFS','ODDS','BULD','TRFK','EAFG','SHPP','ALTL','PALC','PAMC','QDPL','QQQG','FOWF','PSCQ','PSCW','PSCX','PSCJ','PSFM','PSFD','PSFJ','PSFO','PSFF','PSMO','PSMR','PSMD','PSMJ','COWG','QSIX','PAPI','PHEQ','PRCS','PRVS','PSTR','PAAA','PFRL','PJBF','PJFG','PJFV','PBFR','PMIO','PQJA','PBDE','MAYP','JUNP','PBSE','PBOC','PBNV','PMJA','PBJA','PBJN','FCUS','PRAE','PSQO','ARP','PCEM','PCCE','PCGG','PCIG','PPEM','BCHP','PIEQ','BYRE','PQDI','PY','DAT','VERS','ANEW','TINY','IQQQ','QQQA','ITWO','RB','ION','CTEX','ZBIO','PBDC','PEMX','PGRO','PPIE','PFUT','PLDR','QVOY','NVQ','HDIV','NIXT','COAL','NUKZ','RSEE','RTAI','RAYC','RAYJ','RPHS','MOOD','HAUS','RSBA','RSSY','RSBY','AIPI','FEPI','RHRX','FLDZ','THNQ','RMCA','RGEF','RMOP','RSMC','XPAY','XDTE','CHAT','OZEM','QDTE','RDTE','LUXX','BETZ','RUNN','SAEF','SCHY','SMBS','SELV','SEIM','SEIQ','SEIV','SEEM','SEIE','SEIS','EUAD','DYTA','GINX','USDX','SGLC','SHOC','HARD','QQC','GAEM','YGLD','PINK','HEQT','QIS','NMB','SURI','IOPP','SPYC','SPD','SPUC','SPQ','SVOL','ADIV','DIVS','FCTE','DIVY','SHDG','SOVF','SPTE','SPWO','SPRE','SPCX','ITAN','DTAN','CERY','DECO','HECO','TEKX','LQIG','SPDG','EFIV','XCNY','ESIX','ZJPN','SPRX','COPP','SETM','COPJ','URNJ','LITP','NIKL','SRHR','NEWZ','LCG','TUG','TUGN','SAGP','SAMM','SAMT','HECO','STXD','STXG','STXV','STRV','STXE','STXI','STXM','FTWO','STXK','DRLL','HEGD','SHUS','TCAF','TOUS','TCHP','TDVG','TEQI','TFLR','TGRW','TAXE','TTEQ','TOTR','TSPA','TGRT','TVAL','TMSL','FDAT','TSPY','ACLO','GRW','FLXR','SLNZ','VOTE','SUPP','RSHO','VOLT','HRTS','TILL','TXS','BEEX','NITE','IDVZ','SHEH','AIRL','CLOD','COPA','SPAM','FINE','WISE','GSIB','AUMI','CZAR','NATO','HWAY','USRD','SMCF','THLV','THIR','TSME','TPHE','TPLE','THY','MRSK','HEAT','DVND','TSEC','SIO','LCF','TOGA','RVER','RNWZ','QBER','AUGZ','DECZ','SEPZ','FEBZ','JUNZ','LRNZ','COPY','HFND','NANC','WAR','UDI','GLDX','UMI','ZSB','CLOB','PIT','SMHX','GMET','MIG','SMOT','MGRO','DESK','IBOT','VGSR','CIZ','CEY','VFLO','SHLD','MODL','VEMY','ASMF','KMID','PCLO','JOET','AIS','GFLW','SFLO','UMMA','WLTG','DVQQ','DVSP','WINC','MDST','LBO','WDNA','WCBR','GDMN','GDE','DXGE','INDH','DHDG','JAMF','GTR','XC','QSML','QGRW','WGLD','WEEI','USIN','USCA','XAIX','PSWD','KOKU','NRES','SNPG','SMLE','CHPS','CRTC','SMCY','ZECP','GROZ','SMIZ','ZHDG'
        ]
        
        self.algo = algo

    def select(self, algo: QCAlgorithm, fundamental: List[Fundamental]) -> List[Symbol]:
        if self.algo.time.year not in self.algo.picked:
            # Filter out constituents without weight and sort by weight (optional step for cleanliness)
            selected =  [x for x in fundamental if not x.has_fundamental_data and x.price > 10 and x.dollar_volume > 500_000 and x.market_cap == 0 and x.symbol.value in self.whitelist_etfs]

            # Request daily historical data for the entire previous year for all ~500 SPY constituents
            history = self.algo.history([s.symbol for s in selected], 
                                   datetime(self.algo.time.year-2, 12, 1), 
                                   datetime(self.algo.time.year-1, 12, 31), 
                                   Resolution.DAILY)

            # Reset index to make 'symbol' and 'time' accessible columns in the DataFrame
            df = history.reset_index()

            # Extract the year from the timestamp
            df['year'] = df['time'].dt.year

            # 1. Group by symbol and year to find ONLY the 'last' close price of each year
            # We don't need the open price anymore for this specific calculation
            yearly_df = df.groupby(['symbol', 'year'])['close'].last().reset_index()

            # 2. Sort by symbol and year to ensure 2018 comes before 2019
            yearly_df = yearly_df.sort_values(['symbol', 'year'])

            # 3. Calculate the percentage price change: (Current Year Close - Previous Year Close) / Previous Year Close
            # We group by symbol again so we don't calculate change across different stocks
            yearly_df['change'] = yearly_df.groupby('symbol')['close'].pct_change()
            
            yearly_df = yearly_df[yearly_df['year'] == (self.algo.time.year-1)]

            # Select the ticker with the highest percentage change and store it in the 'picked' cache
            # Returns a list containing the Symbol of the #1 top performer
            self.algo.picked[self.algo.time.year] = [c for c in yearly_df.sort_values('change', ascending=False).head(1)['symbol'].values]


            self.algo.log.debug(f"{self.algo.time.year}: {yearly_df.sort_values('change', ascending=False).head(20)['symbol']}")

        # Return the cached list of symbols for the current year to the universe selection model
        return self.algo.picked[self.algo.time.year]


class NonBenchmarkConstantAlphaModel(AlphaModel):
    ''' Provides an implementation of IAlphaModel that always returns the same insight for each security'''

    def __init__(self, algo, type, direction, period, magnitude = None, confidence = None, weight = None):
        '''Initializes a new instance of the ConstantAlphaModel class
        Args:
            type: The type of insight
            direction: The direction of the insight
            period: The period over which the insight with come to fruition
            magnitude: The predicted change in magnitude as a +- percentage
            confidence: The confidence in the insight
            weight: The portfolio weight of the insights'''
        self.algo = algo
        self.type = type
        self.direction = direction
        self.period = period
        self.magnitude = magnitude
        self.confidence = confidence
        self.weight = weight
        self.securities = []
        self.insights_time_by_symbol = {}

        self.Name = '{}({},{},{}'.format(self.__class__.__name__, type, direction, strfdelta(period))
        if magnitude is not None:
            self.Name += ',{}'.format(magnitude)
        if confidence is not None:
            self.Name += ',{}'.format(confidence)

        self.Name += ')'


    def update(self, algorithm, data):
        ''' Creates a constant insight for each security as specified via the constructor
        Args:
            algorithm: The algorithm instance
            data: The new data available
        Returns:
            The new insights generated'''
        insights = []

        for security in self.securities:
            # security price could be zero until we get the first data point. e.g. this could happen
            # when adding both forex and equities, we will first get a forex data point
            if security.price != 0 and self.should_emit_insight(algorithm.utc_time, security.symbol) and security.symbol != self.algo._symbol:
                insights.append(Insight(security.symbol, self.period, self.type, self.direction, self.magnitude, self.confidence, weight = self.weight))

        return insights


    def on_securities_changed(self, algorithm, changes):
        ''' Event fired each time the we add/remove securities from the data feed
        Args:
            algorithm: The algorithm instance that experienced the change in securities
            changes: The security additions and removals from the algorithm'''
        for added in changes.added_securities:
            self.securities.append(added)

        # this will allow the insight to be re-sent when the security re-joins the universe
        for removed in changes.removed_securities:
            if removed in self.securities:
                self.securities.remove(removed)
            if removed.symbol in self.insights_time_by_symbol:
                self.insights_time_by_symbol.pop(removed.symbol)


    def should_emit_insight(self, utc_time, symbol):
        if symbol.is_canonical():
            # canonical futures & options are none tradable
            return False

        generated_time_utc = self.insights_time_by_symbol.get(symbol)

        if generated_time_utc is not None:
            # we previously emitted a insight for this symbol, check it's period to see
            # if we should emit another insight
            if utc_time - generated_time_utc < self.period:
                return False

        # we either haven't emitted a insight for this symbol or the previous
        # insight's period has expired, so emit a new insight now for this symbol
        self.insights_time_by_symbol[symbol] = utc_time
        return True

def strfdelta(tdelta):
    d = tdelta.days
    h, rem = divmod(tdelta.seconds, 3600)
    m, s = divmod(rem, 60)
    return "{}.{:02d}:{:02d}:{:02d}".format(d,h,m,s)
# region imports
from AlgorithmImports import *
# endregion

class CustomSecurityInitializer(BrokerageModelSecurityInitializer):

    def __init__(self, brokerage_model: IBrokerageModel, security_seeder: ISecuritySeeder) -> None:
        super().__init__(brokerage_model, security_seeder)

    def initialize(self, security: Security) -> None:
        super().initialize(security)
        
        security.set_slippage_model(VolumeShareSlippageModel())
        security.set_settlement_model(ImmediateSettlementModel())
        security.set_leverage(1.0)
        security.set_buying_power_model(CashBuyingPowerModel())
        security.set_fee_model(InteractiveBrokersFeeModel())
        security.set_margin_model(SecurityMarginModel.NULL)
from AlgorithmImports import *

from Newtonsoft.Json import JsonConvert
import System
import psutil

class Utils():
    def __init__(self, algo, ticker):
        self.algo = algo
        self.ticker = ticker
        self.mkt = []
        self.insights_key = f"{self.algo.project_id}/Live_{self.algo.live_mode}_insights"
        self.algo.set_benchmark(ticker)

        self._initial_portfolio_value = self.algo.init_cash
        self._initial_benchmark_price = 0
        self._portfolio_high_watermark = 0

        self.init_chart()

    def init_chart(self):
        chart_name = "Strategy Performance"
        chart = Chart(chart_name)

        strategy_series = Series("Strategy", SeriesType.LINE, 0, "$")
        strategy_series.color = Color.ORANGE
        chart.add_series(strategy_series)

        benchmark_series = Series("Benchmark", SeriesType.LINE, 0, "$")
        benchmark_series.color = Color.LIGHT_GRAY
        chart.add_series(benchmark_series)

        drawdown_series = Series("Drawdown", SeriesType.LINE, 1, "%")
        drawdown_series.color = Color.INDIAN_RED
        chart.add_series(drawdown_series)

        allocation_series = Series("Allocation", SeriesType.LINE, 2, "%")
        allocation_series.color = Color.CORNFLOWER_BLUE
        chart.add_series(allocation_series)

        holding_series = Series("Holdings", SeriesType.LINE, 3, "")
        holding_series.color = Color.YELLOW_GREEN
        chart.add_series(holding_series)

        self.algo.add_chart(chart)

    def plot(self):
        if self.algo.live_mode or self.algo.is_warming_up:
            return


        # Capture initial reference values
        if self._initial_portfolio_value == 0.0:
            self._initial_portfolio_value = float(self.algo.portfolio.total_portfolio_value)

        benchmark_price = float(self.algo.securities[self.algo._symbol].price)
        if self._initial_benchmark_price == 0.0 and benchmark_price > 0.0:
            self._initial_benchmark_price = benchmark_price

        # Ensure both initial values are set
        if self._initial_portfolio_value == 0.0 or self._initial_benchmark_price == 0.0:
            return

        # Current values
        current_portfolio_value = float(self.algo.portfolio.total_portfolio_value)

        # Defensive check (avoid division by zero)
        if self._initial_portfolio_value == 0.0 or self._initial_benchmark_price == 0.0:
            return

        # Normalize (start at 1.0)
        normalized_portfolio = current_portfolio_value / self._initial_portfolio_value
        normalized_benchmark = benchmark_price / self._initial_benchmark_price

        current_value = self.algo.portfolio.total_portfolio_value

        if current_value > self._portfolio_high_watermark:
            self._portfolio_high_watermark = current_value

        drawdown = 0.0

        if self._portfolio_high_watermark != 0.0:
            drawdown = (current_value - self._portfolio_high_watermark) / self._portfolio_high_watermark * 100.0

        holding_count = 0

        for symbol in list(self.algo.securities.keys()):
            if symbol is None:
                continue
            holding = self.algo.portfolio[symbol]
            if holding is None or not holding.invested:
                continue

            holding_count += 1

        chart_name = "Strategy Performance"
        self.algo.plot(chart_name, "Drawdown", drawdown)
        self.algo.plot(chart_name, "Strategy", normalized_portfolio*self.algo.init_cash)
        self.algo.plot(chart_name, "Benchmark", normalized_benchmark*self.algo.init_cash)
        self.algo.plot(chart_name, "Allocation", round(self.algo.portfolio.total_holdings_value / self.algo.portfolio.total_portfolio_value,2)*100)
        self.algo.plot(chart_name, "Holdings", holding_count)

        self.algo.plot('Strategy Equity', self.ticker, normalized_benchmark*self.algo.init_cash)

    def pctc(no1, no2):
        return((float(str(no2))-float(str(no1)))/float(str(no1)))


    def stats(self):
        df = None
        trades = self.algo.trade_builder.closed_trades
        for trade in trades:
            data = {
                'symbol': trade.symbol,
                'time': trade.entry_time,
                'entry_price': trade.entry_price,
                'exit_price': trade.exit_price,
                'pnl': trade.profit_loss,
                'pnl_pct': (trade.exit_price - trade.entry_price)/trade.entry_price,
            }
            df = pd.concat([pd.DataFrame(data=data, index=[0]), df])
        
        if df is not None:
            profit = df.query('pnl >= 0')['pnl'].sum()
            loss = df.query('pnl < 0')['pnl'].sum()

            avgWinPercentPerWin = "{0:.2%}".format(df.query('pnl >= 0')['pnl_pct'].mean())
            avgLostPercentPerLost = "{0:.2%}".format(df.query('pnl < 0')['pnl_pct'].mean())
            maxLost = "{0:.2%}".format(df.query('pnl < 0')['pnl_pct'].min())
            maxWin = "{0:.2%}".format(df.query('pnl > 0')['pnl_pct'].max())
            
            self.algo.set_summary_statistic("*Profit Ratio", round(profit / abs(loss),2))
            self.algo.set_summary_statistic("Avg. Win% Per Winner", avgWinPercentPerWin)
            self.algo.set_summary_statistic("Avg. Lost% Per Losser", avgLostPercentPerLost)
            self.algo.set_summary_statistic("Max Loss%", maxLost)
            self.algo.set_summary_statistic("Max Win%", maxWin)


    def read_insight(self):
        if self.algo.object_store.contains_key(self.insights_key) and self.algo.live_mode:
            insights = self.algo.object_store.read_json[System.Collections.Generic.List[Insight]](self.insights_key)
            self.algo.log.debug(f"Read {len(insights)} insight(s) from the Object Store")
            self.algo.insights.add_range(insights)
            
            #self.algo.object_store.delete(self.insights_key)

    def store_insight(self):
        if self.algo.live_mode:
            insights = self.algo.insights.get_insights(lambda x: x.is_active(self.algo.utc_time))
            # If we want to save all insights (expired and active), we can use
            # insights = self.insights.get_insights(lambda x: True)

            self.algo.log.debug(f"Save {len(insights)} insight(s) to the Object Store.")
            content = ','.join([JsonConvert.SerializeObject(x) for x in insights])
            self.algo.object_store.save(self.insights_key, f'[{content}]')

    
    def trace_memory(self, name):
        self.algo.log.debug(f"[{name}] RAM memory % used: {psutil.virtual_memory()[2]} / RAM Used (GB): {round(psutil.virtual_memory()[3]/1000000000,2)}")