Overall Statistics
Total Orders
547
Average Win
1.16%
Average Loss
-0.67%
Compounding Annual Return
17.668%
Drawdown
26.300%
Expectancy
0.610
Start Equity
100000
End Equity
290308.64
Net Profit
190.309%
Sharpe Ratio
0.73
Sortino Ratio
0.793
Probabilistic Sharpe Ratio
20.062%
Loss Rate
41%
Win Rate
59%
Profit-Loss Ratio
1.74
Alpha
0.085
Beta
0.66
Annual Standard Deviation
0.173
Annual Variance
0.03
Information Ratio
0.463
Tracking Error
0.137
Treynor Ratio
0.191
Total Fees
$10948.13
Estimated Strategy Capacity
$78000.00
Lowest Capacity Asset
KF R735QTJ8XC9X
Portfolio Turnover
3.79%
from AlgorithmImports import *
class CustomDataEMFAlgorithm(QCAlgorithm):

    def initialize(self):

        self.set_start_date(2008, 1, 8)
        self.set_end_date(2014, 7, 25)
        self.set_cash(100000)
        self.activeSecurities = set()

        universe = self.add_universe(MyCustomUniverseDataClass, "myCustomUniverse", self.selector_function)
        self.universe_settings.resolution = Resolution.DAILY

        self.portfolioTargets = []
        self.aro_data = {1: {}, 3: {}, 5: {}} # Add this line to initialize the dictionary
        self.cef_discount_data = {} 
        self.cef_purchase_discount = {}

        ### Define discount percentile upper and lower limits below
        self.aro_dptl_lower_limit = float(self.get_parameter("dptl_lower_limit"))
        self.aro_dptl_upper_limit = float(self.get_parameter("dptl_upper_limit"))

        ###Define period whether, 1, 3, or 5 year below
        self.period = 1


     # Define the selector function
    def selector_function(self, data):
        sorted_data = sorted([ x for x in data if x["CEF_Discount"] <= 0 ],
                            key=lambda x: x.end_time) #,
                            # reverse=True)
        self.Log('Stocks trading at a discount to NAV: ' + str(len(sorted_data)))
        for x in sorted_data:
            self.aro_data[5][x.symbol] = x["ARO_DPTL_5YR"]
            self.aro_data[3][x.symbol] = x["ARO_DPTL_3YR"]
            self.aro_data[1][x.symbol] = x["ARO_DPTL_1YR"]
            self.cef_discount_data[x.symbol] = x["CEF_Discount"]
        return [x.symbol for x in sorted_data]    

    def on_securities_changed(self, changes):
        # close positions in removed securities
        for x in changes.RemovedSecurities:
            self.Debug(f"{self.Time}: Removed {x.Symbol}")
            # self.Debug(f"ARO_DPTL_{self.period}YR for {x.Symbol} when removed: {self.aro_data[self.period][x.Symbol]}")
            self.Liquidate(x.Symbol)
            if x.Symbol in self.activeSecurities:
                self.Debug(f"ARO_DPTL_{self.period}YR for {x.Symbol} when removed: {self.aro_data[self.period][x.Symbol]}")
                self.activeSecurities.remove(x.Symbol)
                del self.aro_data[self.period][x.Symbol]

            

        # can't open positions here since data might not be added correctly yet
        for x in changes.AddedSecurities:
            self.Debug(f"{self.Time}: Added {x.Symbol}")
            self.Debug(f"ARO_DPTL_{self.period}YR for {x.Symbol} when added: {self.aro_data[self.period][x.Symbol]}")
            self.activeSecurities.add(x.Symbol)
            

        # adjust targets if universe has changed
        self.portfolioTargets = [PortfolioTarget(symbol, 1/len(self.activeSecurities)) 
                            for symbol in self.activeSecurities]
        
    def OnData(self, data):
        
        
        if self.portfolioTargets == []:
            return
        
        for target in self.portfolioTargets:
            symbol = target.Symbol
            price = self.Securities[symbol].Price  # Get the current price
            aro_value = self.aro_data[self.period][symbol]
            cef_discount_value = self.cef_discount_data[symbol] # Get the CEF discount value
            if aro_value < self.aro_dptl_lower_limit and not self.Portfolio[symbol].Invested:
                self.cef_purchase_discount[symbol] = self.cef_discount_data[symbol]
                self.SetHoldings(self.portfolioTargets) # Buy
                self.Debug(f"Buying {symbol} at {self.Time}, Price: {price}, ARO_DPTL_{self.period}YR: {aro_value}, Purchase Discount: {self.cef_purchase_discount[symbol]}")
            elif aro_value > self.aro_dptl_upper_limit and self.Portfolio[symbol].Invested:
                self.Liquidate(symbol) # Sell
                self.Debug(f"Selling {symbol} at {self.Time}, Price: {price}, ARO_DPTL_{self.period}YR: {aro_value}")

            self.Plot("ARO Data", f"ARO_DPTL_{self.period}YR", self.aro_data[self.period][symbol])
            self.Plot("ARO Data", "CEF_Discount", cef_discount_value)
            self.Plot("ARO Data", "Price", price)
        
        
        # self.portfolioTargets = []
       
# Example custom universe data; it is virtually identical to other custom data types.
class MyCustomUniverseDataClass(PythonData):		
    def get_source(self, config, date, is_live_mode):
        source = "https://raw.githubusercontent.com/jabertech/backtest-qc/main/sorted_5_stocks2.csv"
        return SubscriptionDataSource(source, SubscriptionTransportMedium.RemoteFile);
		
    def Reader(self, config, line, date, isLive):
        if not (line.strip() and line[0].isalnum()):
            return None
			
        items = line.split(",")
    
        # Generate required data, then return an instance of your class.
        data = MyCustomUniverseDataClass()
		
        try:
            data.end_time = datetime.strptime(items[1], '%Y-%m-%d')+timedelta(hours=20)

            # define Time as exactly 1 day earlier Time
            data.time = data.end_time - timedelta(1)

            data.symbol = Symbol.create(items[0], SecurityType.EQUITY, Market.USA)
            data.value = float(items[5])
            data["Open"] = float(items[2])
            data["High"] = float(items[3])
            data["Low"] = float(items[4])
            data["Close"] = float(items[5])
            data["Volume"] = int(items[6])
            data["CEF_NAV"] = float(items[7])
            data["CEF_Discount"] = float(items[8])
            data["CEF_Price"] = float(items[9])
            data["ARO_DPTL_1YR"] = float(items[10])
            data["ARO_DPTL_3YR"] = float(items[12])
            data["ARO_DPTL_5YR"] = float(items[14])
        except ValueError:
            return None
        
        return data