Overall Statistics
Total Trades
922
Average Win
4.38%
Average Loss
-1.25%
Compounding Annual Return
20.769%
Drawdown
35.700%
Expectancy
0.060
Net Profit
26.499%
Sharpe Ratio
0.582
Probabilistic Sharpe Ratio
25.956%
Loss Rate
77%
Win Rate
23%
Profit-Loss Ratio
3.51
Alpha
0.251
Beta
0.248
Annual Standard Deviation
0.409
Annual Variance
0.168
Information Ratio
0.672
Tracking Error
0.431
Treynor Ratio
0.959
Total Fees
$0.00
Estimated Strategy Capacity
$2700000.00
Lowest Capacity Asset
EURUSD 8G
Portfolio Turnover
665.63%
# region imports
from AlgorithmImports import *
# endregion

class SmoothRedDogfish(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2022, 1, 1)  # Set Start Date
        self.SetEndDate(2023, 3, 31) # Set End Date
        self.SetCash(1000)  # Set Strategy Cash
        
        self.SetBenchmark("SPY")

        #self.symbol = self.AddEquity("AAPL", Resolution.Daily).Symbol
        #self.currency_pair = self.AddForex("EURUSD", Resolution.Minute, Market.Oanda).Symbol
        self.currencies = ["EURUSD", "GBPUSD"]
        self.Resolution = Resolution.Minute
        self.Data = {}
        self.BolingerData = {}

        self.SetBrokerageModel(BrokerageName.OandaBrokerage, AccountType.Margin)

        for symbol in self.currencies:
            self.currency = self.AddForex(symbol,self.Resolution, Market.Oanda, True, 30)
            self.Data[symbol] = self.currency
            
            # Consolidate bars 
            self.consolidator = QuoteBarConsolidator(timedelta(minutes=30))
            self.consolidator.DataConsolidated += self.consolidation_handler
            self.SubscriptionManager.AddConsolidator(symbol, self.consolidator)

            self.bollinger_bands = self.BB(symbol, 20, 2, MovingAverageType.Simple, self.Resolution)
            self.RegisterIndicator(symbol, self.bollinger_bands, timedelta(minutes=30))
            self.BolingerData[symbol] = self.bollinger_bands
            

        # Schedule to trade between hour x and y 
        # create on schedile method 
        #self.Schedule.On(self.DateRules.EveryDay('GBPUSD'), self.TimeRules.At(6,0), Action(self._sessionOn))
        #self.Schedule.On(self.DateRules.EveryDay('GBPUSD'), self.TimeRules.At(21,0), Action(self._sessionOff))

        # Add end of day liquidation
        self.Schedule.On(self.DateRules.EveryDay('GBPUSD'), self.TimeRules.At(23, 58), Action(self.closePositionsEndOfDay))

        # Add end of week liquidation
        self.Schedule.On(self.DateRules.Every(DayOfWeek.Friday), self.TimeRules.At(4, 58), Action(self.closePositionsEndOfWeek))


        # Risk Management
        self.riskPercentage = 0.02
        self.stopLossTarget = 40 # Profit target in pips 
        self.profitTarget = 150 # Profit target in pip 
        self.lotSize =  0.03


        # Initilize order ticket 
        self.CurrentOrderTicket       = {symbol: None for symbol in self.currencies}
        self.StopLossTicket           = {symbol: None for symbol in self.currencies}
        self.ProfitTargetTicket       = {symbol: None for symbol in self.currencies}

        self.EURCurrentOrderTicket    = None
        self.EURStopLossTicket        = None
        self.EURProfitTargetTicket    = None 

        self.GBPCurrentOrderTicket    = None
        self.GBPStopLossTicket        = None
        self.GBPProfitTargetTicket    = None 

        # Add a chart  
        self.chart = Chart('Price and Indicators ')
        self.chart.AddSeries(Series("Close",SeriesType.Line))
        self.chart.AddSeries(Series("SMA",SeriesType.Line))
        self.AddChart(self.chart)

    def _sessionOn(self):
        self._session = True

    def _sessionOff(self):
        self._session = False

    def consolidation_handler(self, sender: object, consolidated_bar: QuoteBar) -> None:

        #self.Log(f"{consolidated.Close} at {consolidated.EndTime}")

        if not self.bollinger_bands.IsReady:
            return

        
        # Execute trade for each pair
        for symbol in self.Data.keys():

            invested = self.Portfolio[symbol].Invested
            if not invested:

                self.Debug(f'Symbol : {symbol}')
                # Forex example 
                #mean = self.moving_average.Current.Value
                pip = self.Securities[symbol].SymbolProperties.MinimumPriceVariation
                current_price = self.Securities[symbol].Close
            

                accountBalance = self.Portfolio.CashBook["USD"].Amount
                #if self.Portfolio.CashBook["USD"].Amount < 1000  :
                #    accountBalance = self.Portfolio.CashBook["USD"].Amount
                #else:
                #    accountBalance = 1000
                
                #quantity = self.positionSize(current_price, accountBalance,self.stopLossTarget, self.riskPercentage, pip)
                quantity = self.lotPositionSize(self.lotSize, pip)

                # BB bands 
                lowerBand = self.BolingerData[symbol].LowerBand.Current.Value 
                upperBand = self.BolingerData[symbol].UpperBand.Current.Value 

                self.Debug('I made it here')

                # if current price is below or equals to lower band then take long position 
                if current_price <= lowerBand:
                    self.Debug('I made it here1')

                    # Calculate stop loss and profit target 
                    stop_loss                           = current_price - (self.stopLossTarget * (pip * 10))
                    profit_target                       = current_price + (self.profitTarget * (pip * 10))

                    self.Debug('Long Position:')
                    self.Debug(f'Position Size: {quantity}')
                    self.Debug(f'Current Price: {current_price}')
                    #self.Debug(f'Pip variance: {pip*10}')
                    #self.Debug(f'Stop Loss Calc: {self.stopLossTarget * pip * 10}')
                    self.Debug(f'Stop Loss: {stop_loss}')
                    self.Debug(f'Profit Target: {profit_target}')
                    #self.Plot(self.chart.Name, "Close", current_price)
                    #self.Plot(self.chart.Name, "SMA", current_sma_value)

                    if symbol == 'EURUSD':

                        marketOrderTicket                = self.MarketOrder(symbol, quantity)
                        stopMarketOrderTicket            = self.StopMarketOrder(symbol, -quantity, stop_loss)
                        limitOrderTicket                 = self.LimitOrder(symbol, -quantity, profit_target)
                        
                        self.EURCurrentOrderTicket       = marketOrderTicket
                        self.EURStopLossTicket           = stopMarketOrderTicket
                        self.EURProfitTargetTicket       = limitOrderTicket

                        self.CurrentOrderTicket[symbol]  = marketOrderTicket
                        self.StopLossTicket[symbol]      = stopMarketOrderTicket
                        self.ProfitTargetTicket[symbol]  = limitOrderTicket
                    elif symbol == 'GBPUSD':
                        
                        marketOrderTicket                = self.MarketOrder(symbol, quantity)
                        stopMarketOrderTicket            = self.StopMarketOrder(symbol, -quantity, stop_loss)
                        limitOrderTicket                 = self.LimitOrder(symbol, -quantity, profit_target)

                        self.GBPCurrentOrderTicket       = marketOrderTicket
                        self.GBPStopLossTicket           = stopMarketOrderTicket
                        self.GBPProfitTargetTicket       = limitOrderTicket


                # if current price is above or equals to upper band then take short position
                elif current_price >= upperBand:
                    self.Debug('I made it here2')

                    # Calculate stop loss and profit target 
                    stop_loss                            = current_price + (self.stopLossTarget * (pip * 10))
                    profit_target                        = current_price - (self.profitTarget * (pip * 10))

                    self.Debug('Short Position:')
                    self.Debug(f'Position Size: {quantity}')
                    self.Debug(f'Current Price: {current_price}')
                    #self.Debug(f'Pip variance: {pip*10}')
                    #self.Debug(f'Stop Loss Calc: {self.stopLossTarget * pip * 10}')
                    self.Debug(f'Stop Loss: {stop_loss}')
                    self.Debug(f'Profit Target: {profit_target}')

                    if symbol == 'EURUSD':

                        marketOrderTicket                = self.MarketOrder(symbol, -quantity)
                        stopMarketOrderTicket            = self.StopMarketOrder(symbol, quantity, stop_loss)
                        limitOrderTicket                 = self.LimitOrder(symbol, quantity, profit_target)

                        self.EURCurrentOrderTicket       = marketOrderTicket
                        self.EURStopLossTicket           = stopMarketOrderTicket
                        self.EURProfitTargetTicket       = limitOrderTicket

                    elif symbol == 'GBPUSD':

                        marketOrderTicket                = self.MarketOrder(symbol, -quantity)
                        stopMarketOrderTicket            = self.StopMarketOrder(symbol, quantity, stop_loss)
                        limitOrderTicket                 = self.LimitOrder(symbol, quantity, profit_target)

                        self.GBPCurrentOrderTicket       = marketOrderTicket
                        self.GBPStopLossTicket           = stopMarketOrderTicket
                        self.GBPProfitTargetTicket       = limitOrderTicket

    def OnData(self, data: Slice):
        pass

    def OnOrderEvent(self, orderEvent):
        
        if orderEvent.Status != OrderStatus.Filled:
            return
        self.Debug(f"{self.Time} {orderEvent}")

        ## python doesn't support null. Instead, check for None
        if (self.EURProfitTargetTicket is None) or (self.EURStopLossTicket is None) or (self.GBPProfitTargetTicket is None) or (self.GBPStopLossTicket is None) : 
            return 

        filledOrderId = orderEvent.OrderId
        symbol = orderEvent.Symbol

        if symbol == 'EURUSD':
            self.Debug("Cancelling EURUSD Orders")
            if (self.EURProfitTargetTicket.OrderId == filledOrderId):
                
                self.EURStopLossTicket.Cancel()
            
            if (self.EURStopLossTicket.OrderId == filledOrderId):
                
                self.EURProfitTargetTicket.Cancel()
        elif symbol == 'GBPUSD':
            self.Debug("Cancelling GBPUSD Orders")
            
            if (self.GBPProfitTargetTicket.OrderId == filledOrderId):
                
                self.GBPStopLossTicket.Cancel()
            
            if (self.GBPStopLossTicket.OrderId == filledOrderId):
                
                self.GBPProfitTargetTicket.Cancel()


    # Close positions at the end of the day if in profit
    def closePositionsEndOfDay(self):
        for symbol in self.currencies:
            if self.Portfolio[symbol].Invested and self.Portfolio[symbol].UnrealizedProfitPercent > 0.02:

                unrealisedprofitpercent = self.Portfolio[symbol].UnrealizedProfitPercent
                
                self.Debug(f'Unrealised profit percent: {unrealisedprofitpercent}')

                if self.Portfolio[symbol].IsLong:
                    self.MarketOrder(symbol, -self.Portfolio[symbol].Quantity)

                    self.Debug(f"Closed {symbol} position at the end of the day")

                elif self.Portfolio[symbol].IsShort:
                    self.MarketOrder(symbol, self.Portfolio[symbol].Quantity)
                    self.Debug(f"Closed {symbol} position at the end of the day")

                
                

    # Close positions at the end of the week if in profit
    def closePositionsEndOfWeek(self):
        for symbol in self.currencies:
            if self.Portfolio[symbol].Invested and self.Portfolio[symbol].UnrealizedProfitPercent > 0.02:

                if self.Portfolio[symbol].IsLong:
                    self.MarketOrder(symbol, -self.Portfolio[symbol].Quantity)
                    self.Debug(f"Closed {symbol} position at the end of the week")
                elif self.Portfolio[symbol].IsShort:
                    self.MarketOrder(symbol, self.Portfolio[symbol].Quantity)
                    self.Debug(f"Closed {symbol} position at the end of the week")


    def positionSize(self,_price,_accountBalance, _stopLossPips, _riskPercentage, _minimumPriceVariation):

        # Debug Function Parameters 
        # Calulate position size 
        # based on risk percentage and account balance.
        ###############################################)
            
        _riskAmount      = _accountBalance * _riskPercentage
        _riskValuePerPip = _riskAmount / _stopLossPips
        _positionSize    = _riskValuePerPip / (_minimumPriceVariation * 10)
        self.Debug(f'Position Size: {_positionSize}')

        return _positionSize 
        
    def lotPositionSize(self,_lot, _minimumPriceVariation):


        # Debug Function Parameters 
        # Calulate position size using lots
        # based on risk percentage and account balance.
        ###############################################

        _positionSize    = _lot / _minimumPriceVariation

        self.Debug(f'Position Size: {_positionSize}')
        self.Debug(f'Variance Size: {_minimumPriceVariation * 10}')

        return _positionSize