Overall Statistics
Total Trades
444
Average Win
3.01%
Average Loss
-3.05%
Compounding Annual Return
41.443%
Drawdown
45.800%
Expectancy
0.645
Net Profit
5052.541%
Sharpe Ratio
1.119
Probabilistic Sharpe Ratio
40.083%
Loss Rate
17%
Win Rate
83%
Profit-Loss Ratio
0.99
Alpha
0
Beta
0
Annual Standard Deviation
0.371
Annual Variance
0.138
Information Ratio
1.119
Tracking Error
0.371
Treynor Ratio
0
Total Fees
$7766.06
Estimated Strategy Capacity
$490000.00
class HABBRStrategy (QCAlgorithm):
    
    def Initialize (self):
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) 
        
        self.SetStartDate(2010, 1, 1)
        self.SetEndDate(2021, 12, 31)
        self.SetCash(100000)
        
        self.symbols = [
            # 'EURUSD','AUDCAD','NZDCHF','EURAUD','NZDCAD','AUDJPY','AUDCHF',
            # 'GBPCAD','EURJPY','EURCAD','CADCHF','GBPCHF','NZDJPY','USDCHF',
            # 'CHFJPY','AUDNZD'
            # 'AUDNZD'
            'EURUSD','AUDCHF','AUDJPY','AUDNZD','AUDUSD','CADCHF','CADJPY',
            'CHFJPY','EURAUD','EURCAD','EURCHF','EURGBP','EURJPY','EURNZD',
            'EURUSD','GBPAUD','GBPCAD','GBPCHF','GBPJPY','GBPNZD','GBPUSD',
            'NZDCAD','NZDCHF','NZDJPY','NZDUSD','USDCAD','USDCHF','USDJPY'
        ]   
        
        haPeriod  = 500
        haStdev   = 2
        rsiPeriod = 20
        
        self.SetWarmUp(max(haPeriod, rsiPeriod))
        
        self.ha = {}
        self.bb = {}
        self.rsi = {}
        self.pb = {}
        self.overSold = {}
        self.overBought = {}
        self.orders = {}
        
        # % of BB bandwidth entry above/below limit
        self.longEntry  = 0.382 
        self.longExit   = 1.0 
        self.shortEntry = 0.618
        self.shortExit  = 0.0
        
        # % loss before position closed
        self.lossLimit = -25
        
        for symbol in self.symbols:
            Symbol = self.AddForex(symbol, Resolution.Daily, Market.Oanda).Symbol
            
            self.pb[symbol] = RollingWindow[float](2)            
            self.ha[symbol] = self.HeikinAshi(Symbol)
            self.bb[symbol] = IndicatorExtensions.Of(self.BB(Symbol, haPeriod, haStdev), self.ha[symbol])
            self.bb[symbol].Updated += self.bbUpdated
            self.rsi[symbol] = self.RSI(Symbol, rsiPeriod)
            
            self.overSold[symbol]   = False
            self.overBought[symbol] = False
            
    def bbUpdated (self, sender, updated):
        self.Debug(str(sender))
        for symbol in self.symbols:
            bb = self.bb[symbol]
            self.pb[symbol].Add(bb.PercentB.Current.Value)
        
    def OnData (self, data):
        for symbol in self.symbols:
            if self.IsWarmingUp or not (
                self.ha[symbol].IsReady or 
                self.bb[symbol].IsReady or
                self.rsi[symbol].IsReady or
                self.pb[symbol].IsReady
            ): return
        
            ha     = self.ha[symbol]
            bb     = self.bb[symbol]
            
            open   = ha.Open.Current.Value
            high   = ha.High.Current.Value
            low    = ha.Low.Current.Value
            close  = ha.Close.Current.Value
            
            percB  = bb.PercentB.Current.Value
            percB1 = self.pb[symbol][1]
            
            rsi    = self.rsi[symbol].Current.Value
            
            if percB1 < self.longEntry / 2:
                self.overSold[symbol] = True          
            elif percB1 > (1 - self.shortEntry) / 2  + self.shortEntry:
                self.overBought[symbol] = True
                
            bbVal  = str(round(percB, 2))
            rsiVal = str(round(rsi, 1))
            qty    = self.CalculateOrderQuantity(symbol, 1)
                
            if not self.Portfolio[symbol].Invested:
                isLongSignal  = self.overSold[symbol] and percB > self.longEntry and rsi < 40
                isShortSignal = self.overBought[symbol] and percB < self.shortEntry and rsi > 60
                
                if isLongSignal:  
                    self.orders[symbol] = self.MarketOrder(symbol, qty, False, f'ENTER long {bbVal} {rsiVal}')
                    self.overSold[symbol] = False
                elif isShortSignal:
                    self.orders[symbol] = self.MarketOrder(symbol, -qty, False, f'ENTER short {bbVal} {rsiVal}')
                    self.overBought[symbol] = False
            else:
                isLong = self.Portfolio[symbol].IsLong
                isShort = self.Portfolio[symbol].IsShort
                direction = 'long' if isLong else 'short'
                
                entryPrice = self.orders[symbol].AverageFillPrice
                currentPrice = data[symbol].Close
                
                loss = (currentPrice - entryPrice if isLong else entryPrice - currentPrice) / entryPrice * 100
                
                if loss < self.lossLimit:
                    self.Liquidate(symbol, f'EXIT LOSS {direction} {bbVal} {loss}')
                elif (isLong and percB > self.longExit) or (isShort and percB < self.shortExit):
                    self.Liquidate(symbol, f'EXIT {direction} {bbVal}')
