Skip to content

Commit

Permalink
Support CFDs in IB
Browse files Browse the repository at this point in the history
  • Loading branch information
jhonabreul committed Mar 13, 2024
1 parent b89d7d6 commit b6d2bf8
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 10 deletions.
20 changes: 17 additions & 3 deletions Common/Brokerages/InteractiveBrokersBrokerageModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
using System.Linq;
using QuantConnect.Util;
using QuantConnect.Benchmarks;
using QuantConnect.Data.Shortable;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.TimeInForces;
Expand Down Expand Up @@ -109,6 +107,21 @@ public override IFeeModel GetFeeModel(Security security)
return new InteractiveBrokersFeeModel();
}

/// <summary>
/// Gets the brokerage's leverage for the specified security
/// </summary>
/// <param name="security">The security's whose leverage we seek</param>
/// <returns>The leverage for the specified security</returns>
public override decimal GetLeverage(Security security)
{
if (AccountType == AccountType.Cash)
{
return 1m;
}

return security.Type == SecurityType.Cfd ? 10m : base.GetLeverage(security);
}

/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
Expand Down Expand Up @@ -147,7 +160,8 @@ public override bool CanSubmitOrder(Security security, Order order, out Brokerag
security.Type != SecurityType.Future &&
security.Type != SecurityType.FutureOption &&
security.Type != SecurityType.Index &&
security.Type != SecurityType.IndexOption)
security.Type != SecurityType.IndexOption &&
security.Type != SecurityType.Cfd)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
Expand Down
6 changes: 6 additions & 0 deletions Common/Orders/Fees/InteractiveBrokersFeeModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ public override OrderFee GetOrderFee(OrderFeeParameters parameters)
feeResult = Math.Abs(tradeFee);
break;

case SecurityType.Cfd:
var value = Math.Abs(order.GetValue(security));
feeResult = Math.Max(0.00002m * value, 1); // 0.002% or 1USD minimum
feeCurrency = security.QuoteCurrency.Symbol;
break;

default:
// unsupported security type
throw new ArgumentException(Messages.FeeModel.UnsupportedSecurityType(security));
Expand Down
36 changes: 34 additions & 2 deletions Tests/Common/Brokerages/InteractiveBrokersBrokerageModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@
using QuantConnect.Data;
using QuantConnect.Securities.Option;
using QuantConnect.Securities.Forex;
using QuantConnect.Securities.IndexOption;
using QuantConnect.Tests.Engine.DataFeeds;
using QuantConnect.Securities.FutureOption;
using QuantConnect.Securities.Cfd;

namespace QuantConnect.Tests.Common.Brokerages
{
Expand Down Expand Up @@ -113,6 +112,39 @@ public void CannotSubmitMOCOrdersForOptions(string ticker, SecurityType security
Assert.AreEqual(expectedMessage, message.Message);
}

[TestCase(AccountType.Cash, 1)]
[TestCase(AccountType.Margin, 10)]
public void GetsCorrectLeverageForCfds(AccountType accounType, decimal expectedLeverage)
{
var brokerageModel = new InteractiveBrokersBrokerageModel(accounType);
var security = new Cfd(Symbols.DE10YBEUR,
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
new Cash("USD", 0, 0),
SymbolProperties.GetDefault("USD"),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache());

Assert.AreEqual(expectedLeverage, brokerageModel.GetLeverage(security));
}

[Test]
public void CanSubmitCfdOrder()
{
var security = new Cfd(Symbols.DE10YBEUR,
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
new Cash("USD", 0, 0),
SymbolProperties.GetDefault("USD"),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache());
var order = new MarketOrder(security.Symbol, 1, new DateTime(2023, 1, 20));

var canSubmit = _interactiveBrokersBrokerageModel.CanSubmitOrder(security, order, out var message);

Assert.IsTrue(canSubmit);
}

private static List<Security> GetUnsupportedOptions()
{
// Index option
Expand Down
37 changes: 32 additions & 5 deletions Tests/Common/Orders/Fees/InteractiveBrokersFeeModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using QuantConnect.Securities.Cfd;
using QuantConnect.Securities.Crypto;
using QuantConnect.Securities.Forex;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.FutureOption;
Expand Down Expand Up @@ -99,6 +100,30 @@ public void USAFutureFee(Symbol symbol, decimal expectedFee)
Assert.AreEqual(1000 * expectedFee, fee.Value.Amount);
}

[TestCase(10000, 1)] // The calculated fee will be under 1, but the minimum fee is 1
[TestCase(70000, 0.00002 * 70000)]
[TestCase(100000, 0.00002 * 100000)]
public void CalculatesCFDFee(decimal price, decimal expectedFee)
{
var security = new Cfd(Symbols.DE10YBEUR,
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
new Cash("USD", 0, 0),
SymbolProperties.GetDefault("USD"),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache());
security.QuoteCurrency.ConversionRate = 1;


security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, price, price));

var order = new MarketOrder(security.Symbol, 1, DateTime.UtcNow);
var fee = _feeModel.GetOrderFee(new OrderFeeParameters(security, order));

Assert.AreEqual(Currencies.USD, fee.Value.Currency);
Assert.AreEqual(expectedFee, fee.Value.Amount);
}

[TestCase(OrderType.ComboMarket, 0.01, 250)]
[TestCase(OrderType.ComboLimit, 0.01, 250)]
[TestCase(OrderType.ComboLegLimit, 0.01, 250)]
Expand Down Expand Up @@ -256,13 +281,15 @@ public void GetOrderFeeThrowsForUnsupportedSecurityType()
() =>
{
var tz = TimeZones.NewYork;
var security = new Cfd(
var security = new Crypto(
Symbols.BTCUSD,
SecurityExchangeHours.AlwaysOpen(tz),
new Cash("EUR", 0, 0),
new SubscriptionDataConfig(typeof(QuoteBar), Symbols.DE30EUR, Resolution.Minute, tz, tz, true, false, false),
new SymbolProperties("DE30EUR", "EUR", 1, 0.01m, 1m, string.Empty),
new Cash("USD", 0, 0),
new Cash("BTC", 0, 0),
SymbolProperties.GetDefault("USD"),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, 12000, 12000));

Expand Down

0 comments on commit b6d2bf8

Please sign in to comment.