/*
* 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.
*/
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Algorithm.Framework.Execution;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Algorithm.Framework.Risk;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Data;
using QuantConnect.Orders.Fees;
using System;
using System.Collections.Generic;
using System.Linq;
namespace QuantConnect.Algorithm.CSharp.Alphas
{
///
/// 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.
///
public class TriangleExchangeRateArbitrageAlpha : QCAlgorithm
{
public override void Initialize()
{
SetStartDate(2019, 2, 1);
SetCash(100000);
// Set zero transaction fees
SetSecurityInitializer(security => security.FeeModel = new ConstantFeeModel(0));
// Select trio of currencies to trade where
// Currency A = USD
// Currency B = EUR
// Currency C = GBP
var symbols = new[] { "EURUSD", "EURGBP", "GBPUSD" }
.Select(x => QuantConnect.Symbol.Create(x, SecurityType.Forex, Market.Oanda));
// Set requested data resolution
UniverseSettings.Resolution = Resolution.Minute;
SetUniverseSelection(new ManualUniverseSelectionModel(symbols));
// Use ForexTriangleArbitrageAlphaModel to establish insights
SetAlpha(new ForexTriangleArbitrageAlphaModel(symbols, Resolution.Minute));
// Equally weigh securities in portfolio, based on insights
SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
// Set Immediate Execution Model
SetExecution(new ImmediateExecutionModel());
// Set Null Risk Management Model
SetRiskManagement(new NullRiskManagementModel());
}
private class ForexTriangleArbitrageAlphaModel : AlphaModel
{
private readonly Symbol[] _symbols;
private readonly TimeSpan _insightPeriod;
public ForexTriangleArbitrageAlphaModel(
IEnumerable symbols,
Resolution resolution = Resolution.Minute)
{
_symbols = symbols.ToArray();
_insightPeriod = resolution.ToTimeSpan().Multiply(5);
}
public override IEnumerable Update(QCAlgorithm algorithm, Slice data)
{
// Check to make sure all currency symbols are present
if (data.QuoteBars.Count < 3)
{
return Enumerable.Empty();
}
// Extract QuoteBars for all three Forex securities
var barA = data[_symbols[0]];
var barB = data[_symbols[1]];
var barC = data[_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
var triangleRate = barA.Ask.Close / barB.Bid.Close / barC.Ask.Close;
// If the triangle rate is significantly different than 1, then emit insights
if (triangleRate > 1.0005m)
{
return Insight.Group(new[]
{
Insight.Price(_symbols[0], _insightPeriod, InsightDirection.Up, 0.0001),
Insight.Price(_symbols[1], _insightPeriod, InsightDirection.Down, 0.0001),
Insight.Price(_symbols[2], _insightPeriod, InsightDirection.Up, 0.0001)
});
}
return Enumerable.Empty();
}
}
}
}