diff --git a/Common/Brokerages/InteractiveBrokersBrokerageModel.cs b/Common/Brokerages/InteractiveBrokersBrokerageModel.cs index b93d1f3bd2cd..7fa60bc6191d 100644 --- a/Common/Brokerages/InteractiveBrokersBrokerageModel.cs +++ b/Common/Brokerages/InteractiveBrokersBrokerageModel.cs @@ -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; @@ -109,6 +107,21 @@ public override IFeeModel GetFeeModel(Security security) return new InteractiveBrokersFeeModel(); } + /// + /// Gets the brokerage's leverage for the specified security + /// + /// The security's whose leverage we seek + /// The leverage for the specified security + public override decimal GetLeverage(Security security) + { + if (AccountType == AccountType.Cash) + { + return 1m; + } + + return security.Type == SecurityType.Cfd ? 10m : base.GetLeverage(security); + } + /// /// Returns true if the brokerage could accept this order. This takes into account /// order type, security type, and order size limits. @@ -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)); diff --git a/Common/Orders/Fees/InteractiveBrokersFeeModel.cs b/Common/Orders/Fees/InteractiveBrokersFeeModel.cs index a21347b48765..f538ee0fb5dc 100644 --- a/Common/Orders/Fees/InteractiveBrokersFeeModel.cs +++ b/Common/Orders/Fees/InteractiveBrokersFeeModel.cs @@ -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)); diff --git a/Tests/Common/Brokerages/InteractiveBrokersBrokerageModelTests.cs b/Tests/Common/Brokerages/InteractiveBrokersBrokerageModelTests.cs index 5eb901f05c0d..e3baded8f5b1 100644 --- a/Tests/Common/Brokerages/InteractiveBrokersBrokerageModelTests.cs +++ b/Tests/Common/Brokerages/InteractiveBrokersBrokerageModelTests.cs @@ -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 { @@ -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 GetUnsupportedOptions() { // Index option diff --git a/Tests/Common/Orders/Fees/InteractiveBrokersFeeModelTests.cs b/Tests/Common/Orders/Fees/InteractiveBrokersFeeModelTests.cs index d17252b5cf2f..f5e1d3eb7944 100644 --- a/Tests/Common/Orders/Fees/InteractiveBrokersFeeModelTests.cs +++ b/Tests/Common/Orders/Fees/InteractiveBrokersFeeModelTests.cs @@ -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; @@ -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)] @@ -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));