Overall Statistics
Total Orders
10001
Average Win
0.10%
Average Loss
-0.10%
Compounding Annual Return
26.876%
Drawdown
7.400%
Expectancy
0.161
Start Equity
100000
End Equity
219857.44
Net Profit
119.857%
Sharpe Ratio
2.23
Sortino Ratio
2.475
Probabilistic Sharpe Ratio
99.760%
Loss Rate
43%
Win Rate
57%
Profit-Loss Ratio
1.04
Alpha
0.139
Beta
0.289
Annual Standard Deviation
0.073
Annual Variance
0.005
Information Ratio
0.836
Tracking Error
0.099
Treynor Ratio
0.562
Total Fees
$15359.52
Estimated Strategy Capacity
$3800000.00
Lowest Capacity Asset
BEP VHBJM1A2DQAT
Portfolio Turnover
32.72%
#region imports
from AlgorithmImports import *
#endregion

class Cramer(PythonData):
    '''Cramer Recommendation'''
    def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live_mode: bool) -> SubscriptionDataSource:
        url = "https://raw.githubusercontent.com/aghalandar/Eventstudy/main/Transformed_Cramer2.csv"
        return SubscriptionDataSource(url, SubscriptionTransportMedium.REMOTE_FILE)

    def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live_mode: bool) -> BaseData:
        if not (line.strip() and line[0].isdigit()): return None

        cramer = Cramer()
        cramer.Symbol = config.Symbol

        # Example File Format:
        # Date,Buy,Sell,Negative Mention,Positive Mention  
        # 2016-04-18,"DEPO,OXY,RVNC,VZ","ADMS,GPS,MYGN","AEGN,IBM,IONS","CAT,COP,UTX,WMT,XOM"
        data = line.split(',')
        cramer.Time = datetime.strptime(data[0], "%Y-%m-%d")
        cramer.Buy = data[1].split(' ') if data[1] else []
        cramer.Sell = data[2].split(' ') if data[2] else []
        cramer.Positive_Mention = data[4].split(' ') if data[4] else []
        cramer.Negative_Mention = data[3].split(' ') if data[3] else []
        
        return cramer
# region imports
from AlgorithmImports import *
from CustomData import Cramer
# endregion

class FatLightBrownCaribou(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2016, 4, 1)
        self.set_end_date(2023, 1, 1)
        self.set_cash(100000)
        # add Cutome Data: Stock recommendation
        self.recommendation_symbol = self.AddData(Cramer, "Cramer", Resolution.Daily).Symbol

        # Universe Selection
        self.UniverseSettings.Resolution = Resolution.Daily
        self.fundamental_count = 5000
        self.AddUniverse(self.FundamentalSelectionFunction)
        # self.universe_settings.leverage = 2.0

        self.process_recomm = -1 
        self.buy_recommendations = [] # list of daily Buy recommendation
        self.sell_recommendations = [] # list of daily sell recommendation
        self.entry_dates = {} # Track the entry date of each position
        self.holding_period = 3  # Number of days to hold a position
            
    def FundamentalSelectionFunction(self, fundamental):

        selected = [x for x in fundamental if x.HasFundamentalData and x.MarketCap != 0 and 
                    x.Market == 'usa' and x.Price > 1]

        if len(selected) > self.fundamental_count:
            selected = [x for x in sorted(selected, key = lambda x: x.DollarVolume, reverse = True)]\
                [:self.fundamental_count]

        return [x.Symbol for x in selected]

    def on_data(self, data):
        
        # if we already process the recommendation today
        if self.process_recomm == self.Time.date(): 
            return

        self.process_recomm = self.Time.date()

        #Check if holding period has been reached and close positions
        to_liquidate = []
        for symbol, entry_date in self.entry_dates.items():
            if self.Time.date() > entry_date + timedelta(days=self.holding_period):
                to_liquidate.append(symbol)

        for symbol in to_liquidate:
            self.Liquidate(symbol)
            del self.entry_dates[symbol]  # Remove from the entry_dates dictionary

        # check if there is any recommendation
        if not data.ContainsKey(self.recommendation_symbol):
            #self.Debug(f"No data for {self.recommendation_symbol} at {self.Time}")
            return
        #self.Debug(f"Buy Recommendation are : {data[self.recommendation_symbol].Buy}")
        #self.Debug(f"sell Recommendation are : {data[self.recommendation_symbol].Sell}")

        # Process Buy recommendations
        self.buy_recommendations = data[self.recommendation_symbol].Buy
        self.sell_recommendations = data[self.recommendation_symbol].Sell

        # setting the target based on recommendation
        target = {}
        for i, recomm in enumerate([self.buy_recommendations, self.sell_recommendations]):
            # if there is any recommendation on the list
            if not recomm: continue
            for ticker in recomm:
                # create the ticker. without adding the ticker to our universe. 
                _symbol = Symbol.create(ticker, SecurityType.EQUITY, Market.USA)
                if data.ContainsKey(_symbol) and data[_symbol] is not None and not self.Portfolio[_symbol].Invested:
                    target[_symbol] = ((-1)**i / len(recomm) / self.holding_period)
                    # the weight for buy is maximum 10% and for sell is minimum -10%. 
                    target[_symbol] = min(target[_symbol], 0.1) if i == 0 else max(target[_symbol], -0.1)
                    # Record the entry date
                    self.entry_dates[_symbol] = self.Time.date()  

            self.SetHoldings([PortfolioTarget(symbol, weight) for symbol, weight in target.items()])

            self.buy_recommendations = []
            self.sell_recommendations = []

    # # Just to check the orders. 
    # def OnOrderEvent(self, orderEvent):
    #     if orderEvent.Status == OrderStatus.Filled:
    #         self.Debug(f"Order filled: {orderEvent.Symbol} at {orderEvent.FillPrice} on {self.Time}")