Overall Statistics
import pandas as pd
from datetime import datetime

class FXMomentumAlgorithm(QCAlgorithm):
    def Initialize(self):

        # self.SetStartDate(2019, 1, 5)
        # self.SetEndDate(datetime.now())
        self.SetStartDate(2022, 2, 3)
        self.SetEndDate(2022, 2, 9)
        self.SetCash(100000)
        self.resolution = Resolution.Minute
        self.pair = 'USDCHF'
        self._orders = 0
        self._grid_number = 2
        # self._grid_number = 20
        self._reference_price = None
        self._std = None

        self.AddForex(self.pair, self.resolution, Market.Oanda)
        self.SetBenchmark(self.pair)

        ################################################
        # Add Charting
        self._my_chart = Chart('Indicator')
        self._my_chart.AddSeries(Series("Cash", SeriesType.Line, 0))
        self.AddChart(self._my_chart)

        self._my_chart2 = Chart('Orders')
        self._my_chart2.AddSeries(Series("Cash", SeriesType.Bar, 0))
        self.AddChart(self._my_chart2)

        self.Schedule.On(self.DateRules.EveryDay(self.pair), self.TimeRules.AfterMarketOpen(self.pair), self.openMarket)
        self.Schedule.On(self.DateRules.EveryDay(self.pair), self.TimeRules.BeforeMarketClose(self.pair), self.closeMarket)

    def OnData(self, data):

        # invested_symbols = [symbol for symbol, holding in self.Portfolio.items() if holding.Invested]
        # if data[self.pair].Close < (self._reference_price - self._std) and self.pair not in invested_symbols:
        #     # self.SetHoldings(self.pair, 1)
        #     self._qty = self.CalculateOrderQuantity(self.pair, 1)
        #     self.MarketOrder(self.pair, self._qty, self._reference_price - self._std)
        # elif data[self.pair].Close > (self._reference_price + self._std) and self._qty != 0:
        #     self.Liquidate(self.pair)

        # self.Plot('Indicator', 'Open + STD', self._reference_price + self._std)
        # self.Plot('Indicator', 'Open - STD', self._reference_price - self._std)
        # self.Plot('Indicator', 'Close', data[self.pair].Close)
        # self.Plot('Orders', 'Orders', len([symbol for symbol, holding in self.Portfolio.items() if holding.Invested]) - self._orders)
        # self._orders = len([symbol for symbol, holding in self.Portfolio.items() if holding.Invested])
        pass

    def setParameters(self):
        # ? Should we calculate this std the next day or right after the market is closed?
        history = self.History([self.pair], 1440, self.resolution)
        history = history.droplevel(0)

        # Locate the previous market open date
        for d in reversed(pd.date_range(start=history.index[0], end=history.index[-1], freq='D')):
            if d.weekday() not in [5, 6]:
                # self.Debug(f'{d}, {d.weekday()}')
                tgt = d
                break

        his = history[(history.index.day == tgt.day) & (history.index.year == tgt.year) & (history.index.month == tgt.month)]

        # Get volatility of previous day
        self._std = his.high.max() - his.low.min()
        # Get close of previous day
        self._reference_price = his.close[-1]
        # self.Debug(f'Reference price: {self._reference_price}')

    def openMarket(self):
        # self.Debug(f'{self.Time} Market start')
        self.setParameters()

        # self._qty = self.CalculateOrderQuantity(self.pair, 0.49)
        qty = (self.Portfolio.Cash / self._grid_number) / (self._reference_price - self._std / self._grid_number)
        self.LimitOrder(
            self.pair,
            qty,
            round(self._reference_price - self._std / self._grid_number, 5),
            'Long1'
        )

        qty = (self.Portfolio.Cash / self._grid_number) / (self._reference_price - self._std)
        self.LimitOrder(
            self.pair,
            qty,
            round(self._reference_price - self._std, 5),
            'Long2'
        )

        qty = (self.Portfolio.Cash / self._grid_number) / (self._reference_price + self._std / self._grid_number)
        self.LimitOrder(
            self.pair,
            -qty,
            round(self._reference_price + self._std / self._grid_number, 5),
            'Short1'
        )

        qty = (self.Portfolio.Cash / self._grid_number) / (self._reference_price + self._std)
        self.LimitOrder(
            self.pair,
            -qty,
            round(self._reference_price + self._std, 5),
            'Short2'
        )

    def closeMarket(self):
        # self.Debug(f'{self.Time} Market close')
        # Liquidate all positions by the end of the day
        self.Liquidate(self.pair)

    def OnOrderEvent(self, orderEvent):
        order = self.Transactions.GetOrderById(orderEvent.OrderId)
        if orderEvent.Status == OrderStatus.Filled:
            self.Debug(
                "{0}: {1} ({2})".format(
                    self.Time,
                    orderEvent,
                    order.Tag
                )
            )
            if order.Tag == 'Long1':
                self.LimitOrder(
                    self.pair,
                    -order.Quantity,
                    round(self._reference_price, 5),
                    'Long1-Liquidate'
                )
            elif order.Tag == 'Long2':
                self.LimitOrder(
                    self.pair,
                    -order.Quantity,
                    round(self._reference_price - self._std / self._grid_number, 5),
                    'Long2-Liquidate'
                )
            elif order.Tag == 'Short1':
                self.LimitOrder(
                    self.pair,
                    order.Quantity,
                    round(self._reference_price, 5),
                    'Short1-Liquidate'
                )
            elif order.Tag == 'Short2':
                self.LimitOrder(
                    self.pair,
                    order.Quantity,
                    round(self._reference_price + self._std / self._grid_number, 5),
                    'Short2-Liquidate'
                )


# Others:
# if order.Type == OrderType.Limit or order.Type == OrderType.StopMarket:
#     self.Transactions.CancelOpenOrders(order.Symbol)