Overall Statistics
Total Orders
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
10000
End Equity
10000
Net Profit
0%
Sharpe Ratio
0
Sortino Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
0
Tracking Error
0
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
Drawdown Recovery
0
# region imports
from AlgorithmImports import *
import math
import datetime
from statistics import fmean, pstdev
import copy
from enum import StrEnum, auto
# endregion


class Action(StrEnum):
    BOUGHT_LONG = auto()
    SOLD_SHORT = auto()
    ADDED_TO_LONG = auto()
    ADDED_TO_SHORT = auto()
    CLOSED_LONG_POSITION = auto()
    CLOSED_SHORT_POSITION = auto()


class Exit(StrEnum):
    TAKE_PROFIT = auto()
    STOP_LOSS = auto()
    END_OF_DAY = auto()


class SP500Algorithm(QCAlgorithm):

    def initialize(self):
        self.initial_margin = 1600
        self.maintenance_margin = 1100
        self.initial_cash = 10000
        self.portfolio_buffer = 0.15
        self.margin_buffer = 0.05
        self.order_fee = 0.60
        
        self.set_start_date(2025, 1, 2)
        self.set_end_date(2025, 1, 2)
        self.set_cash(self.initial_cash)

        self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN)

        # Add S&P 500 E-mini futures contract
        # Read https://www.quantconnect.com/docs/v2/writing-algorithms/universes/futures for more on parameters
        self._continuous_contract = self.add_future(Futures.Indices.MICRO_SP_500_E_MINI,
                                                    resolution=Resolution.TICK,
                                                    data_normalization_mode=DataNormalizationMode.RAW,
                                                    data_mapping_mode=DataMappingMode.OpenInterest,
                                                    contract_depth_offset=0,
                                                    extended_market_hours=False)
        
        self.sp500symbol = self._continuous_contract.symbol
        self._sma20 = SimpleMovingAverage(20)
        self._atr = AverageTrueRange(6, MovingAverageType.SIMPLE)
        self.rate = InterestRateProvider()       
        self._sr = self.sr(self.sp500symbol, 22, self.rate.get_interest_rate(self.time), Resolution.DAILY)
        self._sr_annual = self.sr(self.sp500symbol, 252, self.rate.get_interest_rate(self.time), Resolution.DAILY)
        self._std = self.std(self.sp500symbol, 22, Resolution.DAILY)
        self._std_annual = self.std(self.sp500symbol, 252, Resolution.DAILY)
        self._current_contract = None
        self.holding = None

        # Get the time zone of the exchange for a specific asset.
        time_zone = self.securities[self.sp500symbol].exchange.hours.time_zone
        self.set_time_zone(time_zone)
        self.exchange = self.securities[self.sp500symbol].exchange
        self.schedule.on(
            self.date_rules.every_day(self.sp500symbol),
            self.time_rules.midnight,
            self.update_market_time
        )

        # for daily summary report
        self.schedule.on(
            self.date_rules.every_day(self.sp500symbol),
            self.time_rules.after_market_close(self.sp500symbol),
            self.update_summary_report
        )

        # reset indicators/windows every midnight
        self.schedule.on(
            self.date_rules.every_day(self.sp500symbol),
            self.time_rules.midnight,
            self.reset_values
        )

        # for buy and sell signal
        self.buy_atr = 0
        self.sell_atr = 0
        self.bar_counter = 0
        self.increasing_high = None
        self.decreasing_low = None

        self.first_buy_condition = False
        self.third_buy_condition = False
        self.first_sell_condition = False  
        self.third_sell_condition = False

        # record previous data
        self._trade_bar_window = RollingWindow[TradeBar](3)
        self._sma20_window = RollingWindow[float](2)

        self._consolidator = TickConsolidator(timedelta(minutes=5))
        self._consolidator.data_consolidated += self._consolidation_handler
        self.subscription_manager.add_consolidator(self.sp500symbol, self._consolidator)

        self.summary_list = []
        self.action_type = None
        self.exit_type = None
        self.total_position = 0 # average fill price times quantity

        self.margin_list = []
        self.portfolio_cash_list = []
        self.portfolio_value_list = []
        self.trading_fees_list = []
        self.sharpe_ratio_info = []


        self.set_trade_builder(TradeBuilder(FillGroupingMethod.FLAT_TO_FLAT, FillMatchingMethod.FIFO))

        self.log_header()
        
        self.security_exchange_hours = self.securities[self.sp500symbol].exchange.hours
        self.update_market_time()


    def log_header(self):
        # self.log(", Date, Time, Current SMA-20,SP 500 High,SP 500 Low,SP 500 Close,SP 500 Volume,ATR,Increasing High,Decreasing Low,Status") 
        # self.log(",Open,High,Low,Close,Volume")
        pass
    def calculate_contracts(self):
        buffer = self.calculate_margin_buffer()
        available_cash = self.portfolio.cash - buffer
        contracts = math.floor((available_cash/(self.initial_margin + self.maintenance_margin))* 2/3)
        if available_cash + (buffer * 0.10) >= contracts *(self.order_fee + self.initial_margin + self.maintenance_margin):
            self.half_contracts = math.floor(contracts/2)
            return contracts
        else:
            self.half_contracts = math.floor((contracts-1)/2)
            return contracts - 1

    def calculate_margin_buffer(self):
        if self.portfolio_buffer * self.portfolio.cash > self.initial_margin * (1 + self.margin_buffer):
            return self.portfolio_buffer * self.initial_cash 
        else:
            return self.initial_margin * (1 + self.margin_buffer)   

    def csv_log(self, message):
        bar = self._trade_bar_window[0]
        # self.log(f",{self.time.date()}, {self.time.time()}, {self.current_sma20_value}, {bar.high}, {bar.low}, {bar.close}, {bar.volume}, {self._atr.current.value}, {self.increasing_high}, {self.decreasing_low}, {message}")

    def update_market_time(self):
        self.market_open = self.security_exchange_hours.get_next_market_open(self.time, extended_market_hours=False)
        self.market_close = self.security_exchange_hours.get_next_market_close(self.time, extended_market_hours=False)

    def update_summary_report(self):
        # for summary report
        self.portfolio_cash_list.append(f",{self.time.date()}, {self.time.time()}, Portfolio Cash, {self.portfolio.cash}")
        self.portfolio_value_list.append(f",{self.time.date()}, {self.time.time()}, Portfolio Value, {self.portfolio.total_portfolio_value}")
        self.trading_fees_list.append(f",{self.time.date()}, {self.time.time()}, Trading Fees, {self.portfolio.total_fees}")
        self.sharpe_ratio_info.append(f",{self.time.date()}, {self.time.time()}, Risk Free Rate, {self.rate.get_interest_rate(self.time)}, Portfolio Value, {self.portfolio.total_portfolio_value}, Sharpe Ratio Monthly, {self._sr.current.value}, Standard Deviation Monthly, {self._std.current.value}, Sharpe Ratio Annual, {self._sr_annual.current.value}, Standard Deviation Annual, {self._std_annual.current.value}")
        # self.generate_report() 

    def on_order_event(self, order_event: OrderEvent) -> None:
        "Record order event for log file summary"
        order = self.transactions.get_order_by_id(order_event.order_id)
        if order_event.status == OrderStatus.FILLED:
            if self.holding.invested:
                pnl = ""
            else:
                pnl = -(order_event.fill_price * order_event.fill_quantity + self.total_position) * 5
            s = f"{self.time.date()}, {self.time.time()}, {order_event.symbol}, {order_event.fill_price}, {order_event.fill_quantity}, {pnl}, {self.action_type}, {self.exit_type}, {self._atr.current.value:.3f}, {order_event.order_fee}, {self.portfolio.total_portfolio_value}"
            self.summary_list.append(s)

            # portfolio info log output for analysis
            s = f"{self.time}, {self.portfolio.cash}, {self.portfolio.total_margin_used}, {self.portfolio.get_margin_remaining(self.sp500symbol, OrderDirection.BUY)}"
            self.margin_list.append(s)

    def _order_handler(self, tick):
        "Handles tick data with 20 millisecond (backtesting) or 70 millisecond (live) period after getting a position."
        bar = self._trade_bar_window[0]
        previous_bar = self._trade_bar_window[1]
        second_previous_bar = self._trade_bar_window[2]

        self.current_sma20_value = self._sma20.current.value
        current_time = self.time
        closing_day = self.market_close - datetime.timedelta(minutes=61)
        end_of_day = current_time >= closing_day

        # 1st liquidate condition:take profit
        # 2nd liquidate condition: stop loss
        # 3rd liquidate condition: 3:59PM closing time
        if self.holding.is_long:
            take_profit_period = self.buy_atr * 2.5
            take_profit = tick.price >= self.holding.average_price + take_profit_period
            stop_loss_period = self.buy_atr * 1.5
            take_profit_half_period = self.holding.average_price + take_profit_period/2
            stop_loss = tick.price  <= self.holding.average_price - stop_loss_period 

            if take_profit:
                self.exit_type = Exit.TAKE_PROFIT
                self.csv_log("trade entered: liquidating - taking profit")
                s = f", {self.holding.average_price + take_profit_period},"
            elif stop_loss:
                self.exit_type = Exit.STOP_LOSS
                self.csv_log("trade entered: liquidating - stop loss")
                s = f", , {self.holding.average_price - stop_loss_period}"
            elif end_of_day:
                self.exit_type = Exit.END_OF_DAY
                self.csv_log(f"trade entered: liquidating - closing date at {closing_day}")
                s = ", , "
            if take_profit or stop_loss or end_of_day:
                self.action_type = Action.CLOSED_LONG_POSITION
                self.liquidate(self._current_contract.symbol)
                self.total_position = 0
                self.summary_list[-1] += s
                return 

        # 1st liquidate condition: take profit
        # 2nd liquidate condition: stop loss
        # 3rd liquidate condition: 3:59PM closing time
        if self.holding.is_short:
            take_profit_period = self.sell_atr * 2.5
            take_profit = tick.price  <= self.holding.average_price - take_profit_period
            stop_loss_period = self.sell_atr * 1.5
            take_profit_half_period = self.holding.average_price - take_profit_period/2         
            stop_loss = tick.price >= self.holding.average_price + stop_loss_period  

            if take_profit:
                self.exit_type = Exit.TAKE_PROFIT
                self.csv_log("trade entered: liquidating - taking profit")
                s = f", {self.holding.average_price - take_profit_period}, "
            elif stop_loss:
                self.exit_type = Exit.STOP_LOSS
                self.csv_log("trade entered: liquidating - stop loss")
                s = f", , {self.holding.average_price + stop_loss_period}"
            elif end_of_day:
                self.exit_type = Exit.END_OF_DAY
                self.csv_log(f"trade entered: liquidating - closing date at {closing_day}")
                s = ", , "
            if take_profit or stop_loss or end_of_day:
                self.action_type = Action.CLOSED_SHORT_POSITION
                self.liquidate(self._current_contract.symbol)
                self.total_position = 0
                self.summary_list[-1] += s

                # ensures buying signal state is properly reset before going back to 5-minute bar
                self.first_buy_condition = False
                return 

    def _consolidation_handler(self, sender: object, bar: TradeBar) -> None:
        "Handles 5-minute bar period."
        # run code only from 12:00AM to 4:00PM regular trading hour
        current_time = self.time
        closing_day = self.market_close - datetime.timedelta(minutes=60)
        opening_day = self.market_open + datetime.timedelta(minutes=150)
        if current_time >= self.market_open and current_time <= self.market_close:
            # manually update simple moving average and average true range
            self._sma20.update(bar.end_time, bar.close)
            self._atr.update(bar)
            self._sma20_window.add(self._sma20.current.value)
            if current_time < opening_day:
                return
        elif current_time >= opening_day and current_time < closing_day:
            pass
        else:
            if self.live_mode:
                self.log(f"{self.time}, Received data outside of running time from {opening_day} to {closing_day}")
            return     

        # record bar data to enable access previous bar
        self._trade_bar_window.add(bar) 
        previous_bar = self._trade_bar_window[1]
        second_previous_bar = self._trade_bar_window[2]

        # plot sharpe ratio
        if self._sr.is_ready:
            # The current value of self._sr is represented by self._sr.current.value
            self.plot("SharpeRatio", "sr", self._sr.current.value)

        self.current_sma20_value = self._sma20_window[0]
        previous_sma20_value = self._sma20_window[1]
        current_close_above_sma = bar.close > self.current_sma20_value
        current_close_below_sma = bar.close < self.current_sma20_value

        try:
            previous_close_above_sma = previous_bar.close > previous_sma20_value
        except:
            previous_close_above_sma = None

        try:
            self.increasing_high = second_previous_bar.high < previous_bar.high and previous_bar.high < bar.high
        except:
            self.increasing_high = False

        try:
            self.decreasing_low = second_previous_bar.low > previous_bar.low and previous_bar.low > bar.low   
        except:
            self.decreasing_low = False

        # check for condition to increase position due to increasing high or decreasing low
        if self.portfolio.invested:
            self.bar_counter += 1
            closing_trading_day = self.market_close - datetime.timedelta(minutes=60)
            end_of_day = current_time > closing_trading_day
            if self.holding.is_long and previous_close_above_sma is not None:
                # increasing high
                if  self.increasing_high and self.bar_counter >= 3 and self.third_buy_condition:
                    self.action_type = Action.ADDED_TO_LONG
                    self.exit_type = ""
                    self.market_order(self._current_contract.symbol, self.half_contracts)
                    self.total_position = self.holding.average_price * self.holding.quantity
                    self.third_buy_condition = False
                    self.csv_log("trade entered: increasing three consecutive bar price's high")
                    return  

            if self.holding.is_short and previous_close_above_sma is not None:                   
                # decreasing low
                if self.decreasing_low and self.bar_counter >= 3 and self.third_sell_condition: 
                    self.action_type = Action.ADDED_TO_SHORT
                    self.exit_type = ""
                    self.market_order(self._current_contract.symbol, -self.half_contracts)
                    self.total_position = self.holding.average_price * self.holding.quantity
                    self.third_sell_condition = False
                    self.csv_log("trade entered: decreasing three consecutive bar price's low")
                    return 
            if self.third_buy_condition or self.third_sell_condition:
                self.csv_log("condition for increasing high or decreasing low not met")
            else:
                self.csv_log("Liquidation conditions not met")                           
            return # prevents double buying/selling

        closing_trading_day = self.market_close - datetime.timedelta(minutes=65)
        last_trading_day = current_time > closing_trading_day
        # Implement trading logic based on three buy/sell conditions           
        if previous_bar and not last_trading_day:  
            # Check if current price is above the SMA and higher than previous close with increased volume
            current_price_above_sma = bar.close > self.current_sma20_value
            higher_than_previous_close = bar.close > previous_bar.close
            lower_than_previous_close = bar.close < previous_bar.close
            increased_volume = bar.volume > previous_bar.volume

            # first buy condition: price moves above the SMA from below
            if not self.first_buy_condition:
                if not previous_close_above_sma and current_price_above_sma: # increase price and volume condition
                    self.first_buy_condition = True
                    self.csv_log("first buying condition met")
                else:
                    self.csv_log("first buying condition not met")

            # second buying condition: the next bar has higher price & higher volume
            # third buying condition: ATR must be less than to 6.75
            elif self.first_buy_condition and higher_than_previous_close and increased_volume and self._atr.current.value < 6.75: 
                self._current_contract = self.securities[self._continuous_contract.mapped]
                self.holding = self.portfolio[self._current_contract.symbol]
                self.action_type = Action.BOUGHT_LONG
                self.exit_type = ""
                contracts = self.calculate_contracts()
                self.market_order(self._current_contract.symbol, contracts)
                self.total_position = self.holding.average_price * self.holding.quantity
                self.csv_log("trade entered: all buying signal conditions met")
                self.third_buy_condition = True
                self.first_buy_condition = False
                self.buy_atr = self._atr.current.value
                self.bar_counter = 0
            # reset state of first buy condition if second or third buying condition not met
            else:
                self.first_buy_condition = False
                if not (higher_than_previous_close and increased_volume):
                    self.csv_log("second buying condition not met")
                else:
                    self.csv_log("third buying condition not met")

            # first sell condition: price moves below the SMA from above
            if not self.first_sell_condition:
                if previous_close_above_sma and not current_price_above_sma: # increase price and volume condition
                    self.first_sell_condition = True
                    self.csv_log("first selling condition met")
                else:
                    self.csv_log("first selling condition not met")
 
            # second selling condition: the next bar has lower price & higher volume
            # third selling condition:  ATR must be less than to 6.75
            elif self.first_sell_condition and lower_than_previous_close and increased_volume and self._atr.current.value < 6.75:
                self._current_contract = self.securities[self._continuous_contract.mapped]
                self.holding = self.portfolio[self._current_contract.symbol]
                self.action_type = Action.SOLD_SHORT
                self.exit_type = ""  
                contracts = self.calculate_contracts()             
                self.market_order(self._current_contract.symbol, -contracts)
                self.total_position = self.holding.average_price * self.holding.quantity
                self.csv_log("trade entered: all selling signal conditions met")
                self.third_sell_condition = True
                self.first_sell_condition = False
                self.sell_atr = self._atr.current.value
                self.bar_counter = 0
            # reset state of first sell condition if second and third selling condition not met
            else:
                self.first_sell_condition = False
                if not (lower_than_previous_close and increased_volume):
                    self.csv_log("second selling condition not met")
                else:
                    self.csv_log("third selling condition not met")

    # def on_symbol_changed_events(self, symbol_changed_events):
    #     for symbol, changed_event in  symbol_changed_events.items():
    #         old_symbol = changed_event.old_symbol
    #         new_symbol = changed_event.new_symbol
    #         quantity = self.portfolio[old_symbol].quantity
            
    #         # Rolling over: To liquidate the old mapped contract and switch to the new mapped contract.
    #         self.log(f"Rollover - Symbol changed at {self.time}: {old_symbol} -> {new_symbol}")
    #         self.liquidate(old_symbol)
    #         if quantity: 
    #             self.market_order(new_symbol, quantity)
    #             self._current_contract = self.securities[self._continuous_contract.mapped]
    #             self.holding = self.portfolio[tself._current_contract.symbol]


    def reset_values(self):
        "Reset values so we start fresh"
        self.__last = None
        self._trade_bar_window.reset()
        self._sma20_window.reset()
        self.first_buy_condition = False
        self.first_sell_condition = False


    def on_data(self, data: Slice):
        # opening_day = datetime.datetime(2025, 1, 7, 10, 21, 46, 939891)
        # closing_day = datetime.datetime(2025, 1, 7, 10, 30, 0, 0)
        # current_time = self.time

        # quarter_hour = 31 # change from 0 to 31
        # current_time = self.time
        # opening_day = self.market_open + datetime.timedelta(minutes=quarter_hour*15)
        # closing_day = self.market_open + datetime.timedelta(minutes=(quarter_hour+1)*15)


        # half_hour = 14 # change from 0 to 14
        # current_time = self.time
        # opening_day = self.market_open + datetime.timedelta(minutes=half_hour*30)
        # closing_day = self.market_open + datetime.timedelta(minutes=(half_hour+1)*30)

        hour = 2 # change from 0 to 7
        current_time = self.time
        opening_day = self.market_open + datetime.timedelta(minutes=hour*60)
        closing_day = self.market_open + datetime.timedelta(minutes=(hour+1)*60)

        if current_time < opening_day or current_time >= closing_day:
            return
        # if not self.portfolio.invested:
        #     return
        if not data.contains_key(self.sp500symbol):
            return
        if not self.is_market_open(self.sp500symbol):
            return

        ticks = data.ticks.get(self.sp500symbol, [])   # Empty if not found
    
        for tick in ticks:
            if tick.quantity:
                self.log(f", {tick.end_time}, {tick.price}, {tick.quantity}, {tick.exchange_code}")
            # self._order_handler(tick)
            return  # check only a single tick

    def on_end_of_algorithm(self) -> None:
    # def generate_report(self):
        "Create Log output for Summary"
        pass
        # self.log(", Trade Summary")
        # self.log(f", Date, Time, Symbol, Fill Price, Quantity, PnL, Action, Exit Type, ATR, Order Fee, Portfolio Value, Take Profit Price, Stop Loss Price")
        # for x in self.summary_list:
        #     self.log(", " + x)
        # value_list = [float(value.split(",")[4]) for value in self.summary_list if len(value.split(",")) == 5]
        # pnl = -sum(value_list)
        # self.log(f", {self.time}, pnl:, {pnl}")

        # self.log(", Margin Summary")
        # self.log(f", Time, Portfolio Cash, Margin Used, Margin Remaining")
        # for x in self.margin_list:
        #     self.log(", " + x)

        # trades = self.trade_builder.closed_trades
        # ts = TradeStatistics(trades)
        # if trades:
        #     profitable_trades = list()
        #     losing_trades = list()
        #     breakeven_trades = list()
        #     for trade in trades:
        #         if trade.profit_loss > 0:
        #             profitable_trades.append(trade)
        #         elif trade.profit_loss < 0:
        #             losing_trades.append(trade)
        #         else:
        #             breakeven_trades.append(trade)
        #     gross_pnl = sum([trade.profit_loss for trade in trades])
        #     no_of_trades = len(trades)
        #     contracts = int(sum([trade.quantity for trade in trades]))
        #     time_trades = [trade.duration for trade in trades]
        #     copy_tt = copy.deepcopy(time_trades)
        #     time_trades.sort()
        #     sum_trades = sum(time_trades, datetime.timedelta())
        #     mean_time_trade = str(sum_trades / no_of_trades)
        #     trades_fees = sum([trade.total_fees for trade in trades])
        #     total_pnl = self.portfolio.total_net_profit
        #     currency = self.account_currency

        #     self.log("All TRADES")
        #     self.log(f"Gross P/L: {currency}{gross_pnl:.2f}")
        #     self.log(f"No. of Trades: {no_of_trades}")
        #     self.log(f"No. of Contracts: {contracts}")
        #     self.log(f"Average Trade Time: {mean_time_trade}")
        #     longest = time_trades[-1]
        #     self.log(f"Longest Trade Time: {longest}")
        #     win_percent = (len(profitable_trades)/no_of_trades)
        #     loss_percent = 1 - win_percent
        #     if profitable_trades:
        #         ave_win = sum([trade.profit_loss for trade in profitable_trades]) / len(profitable_trades)
        #     else:
        #         ave_win = 0
        #     if losing_trades:
        #         ave_loss = sum([trade.profit_loss for trade in losing_trades]) / len(losing_trades)
        #     else:
        #         ave_loss = 0
        #     self.log(f"Profitable Trades: {(len(profitable_trades)/no_of_trades):.0%}")
        #     self.log(f"Expectancy: {currency}{(win_percent * ave_win - loss_percent * ave_loss):.2f}")
        #     self.log(f"Trade Fees: {currency}{trades_fees:.2f}")
        #     self.log(f"Total PnL: {currency}{total_pnl:.2f}")
        #     sharpe = self.statistics.total_performance.portfolio_statistics.sharpe_ratio
        #     self.log(f"Sharpe Ratio: {sharpe:.2f}")

        #     self.log("Annual Trades")
        #     # We can access the statistics summary at runtime
        #     statistics = self.statistics.rolling_performances
        #     standard_list = [f"{kvp.key}: {kvp.value.portfolio_statistics.annual_standard_deviation:.4f}" for kvp in statistics if kvp.key.startswith("M1_")]
        #     profit_list = [f"{kvp.key}: {kvp.value.portfolio_statistics.total_net_profit:.4f}" for kvp in statistics if kvp.key.startswith("M1_")]
        #     sharpe_list = [f"{kvp.key}: {kvp.value.portfolio_statistics.sharpe_ratio:.4f}" for kvp in statistics if kvp.key.startswith("M1_")]
        #     for stat in sharpe_list:
        #         self.log(f"Sharpe Ratio: {stat}")
        #     for stat in standard_list:
        #         self.log(f"Standard Deviation: {stat}")
        #     for stat in profit_list:
        #         self.log(f"Net Profit: {stat}")

        #     trade_names = ("Winning", "Losing")
        #     stat_names = ("Max Run-up", "Max Drawdown")
        #     pnl_names = ("Profit", "Loss")
        #     trades_list = (profitable_trades, losing_trades)
        #     for index, trades in enumerate(trades_list):
        #         self.log(" ") # adds new line to log file
        #         if trades:
        #             profits = [trade.profit_loss for trade in trades]
        #             if index:
        #                 profits.sort(reverse=True)
        #             else:
        #                 profits.sort()
        #             time_trades = [trade.duration for trade in trades]
        #             time_trades.sort()
        #             run_ups = [(index, trade.mfe) for index, trade in enumerate(trades)]
        #             run_ups.sort(key=lambda x: x[1])
        #             i, max_run_up = run_ups[-1]
        #             max_trade = trades[i]
        #             self.log(f"{trade_names[index]} Trades")
        #             self.log(f"Total {pnl_names[index]}: {currency}{sum(profits):.2f}")
        #             self.log(f"No. of {trade_names[index]} Trades: {len(trades)}")
        #             self.log(f"No. of {trade_names[index]} Contracts: {int(sum([trade.quantity for trade in trades]))}")
        #             self.log(f"Largest {trade_names[index]} Trade: {currency}{profits[-1]:.2f}")
        #             self.log(f"Average {trade_names[index]} Trade: {currency}{fmean(profits):.2f}")
        #             self.log(f"Std. Dev. {trade_names[index]} Trade: {currency}{pstdev(profits):.2f}")
        #             self.log(f"Longest {trade_names[index]} Trade Time: {time_trades[-1]}")
        #             self.log(f"{stat_names[index]}: {currency}{max_run_up:.2f}")
        #             self.log(f"{stat_names[index]} from: {max_trade.entry_time}")
        #             self.log(f"{stat_names[index]} to: {max_trade.exit_time}")


        # self.log("DAILY STATS")
        # for cash in self.portfolio_cash_list:
        #     self.log(cash)
        # for value in self.portfolio_value_list:
        #     self.log(value)
        # for fee in self.trading_fees_list:
        #     self.log(fee)
        # for info in self.sharpe_ratio_info:
        #     self.log(info)

        # # add marker to seperate daily logs
        # self.log("<END OF DAY>")
        # self.log_header()

        # self.summary_list = []
        # self.margin_list = []
        # self.portfolio_cash_list = []
        # self.portfolio_value_list = []
        # self.trading_fees_list = []