text: # QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
  # Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  from AlgorithmImports import *
  
  #
  # In a perfect market, you could buy 100 EUR worth of USD, sell 100 EUR worth of GBP,
  # and then use the GBP to buy USD and wind up with the same amount in USD as you received when
  # you bought them with EUR. This relationship is expressed by the Triangle Exchange Rate, which is
  #
  #     Triangle Exchange Rate = (A/B) * (B/C) * (C/A)
  #
  # where (A/B) is the exchange rate of A-to-B. In a perfect market, TER = 1, and so when
  # there is a mispricing in the market, then TER will not be 1 and there exists an arbitrage opportunity.
  #
  # This alpha is part of the Benchmark Alpha Series created by QuantConnect which are open sourced so the community and client funds can see an example of an alpha.
  #
  
  class TriangleExchangeRateArbitrageAlpha(QCAlgorithm):
  
      def Initialize(self):
  
          self.SetStartDate(2019, 2, 1)   #Set Start Date
          self.SetCash(100000)           #Set Strategy Cash
  
          # Set zero transaction fees
          self.SetSecurityInitializer(lambda security: security.SetFeeModel(ConstantFeeModel(0)))
  
          ## Select trio of currencies to trade where
          ## Currency A = USD
          ## Currency B = EUR
          ## Currency C = GBP
          currencies = ['EURUSD','EURGBP','GBPUSD']
          symbols = [ Symbol.Create(currency, SecurityType.Forex, Market.Oanda) for currency in currencies]
  
          ## Manual universe selection with tick-resolution data
          self.UniverseSettings.Resolution = Resolution.Minute
          self.SetUniverseSelection( ManualUniverseSelectionModel(symbols) )
  
          self.SetAlpha(ForexTriangleArbitrageAlphaModel(Resolution.Minute, symbols))
  
          ## Set Equal Weighting Portfolio Construction Model
          self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
  
          ## Set Immediate Execution Model
          self.SetExecution(ImmediateExecutionModel())
  
          ## Set Null Risk Management Model
          self.SetRiskManagement(NullRiskManagementModel())
  
  
  class ForexTriangleArbitrageAlphaModel(AlphaModel):
  
      def __init__(self, insight_resolution, symbols):
          self.insight_period = Time.Multiply(Extensions.ToTimeSpan(insight_resolution), 5)
          self.symbols = symbols
  
      def Update(self, algorithm, data):
          ## Check to make sure all currency symbols are present
          if len(data.Keys) < 3:
              return []
  
          ## Extract QuoteBars for all three Forex securities
          bar_a = data[self.symbols[0]]
          bar_b = data[self.symbols[1]]
          bar_c = data[self.symbols[2]]
  
          ## Calculate the triangle exchange rate
          ## Bid(Currency A -> Currency B) * Bid(Currency B -> Currency C) * Bid(Currency C -> Currency A)
          ## If exchange rates are priced perfectly, then this yield 1. If it is different than 1, then an arbitrage opportunity exists
          triangleRate = bar_a.Ask.Close / bar_b.Bid.Close / bar_c.Ask.Close
  
          ## If the triangle rate is significantly different than 1, then emit insights
          if triangleRate > 1.0005:
              return Insight.Group(
                  [
                      Insight.Price(self.symbols[0], self.insight_period, InsightDirection.Up, 0.0001, None),
                      Insight.Price(self.symbols[1], self.insight_period, InsightDirection.Down, 0.0001, None),
                      Insight.Price(self.symbols[2], self.insight_period, InsightDirection.Up, 0.0001, None)
                  ] )
  
          return []
  