from datetime import timedelta
import numpy as np

class HABBRStrategy(QCAlgorithm):
    
    def Initialize(self):
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) 
        
        self.SetStartDate(2010, 1, 1)
        self.SetEndDate(2021, 12, 31)
        self.SetCash(100000)
        
        self.symbols = [
            # 'AUDCAD'
            'AUDCAD','AUDCHF','AUDJPY','AUDNZD','AUDUSD','CADCHF','CADJPY',
            'CHFJPY','EURAUD','EURCAD','EURCHF','EURGBP','EURJPY','EURNZD',
            'EURUSD','GBPAUD','GBPCAD','GBPCHF','GBPJPY','GBPNZD','GBPUSD',
            'NZDCAD','NZDCHF','NZDJPY','NZDUSD','USDCAD','USDCHF','USDJPY'
        ]   
        
        n = 500
        self.SetWarmUp(n)
        
        self.ha = {}
        self.bb = {}
        self.rsi = {}
        self.windowBBPercB = {}
        self.overSold = {}
        self.overBought = {}
        self.orders = {}
        
        # % of BB bandwidth entry above/below limit
        self.longEntry  = 0.382 
        self.longExit   = 1.0 
        self.shortEntry = 0.618
        self.shortExit  = 0.0
        
        for symbol in self.symbols:
            self.AddForex(symbol, Resolution.Daily, Market.Oanda)
            
            self.ha[symbol] = self.HeikinAshi(symbol)
            self.bb[symbol] = IndicatorExtensions.Of(self.BB(symbol, n, 2), self.ha[symbol])
            self.bb[symbol].Updated += self.bbUpdated
            self.rsi[symbol] = self.RSI(symbol, 20)
            
            self.overSold[symbol]   = False
            self.overBought[symbol] = False
            
            self.windowBBPercB[symbol]  = RollingWindow[float](2)
            
    def bbUpdated (self, sender, updated):
        for symbol in self.symbols:
            bb = self.bb[symbol]
            self.windowBBPercB[symbol].Add(bb.PercentB.Current.Value)
        
    def OnData (self, data):
        if self.IsWarmingUp: return
    
        for symbol in self.symbols:
            ha     = self.ha[symbol]
            bb     = self.bb[symbol]
            
            open   = ha.Open.Current.Value
            high   = ha.High.Current.Value
            low    = ha.Low.Current.Value
            close  = ha.Close.Current.Value
            
            percB  = bb.PercentB.Current.Value
            percB1 = self.windowBBPercB[symbol][1]
            
            rsi    = self.rsi[symbol].Current.Value
            
            self.Debug(rsi)
            
            if percB1 < self.longEntry / 2:
                
                
                self.overSold[symbol] = True          
            elif percB1 > (1 - self.shortEntry) / 2  + self.shortEntry:
                self.overBought[symbol] = True
                
            bbVal  = str(round(percB, 2))
            rsiVal = str(round(rsi, 1))
            qty    = self.CalculateOrderQuantity(symbol, 1)
                
            if not self.Portfolio[symbol].Invested:
                isLongSignal  = self.overSold[symbol] and percB > self.longEntry and rsi < 40
                isShortSignal = self.overBought[symbol] and percB < self.shortEntry and rsi > 60
                
                if isLongSignal:  
                    self.orders[symbol] = self.MarketOrder(symbol, qty, False, f'ENTER long {bbVal} {rsiVal}')
                    self.overSold[symbol] = False
                elif isShortSignal:
                    self.orders[symbol] = self.MarketOrder(symbol, -qty, False, f'ENTER short {bbVal} {rsiVal}')
                    self.overBought[symbol] = False
            else:
                isLong = self.Portfolio[symbol].IsLong
                isShort = self.Portfolio[symbol].IsShort
                direction = 'long' if isLong else 'short'
                
                entryPrice = self.orders[symbol].AverageFillPrice
                currentPrice = data[symbol].Close
                
                loss = (currentPrice - entryPrice if isLong else entryPrice - currentPrice) / entryPrice * 100
                
                if loss < -25:
                    self.Liquidate(symbol, f'EXIT LOSS {direction} {bbVal}')
                elif (isLong and percB > self.longExit) or (isShort and percB < self.shortExit):
                    self.Liquidate(symbol, f'EXIT {direction} {bbVal}')
from datetime import datetime
import decimal
import numpy as np

# Heikin Ashi Bollinger Bands Reversion (HABBR) Strategy
class HABBR (QCAlgorithm):

    def Initialize (self):
        # self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) 
        
        self.SetStartDate(2020, 10, 1)
        self.SetEndDate(2021, 12, 31)
        self.SetCash(100000)
        
        # available pairs for modelling
        # AUDCAD, AUDCHF, AUDJPY, AUDNZD, AUDUSD, CADCHF, CADJPY
        # CHFJPY, EURAUD, EURCAD, EURCHF, EURGBP, EURJPY, EURNZD
        # EURUSD, GBPAUD, GBPCAD, GBPCHF, GBPJPY, GBPNZD, GBPUSD
        # NZDCAD, NZDCHF, NZDJPY, NZDUSD, USDCAD, USDCHF, USDJPY 
        
        fx = self.AddForex('AUDCAD', Resolution.Hour, Market.Oanda)
        
        self.syl = fx.Symbol
        self.Schedule.On(self.DateRules.EveryDay(self.syl), self.TimeRules.BeforeMarketClose(self.syl, 1), Action(self.SetSignal))

        self.overBought, self.overSold = False, False
        self.SetBenchmark(self.syl)
        self.Bolband = self.BB(self.syl, 20, 2, MovingAverageType.Simple, Resolution.Daily)
    
    def OnData (self, data):
    
        close = self.History(self.syl, 1, Resolution.Daily)['close']

        self.yesterdayclose = close.iloc[-1]
        
        # wait for our BollingerBand to fully initialize
        if not self.Bolband.IsReady: return
    
        holdings = self.Portfolio[self.syl].Quantity
        
        if self.yesterdayclose > self.Bolband.UpperBand.Current.Value:
            self.overBought = True
            self.overSold = False
        elif self.yesterdayclose < self.Bolband.LowerBand.Current.Value:
            self.overSold = True
            self.overBought = False
            
        if self.overBought and self.yesterdayclose < self.Bolband.MiddleBand.Current.Value:
            self.SetHoldings(self.syl, -1)
            self.overBought = False
        elif self.overSold and self.yesterdayclose > self.Bolband.MiddleBand.Current.Value:
            self.SetHoldings(self.syl, 1)
            self.overSold = False
    
        if holdings > 0 and self.yesterdayclose < self.Bolband.MiddleBand.Current.Value:
            self.Liquidate(self.syl)
        elif holdings < 0 and self.yesterdayclose > self.Bolband.MiddleBand.Current.Value:
            self.Liquidate(self.syl)
import decimal as d
import numpy as np

class heikinAshiExample(QCAlgorithm):
    
    def Initialize(self):
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)  
        
        self.SetStartDate(2019, 1, 1)
        self.SetEndDate(2020, 12, 31)
        self.SetCash(100000)
        self.SetWarmUp(20)
        
        self.symbols = [
            'AUDCAD','AUDCHF','AUDJPY','AUDNZD','AUDUSD','CADCHF','CADJPY',
            'CHFJPY','EURAUD','EURCAD','EURCHF','EURGBP','EURJPY','EURNZD',
            'EURUSD','GBPAUD','GBPCAD','GBPCHF','GBPJPY','GBPNZD','GBPUSD',
            'NZDCAD','NZDCHF','NZDJPY','NZDUSD','USDCAD','USDCHF','USDJPY'
        ]   
        
        self.ha = {}
        self.bb = {}
        self.windowHAClose = {}
        self.windowBBUpper = {}
        self.windowBBMiddle = {}
        self.windowBBLower = {}
        self.overSold = {}
        self.overBought = {}
        self.consolidators = {}
        
        # % of BB bandwitdh for stop loss/entry above/below limit
        # self.longStopLoss  = -0.20
        self.longEntry     = 0.4 
        self.longExit      = 1.0 
        
        # self.shortStopLoss = 1.20
        self.shortEntry    = 0.6
        self.shortExit     = 0.0
        
        for symbol in self.symbols:
            
            Symbol = self.AddForex(symbol, Resolution.Hour, Market.Oanda).Symbol
            
            self.ha[symbol] = self.HeikinAshi(Symbol, Resolution.Daily)
            self.bb[symbol] = IndicatorExtensions.Of(
                self.BB(Symbol, 20, 2, MovingAverageType.Simple, Resolution.Daily), 
                self.ha[symbol]
            )
            self.overSold[symbol] = False
            self.overBought[symbol] = False
            
            self.ha[symbol].Updated += self.haUpdated
            self.bb[symbol].Updated += self.bbUpdated
            
            self.windowHAClose[symbol]  = RollingWindow[float](3)
            self.windowBBUpper[symbol]  = RollingWindow[float](3)
            self.windowBBMiddle[symbol] = RollingWindow[float](3)
            self.windowBBLower[symbol]  = RollingWindow[float](3)
            
        ## Oanda Forex opens 17:00 Sunday, closes Friday 17:00
        self.Schedule.On(self.DateRules.On(2019, 1, 1), self.TimeRules.At(17, 0), self.DailyConsolidator)
            
    # ==================================================================================================================        
        
    def DailyConsolidator (self):
        for symbol in self.symbols:
            self.consolidators[symbol] = QuoteBarConsolidator(24)  # 24 hours | 1440 minutes | 86400 seconds
            self.consolidators[symbol].DataConsolidated += self.OnDailyData            
            self.SubscriptionManager.AddConsolidator(symbol, self.consolidators[symbol])  
        
    # ==================================================================================================================         
            
    def haUpdated (self, sender, updated):
        for symbol in self.symbols:
            ha = self.ha[symbol]
            self.windowHAClose[symbol].Add(ha.Close.Current.Value)
            
    # ==================================================================================================================
            
    def bbUpdated (self, sender, updated):
        for symbol in self.symbols:
            bb = self.bb[symbol]
            self.windowBBUpper[symbol].Add(bb.UpperBand.Current.Value)
            self.windowBBMiddle[symbol].Add(bb.MiddleBand.Current.Value)
            self.windowBBLower[symbol].Add(bb.LowerBand.Current.Value)
            
    # ==================================================================================================================
        
    def OnDailyData (self, sender, consolidated):
        if self.IsWarmingUp: return
    
        for symbol in self.symbols:
            open   = self.ha[symbol].Open.Current.Value
            high   = self.ha[symbol].High.Current.Value
            low    = self.ha[symbol].Low.Current.Value
            close  = self.ha[symbol].Close.Current.Value  
            close1 = self.windowHAClose[symbol][1]
            close2 = self.windowHAClose[symbol][2]
            
            upper  = self.bb[symbol].UpperBand.Current.Value
            upper1 = self.windowBBUpper[symbol][1]
            upper2 = self.windowBBUpper[symbol][2]
            middle = self.bb[symbol].MiddleBand.Current.Value
            lower  = self.bb[symbol].LowerBand.Current.Value
            lower1 = self.windowBBLower[symbol][1]  
            lower2 = self.windowBBLower[symbol][2]
            
            bbPercentBandwidth  = (close - lower) / (upper - lower)
            bbPercentBandwidth1 = (close1 - lower1) / (upper1 - lower1)
            bbPercentBandwidth2 = (close2 - lower2) / (upper2 - lower2)
            
            # two preceding bars close outside BB range
            if bbPercentBandwidth2 < 0 and bbPercentBandwidth1 < self.longEntry / 2 and bbPercentBandwidth < self.longEntry:
                self.overSold[symbol] = True          
            elif bbPercentBandwidth2 > 1 and bbPercentBandwidth1 > (1 - self.shortEntry) / 2  + self.shortEntry and bbPercentBandwidth > self.shortEntry:
                self.overBought[symbol] = True
                
            if not self.Portfolio[symbol].Invested:
                qty           = self.Portfolio.TotalPortfolioValue
                # longStopLoss  = round(lower + lower * self.longStopLoss, 3)
                # shortStopLoss = round(lower + lower * self.shortStopLoss, 3)
                
                # LONG entry
                if self.overSold[symbol] and bbPercentBandwidth > self.longEntry:     
                    self.MarketOrder(symbol, qty, False, 'ENTER long')
                    # self.StopMarketOrder(symbol, -qty, longStopLoss, 'EXIT stop loss')
                    self.overSold[symbol] = False
                # SHORT entry
                elif self.overBought[symbol] and bbPercentBandwidth < self.shortEntry: 
                    self.MarketOrder(symbol, -qty, False, 'ENTER short')
                    # self.StopMarketOrder(symbol, qty, shortStopLoss, 'EXIT stop loss')
                    self.overBought[symbol] = False 
            else:
                is_long  = self.Portfolio[symbol].IsLong
                is_short = self.Portfolio[symbol].IsShort
                if is_long and bbPercentBandwidth > self.longExit:
                    self.Liquidate(symbol, 'EXIT long')
                    # self.Transactions.CancelOpenOrders(symbol, 'Cancel limits/stop losses')
                elif is_short and bbPercentBandwidth < self.shortExit:
                    self.Liquidate(symbol, 'EXIT short')
                    # self.Transactions.CancelOpenOrders(symbol, 'Cancel limits/stop losses')
                    
    # ==================================================================================================================
    
    def OnData (self, data):
        pass
        
    # ==================================================================================================================
    
    def OnOrderEvent (self, OrderEvent):
        
        order = self.Transactions.GetOrderById(OrderEvent.OrderId)
        symbol = OrderEvent.Symbol        
        
        # self.Debug(f'{OrderEvent}')
            
        # if OrderEvent.Status == OrderStatus.Filled:
            
        #     if order.Type == OrderType.Limit:
        #         self.Transactions.CancelOpenOrders(symbol, 'Cancel stop loss')
                
        #     if order.Type == OrderType.StopMarket:
        #         self.Transactions.CancelOpenOrders(symbol, 'Cancel limit')
                
   # ==================================================================================================================
import decimal as d
import numpy as np

class heikinAshiExample(QCAlgorithm):
    
    def Initialize(self):
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)  
        
        self.SetStartDate(2017, 1, 1)
        self.SetEndDate(2018, 12, 31)
        self.SetCash(100000)
        self.SetWarmUp(20)
        
        self.symbols = [
            'EURNZD'
            # 'AUDCAD','AUDCHF','AUDJPY','AUDNZD','AUDUSD','CADCHF','CADJPY'
            # 'CHFJPY','EURAUD','EURCAD','EURCHF','EURGBP','EURJPY','EURNZD'
            # 'EURUSD','GBPAUD','GBPCAD','GBPCHF','GBPJPY','GBPNZD','GBPUSD'
            # 'NZDCAD','NZDCHF','NZDJPY','NZDUSD','USDCAD','USDCHF','USDJPY'
        ]   
        
        self.ha = {}
        self.bb = {}
        self.windowHAClose = {}
        self.windowBBUpper = {}
        self.windowBBMiddle = {}
        self.windowBBLower = {}
        self.overSold = {}
        self.overBought = {}
        
        # % of BB bandwidth entry above/below limit
        self.longEntry     = 0.4 
        self.longExit      = 1.0 
        
        self.shortEntry    = 0.6
        self.shortExit     = 0.0
        
        for symbol in self.symbols:
            Symbol = self.AddForex(symbol, Resolution.Daily, Market.Oanda).Symbol
            
            self.ha[symbol] = self.HeikinAshi(Symbol, Resolution.Daily)
            self.bb[symbol] = IndicatorExtensions.Of(
                self.BB(Symbol, 20, 2, MovingAverageType.Simple, Resolution.Daily), 
                self.ha[symbol]
            )
            self.overSold[symbol] = False
            self.overBought[symbol] = False
            
            self.ha[symbol].Updated += self.haUpdated
            
            self.windowHAClose[symbol]  = RollingWindow[float](3)
            self.windowBBUpper[symbol]  = RollingWindow[float](3)
            self.windowBBMiddle[symbol] = RollingWindow[float](3)
            self.windowBBLower[symbol]  = RollingWindow[float](3)
            
    def haUpdated (self, sender, updated):
        for symbol in self.symbols:
            ha     = self.ha[symbol]
            bb     = self.bb[symbol]
            self.windowHAClose[symbol].Add(ha.Close.Current.Value)   
            self.windowBBUpper[symbol].Add(bb.UpperBand.Current.Value)
            self.windowBBMiddle[symbol].Add(bb.MiddleBand.Current.Value)
            self.windowBBLower[symbol].Add(bb.LowerBand.Current.Value)        
        
    def OnData (self, data):
        if self.IsWarmingUp: return
    
        for symbol in self.symbols:
            open   = self.ha[symbol].Open.Current.Value
            high   = self.ha[symbol].High.Current.Value
            low    = self.ha[symbol].Low.Current.Value
            close  = self.ha[symbol].Close.Current.Value  
            close1 = self.windowHAClose[symbol][1]
            close2 = self.windowHAClose[symbol][2]
            
            upper  = self.bb[symbol].UpperBand.Current.Value
            upper1 = self.windowBBUpper[symbol][1]
            upper2 = self.windowBBUpper[symbol][2]
            middle = self.bb[symbol].MiddleBand.Current.Value
            lower  = self.bb[symbol].LowerBand.Current.Value
            lower1 = self.windowBBLower[symbol][1]  
            lower2 = self.windowBBLower[symbol][2]
            
            bbPercentBandwidth  = (close - lower) / (upper - lower)
            bbPercentBandwidth1 = (close1 - lower1) / (upper1 - lower1)
            bbPercentBandwidth2 = (close2 - lower2) / (upper2 - lower2)
            
            if bbPercentBandwidth2 < 0 and bbPercentBandwidth1 < self.longEntry / 2 and bbPercentBandwidth < self.longEntry:
                self.overSold[symbol] = True          
            elif bbPercentBandwidth2 > 1 and bbPercentBandwidth1 > (1 - self.shortEntry) / 2  + self.shortEntry and bbPercentBandwidth > self.shortEntry:
                self.overBought[symbol] = True
                
            if not self.Portfolio[symbol].Invested:
                # LONG entry
                if self.overSold[symbol] and bbPercentBandwidth > self.longEntry:     
                    self.SetHoldings(symbol, 1)
                    # self.MarketOrder(symbol, qty, False, 'ENTER long')
                    self.overSold[symbol] = False
                # SHORT entry
                elif self.overBought[symbol] and bbPercentBandwidth < self.shortEntry: 
                    self.SetHoldings(symbol, -1)
                    # self.MarketOrder(symbol, -qty, False, 'ENTER short')
                    self.overBought[symbol] = False 
            else:
                if self.Portfolio[symbol].IsLong and (bbPercentBandwidth > self.longExit):
                    self.Liquidate(symbol, 'EXIT long')
                elif self.Portfolio[symbol].IsShort and (bbPercentBandwidth < self.shortExit):
                    self.Liquidate(symbol, 'EXIT short')
from datetime import timedelta
import numpy as np

class HABBRStrategy(QCAlgorithm):
    
    def Initialize(self):
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)  
        
        self.SetStartDate(2021, 1, 1)
        self.SetEndDate(2021, 12, 31)
        self.SetCash(100000)
        self.SetWarmUp(125)
        
        self.symbols = [
            # 'CADJPY'
            'AUDCAD','AUDCHF','AUDJPY','AUDNZD','AUDUSD','CADCHF','CADJPY',
            'CHFJPY','EURAUD','EURCAD','EURCHF','EURGBP','EURJPY','EURNZD',
            'EURUSD','GBPAUD','GBPCAD','GBPCHF','GBPJPY','GBPNZD','GBPUSD',
            'NZDCAD','NZDCHF','NZDJPY','NZDUSD','USDCAD','USDCHF','USDJPY'
        ]   
        
        self.ha = {}
        self.bb = {}
        self.windowHAClose = {}
        self.windowBBUpper = {}
        self.windowBBLower = {}
        self.overSold = {}
        self.overBought = {}
        self.orders = {}
        
        # % of BB bandwidth entry above/below limit
        self.longEntry  = 0.382 
        self.longExit   = 1.0 
        self.shortEntry = 0.618
        self.shortExit  = 0.0
        
        for symbol in self.symbols:
            self.AddForex(symbol, Resolution.Daily, Market.Oanda)
            
            self.ha[symbol] = self.HeikinAshi(symbol)
            self.ha[symbol].Updated += self.haUpdated
            self.bb[symbol] = IndicatorExtensions.Of(self.BB(symbol, 125, 2), self.ha[symbol])
            
            self.overSold[symbol]   = False
            self.overBought[symbol] = False
            
            self.windowHAClose[symbol]  = RollingWindow[float](3)
            self.windowBBUpper[symbol]  = RollingWindow[float](3)
            self.windowBBLower[symbol]  = RollingWindow[float](3)
            
    def haUpdated (self, sender, updated):
        for symbol in self.symbols:
            ha     = self.ha[symbol]
            bb     = self.bb[symbol]
            self.windowHAClose[symbol].Add(ha.Close.Current.Value)   
            self.windowBBUpper[symbol].Add(bb.UpperBand.Current.Value)
            self.windowBBLower[symbol].Add(bb.LowerBand.Current.Value)        
        
    def OnData (self, data):
        if self.IsWarmingUp: return
    
        for symbol in self.symbols:
            ha     = self.ha[symbol]
            bb     = self.bb[symbol] 
            
            open   = ha.Open.Current.Value
            high   = ha.High.Current.Value
            low    = ha.Low.Current.Value
            close  = ha.Close.Current.Value  
            upper  = bb.UpperBand.Current.Value
            lower  = bb.LowerBand.Current.Value 
            
            close1 = self.windowHAClose[symbol][1]
            upper1 = self.windowBBUpper[symbol][1]
            lower1 = self.windowBBLower[symbol][1]
            
            bbPercentBandwidth  = (close - lower) / (upper - lower)
            bbPercentBandwidth1 = (close1 - lower1) / (upper1 - lower1)
            
            if bbPercentBandwidth1 < self.longEntry / 2 and bbPercentBandwidth < self.longEntry:
                self.overSold[symbol] = True          
            elif bbPercentBandwidth1 > (1 - self.shortEntry) / 2  + self.shortEntry and bbPercentBandwidth > self.shortEntry:
                self.overBought[symbol] = True
                
            bbVal = str(round(bbPercentBandwidth, 2))
            qty = self.CalculateOrderQuantity(symbol, 0.2)
                
            if not self.Portfolio[symbol].Invested:
                if self.overSold[symbol] and bbPercentBandwidth > self.longEntry:      # LONG entry   
                    self.orders[symbol]     = self.MarketOrder(symbol, qty, False, f'ENTER long {bbVal}')
                    self.overSold[symbol]   = False
                elif self.overBought[symbol] and bbPercentBandwidth < self.shortEntry: # SHORT entry
                    self.orders[symbol]     = self.MarketOrder(symbol, -qty, False, f'ENTER short {bbVal}')
                    self.overBought[symbol] = False
            else:
                isLong  = self.Portfolio[symbol].IsLong
                isShort = self.Portfolio[symbol].IsShort
                entryPrice   = self.orders[symbol].AverageFillPrice
                currentPrice = data[symbol].Close
                
                if isLong and bbPercentBandwidth > self.longExit:
                    self.Liquidate(symbol, f'EXIT long {bbVal}')
                elif isShort and bbPercentBandwidth < self.shortExit:
                    self.Liquidate(symbol, f'EXIT short {bbVal}')