diff --git a/Algorithm.CSharp/IGLiveTestAlgorithm.cs b/Algorithm.CSharp/IGLiveTestAlgorithm.cs new file mode 100644 index 000000000000..723299f5d96d --- /dev/null +++ b/Algorithm.CSharp/IGLiveTestAlgorithm.cs @@ -0,0 +1,61 @@ +/* + * Simple live test algorithm for IG Markets brokerage + * Subscribes to EURUSD, logs market data, and places a small test trade + */ + +using System; +using QuantConnect.Data; +using QuantConnect.Orders; + +namespace QuantConnect.Algorithm.CSharp +{ + public class IGLiveTestAlgorithm : QCAlgorithm + { + private Symbol _eurusd; + private bool _tradePlaced; + private int _dataPoints; + + public override void Initialize() + { + SetStartDate(DateTime.UtcNow.Date); + SetCash(100000); + + SetBrokerageModel(Brokerages.BrokerageName.IG, AccountType.Margin); + + // Subscribe to EURUSD forex pair on IG market + _eurusd = AddForex("EURUSD", Resolution.Second, Market.IG).Symbol; + + Log("IGLiveTestAlgorithm: Initialized - subscribing to EURUSD on IG Markets"); + } + + public override void OnData(Slice data) + { + _dataPoints++; + + if (data.QuoteBars.ContainsKey(_eurusd)) + { + var bar = data.QuoteBars[_eurusd]; + Log($"IGLiveTestAlgorithm: EURUSD Bid={bar.Bid.Close} Ask={bar.Ask.Close} Time={bar.EndTime}"); + } + + // Place a small market order after receiving some data + if (!_tradePlaced && _dataPoints >= 3) + { + Log("IGLiveTestAlgorithm: Placing test market order - Buy 1000 EURUSD"); + var ticket = MarketOrder(_eurusd, 1000); + _tradePlaced = true; + Log($"IGLiveTestAlgorithm: Order placed - Ticket ID: {ticket.OrderId}"); + } + } + + public override void OnOrderEvent(OrderEvent orderEvent) + { + Log($"IGLiveTestAlgorithm: OrderEvent - {orderEvent}"); + } + + public override void OnEndOfAlgorithm() + { + Log($"IGLiveTestAlgorithm: Algorithm ended. Total data points received: {_dataPoints}"); + } + } +} diff --git a/Common/Brokerages/BrokerageName.cs b/Common/Brokerages/BrokerageName.cs index 7f6edaab7a5e..66f40f65f358 100644 --- a/Common/Brokerages/BrokerageName.cs +++ b/Common/Brokerages/BrokerageName.cs @@ -197,6 +197,11 @@ public enum BrokerageName /// /// Transaction and submit/execution rules will use dYdX models /// - DYDX + DYDX, + + /// + /// Transaction and submit/execution rules will use IG Markets models + /// + IG } } diff --git a/Common/Brokerages/IBrokerageModel.cs b/Common/Brokerages/IBrokerageModel.cs index 4544c388d8c1..ab261a236484 100644 --- a/Common/Brokerages/IBrokerageModel.cs +++ b/Common/Brokerages/IBrokerageModel.cs @@ -291,6 +291,9 @@ public static IBrokerageModel Create(IOrderProvider orderProvider, BrokerageName case BrokerageName.DYDX: return new dYdXBrokerageModel(accountType); + case BrokerageName.IG: + return new IGBrokerageModel(accountType); + default: throw new ArgumentOutOfRangeException(nameof(brokerage), brokerage, null); } diff --git a/Common/Brokerages/IGBrokerageModel.cs b/Common/Brokerages/IGBrokerageModel.cs new file mode 100644 index 000000000000..42d30e6e2e80 --- /dev/null +++ b/Common/Brokerages/IGBrokerageModel.cs @@ -0,0 +1,191 @@ +/* + * 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 System; +using System.Collections.Generic; +using QuantConnect.Benchmarks; +using QuantConnect.Orders; +using QuantConnect.Orders.Fees; +using QuantConnect.Orders.Fills; +using QuantConnect.Orders.Slippage; +using QuantConnect.Securities; +using QuantConnect.Util; + +namespace QuantConnect.Brokerages +{ + /// + /// Provides IG Markets brokerage model specific properties and methods + /// + /// + /// IG Markets supports trading in Forex, Indices, Commodities, Crypto, and Share CFDs. + /// This model configures the appropriate fee models, fill models, and order validation + /// for IG Markets trading. + /// + public class IGBrokerageModel : DefaultBrokerageModel + { + /// + /// The default markets for IG brokerage + /// + public new static readonly IReadOnlyDictionary DefaultMarketMap = new Dictionary + { + { SecurityType.Forex, Market.IG }, + { SecurityType.Cfd, Market.IG }, + { SecurityType.Crypto, Market.IG }, + { SecurityType.Index, Market.IG }, + { SecurityType.Equity, Market.IG } + }.ToReadOnlyDictionary(); + + /// + /// Gets a map of the default markets to be used for each security type + /// + public override IReadOnlyDictionary DefaultMarkets => DefaultMarketMap; + + /// + /// Initializes a new instance of the class + /// + /// The type of account to be modelled, defaults to Margin + public IGBrokerageModel(AccountType accountType = AccountType.Margin) + : base(accountType) + { + } + + /// + /// Returns true if the brokerage could accept this order. + /// + /// The security being ordered + /// The order to be processed + /// If this function returns false, a brokerage message detailing why the order may not be submitted + /// True if the brokerage could accept the order, false otherwise + public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message) + { + message = null; + + // Validate security type + if (!IsSecurityTypeSupported(security.Type)) + { + message = new BrokerageMessageEvent( + BrokerageMessageType.Warning, + "UnsupportedSecurityType", + $"IG does not support {security.Type} security type." + ); + return false; + } + + // Validate order type + if (!IsOrderTypeSupported(order.Type)) + { + message = new BrokerageMessageEvent( + BrokerageMessageType.Warning, + "UnsupportedOrderType", + $"IG does not support {order.Type} order type." + ); + return false; + } + + return base.CanSubmitOrder(security, order, out message); + } + + /// + /// Returns true if the brokerage would allow updating the order as specified by the request + /// + /// The security of the order + /// The order to be updated + /// The requested update to be made to the order + /// If this function returns false, a brokerage message detailing why the order may not be updated + /// True if the brokerage would allow updating the order, false otherwise + public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message) + { + message = null; + + // IG supports updating working orders (limit, stop) + if (order.Type == OrderType.Market) + { + message = new BrokerageMessageEvent( + BrokerageMessageType.Warning, + "OrderUpdateNotSupported", + "IG does not support updating market orders." + ); + return false; + } + + return base.CanUpdateOrder(security, order, request, out message); + } + + /// + /// Gets a new fill model for the specified security + /// + /// The security to get fill model for + /// The fill model for this brokerage + public override IFillModel GetFillModel(Security security) + { + return new ImmediateFillModel(); + } + + /// + /// Gets the fee model for the specified security + /// + /// The security to get fee model for + /// The fee model for this brokerage + public override IFeeModel GetFeeModel(Security security) + { + return new IGFeeModel(); + } + + /// + /// Gets the slippage model for the specified security + /// + /// The security to get slippage model for + /// The slippage model for this brokerage + public override ISlippageModel GetSlippageModel(Security security) + { + return new ConstantSlippageModel(0m); + } + + /// + /// Gets the benchmark for the specified algorithm + /// + /// The securities for the benchmark + /// The benchmark for this brokerage + public override IBenchmark GetBenchmark(SecurityManager securities) + { + // Use SPY as default benchmark + var symbol = Symbol.Create("SPY", SecurityType.Equity, Market.USA); + return SecurityBenchmark.CreateInstance(securities, symbol); + } + + /// + /// Determines if the specified security type is supported by IG + /// + private static bool IsSecurityTypeSupported(SecurityType securityType) + { + return securityType == SecurityType.Forex || + securityType == SecurityType.Cfd || + securityType == SecurityType.Crypto || + securityType == SecurityType.Index || + securityType == SecurityType.Equity; + } + + /// + /// Determines if the specified order type is supported by IG + /// + private static bool IsOrderTypeSupported(OrderType orderType) + { + return orderType == OrderType.Market || + orderType == OrderType.Limit || + orderType == OrderType.StopMarket || + orderType == OrderType.StopLimit; + } + } +} diff --git a/Common/Market.cs b/Common/Market.cs index e4745023ce2f..f16bf34cd01c 100644 --- a/Common/Market.cs +++ b/Common/Market.cs @@ -71,7 +71,8 @@ public static class Market Tuple.Create(InteractiveBrokers, 39), Tuple.Create(EUREX, 40), Tuple.Create(OSE, 41), - Tuple.Create(DYDX, 42) + Tuple.Create(DYDX, 42), + Tuple.Create(IG, 43) }; static Market() @@ -104,6 +105,11 @@ static Market() /// public const string Dukascopy = "dukascopy"; + /// + /// IG Markets + /// + public const string IG = "ig"; + /// /// Bitfinex market /// diff --git a/Common/Orders/Fees/IGFeeModel.cs b/Common/Orders/Fees/IGFeeModel.cs new file mode 100644 index 000000000000..bfe2bd8a46bd --- /dev/null +++ b/Common/Orders/Fees/IGFeeModel.cs @@ -0,0 +1,117 @@ +/* + * 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 System; +using QuantConnect.Securities; + +namespace QuantConnect.Orders.Fees +{ + /// + /// Provides an implementation of that models IG Markets order fees + /// + /// + /// IG Markets Fee Structure: + /// - Forex: Spread-based pricing (no commission) + /// - CFD/Index/Equity: 0.1% commission with £10 minimum + /// - Crypto: Spread-based pricing (no commission) + /// + /// Note: Fees are charged in GBP regardless of the traded instrument currency + /// + public class IGFeeModel : FeeModel + { + private const decimal CommissionRate = 0.001m; // 0.1% + private const decimal MinimumCommission = 10m; // £10 GBP + private const string FeeCurrency = "GBP"; + + /// + /// Get the fee for this order in units of the account currency + /// + /// A object + /// containing the security and order + /// The cost of the order in units of the account currency + public override OrderFee GetOrderFee(OrderFeeParameters parameters) + { + if (parameters?.Order == null || parameters.Security == null) + { + return OrderFee.Zero; + } + + var order = parameters.Order; + var security = parameters.Security; + + // Calculate absolute order value + var fillPrice = order.Price; + var fillQuantity = order.AbsoluteQuantity; + var orderValue = Math.Abs(fillPrice * fillQuantity); + + decimal feeAmount = 0m; + + switch (security.Type) + { + case SecurityType.Forex: + // Forex: Spread-based pricing, no commission + // The spread cost is already included in the execution price + return OrderFee.Zero; + + case SecurityType.Index: + case SecurityType.Cfd: + // Index/CFD: 0.1% commission with £10 minimum + feeAmount = CalculateCommissionFee(orderValue); + break; + + case SecurityType.Equity: + // Equity: 0.1% commission with £10 minimum + feeAmount = CalculateCommissionFee(orderValue); + break; + + case SecurityType.Crypto: + // Crypto: Spread-based pricing, no commission + return OrderFee.Zero; + + case SecurityType.Future: + case SecurityType.Option: + case SecurityType.FutureOption: + case SecurityType.IndexOption: + // Not currently supported + return OrderFee.Zero; + + default: + // Unknown security type, return zero + return OrderFee.Zero; + } + + return new OrderFee(new CashAmount(feeAmount, FeeCurrency)); + } + + /// + /// Calculate commission-based fee (0.1% with £10 minimum) + /// + /// The absolute order value + /// Fee amount in GBP + private static decimal CalculateCommissionFee(decimal orderValue) + { + if (orderValue <= 0) + { + return 0m; + } + + // Calculate 0.1% commission + var commission = orderValue * CommissionRate; + + // Apply minimum commission of £10 + return Math.Max(commission, MinimumCommission); + } + } +} diff --git a/Data/market-hours/market-hours-database.json b/Data/market-hours/market-hours-database.json index 507913acf45a..fe027fde054d 100644 --- a/Data/market-hours/market-hours-database.json +++ b/Data/market-hours/market-hours-database.json @@ -122781,6 +122781,262 @@ "holidays": [], "earlyCloses": {}, "lateOpens": {} + }, + "Forex-ig-[*]": { + "dataTimeZone": "UTC", + "exchangeTimeZone": "UTC", + "sunday": [ + { + "start": "22:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "monday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "tuesday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "wednesday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "thursday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "friday": [ + { + "start": "00:00:00", + "end": "22:00:00", + "state": "market" + } + ], + "saturday": [], + "holidays": [], + "earlyCloses": {}, + "lateOpens": {} + }, + "Index-ig-[*]": { + "dataTimeZone": "UTC", + "exchangeTimeZone": "UTC", + "sunday": [ + { + "start": "22:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "monday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "tuesday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "wednesday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "thursday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "friday": [ + { + "start": "00:00:00", + "end": "22:00:00", + "state": "market" + } + ], + "saturday": [], + "holidays": [], + "earlyCloses": {}, + "lateOpens": {} + }, + "Crypto-ig-[*]": { + "dataTimeZone": "UTC", + "exchangeTimeZone": "UTC", + "sunday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "monday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "tuesday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "wednesday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "thursday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "friday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "saturday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "holidays": [], + "earlyCloses": {}, + "lateOpens": {} + }, + "Cfd-ig-[*]": { + "dataTimeZone": "UTC", + "exchangeTimeZone": "UTC", + "sunday": [ + { + "start": "22:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "monday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "tuesday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "wednesday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "thursday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "friday": [ + { + "start": "00:00:00", + "end": "22:00:00", + "state": "market" + } + ], + "saturday": [], + "holidays": [], + "earlyCloses": {}, + "lateOpens": {} + }, + "Equity-ig-[*]": { + "dataTimeZone": "UTC", + "exchangeTimeZone": "UTC", + "sunday": [ + { + "start": "22:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "monday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "tuesday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "wednesday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "thursday": [ + { + "start": "00:00:00", + "end": "1.00:00:00", + "state": "market" + } + ], + "friday": [ + { + "start": "00:00:00", + "end": "22:00:00", + "state": "market" + } + ], + "saturday": [], + "holidays": [], + "earlyCloses": {}, + "lateOpens": {} } } -} +} \ No newline at end of file diff --git a/Data/symbol-properties/symbol-properties-database.csv b/Data/symbol-properties/symbol-properties-database.csv index ef8d02d02d67..11994733683b 100644 --- a/Data/symbol-properties/symbol-properties-database.csv +++ b/Data/symbol-properties/symbol-properties-database.csv @@ -10407,4 +10407,33 @@ dydx,ZETAUSD,cryptofuture,ZETA-USD,USD,1,0.0001,10,ZETA-USD,10.00000,10000000000 dydx,ZKUSD,cryptofuture,ZK-USD,USD,1,0.0001,10,ZK-USD,10.00000,10000000000,100000 dydx,ZORAUSD,cryptofuture,ZORA-USD,USD,1,0.00001,100,ZORA-USD,100.0000,100000000000,10000 dydx,ZROUSD,cryptofuture,ZRO-USD,USD,1,0.001,1,ZRO-USD,1.000000,1000000000,1000000 -dydx,ZRXUSD,cryptofuture,ZRX-USD,USD,1,0.0001,10,ZRX-USD,10.00000,10000000000,100000 \ No newline at end of file +dydx,ZRXUSD,cryptofuture,ZRX-USD,USD,1,0.0001,10,ZRX-USD,10.00000,10000000000,100000 +# IG Markets +ig,EURUSD,forex,,USD,1,0.00001,1000,,1 +ig,GBPUSD,forex,,USD,1,0.00001,1000,,1 +ig,USDJPY,forex,,JPY,1,0.001,1000,,1 +ig,AUDUSD,forex,,USD,1,0.00001,1000,,1 +ig,USDCAD,forex,,CAD,1,0.00001,1000,,1 +ig,USDCHF,forex,,CHF,1,0.00001,1000,,1 +ig,NZDUSD,forex,,USD,1,0.00001,1000,,1 +ig,EURGBP,forex,,GBP,1,0.00001,1000,,1 +ig,EURJPY,forex,,JPY,1,0.001,1000,,1 +ig,GBPJPY,forex,,JPY,1,0.001,1000,,1 +ig,EURCHF,forex,,CHF,1,0.00001,1000,,1 +ig,EURAUD,forex,,AUD,1,0.00001,1000,,1 +ig,AUDJPY,forex,,JPY,1,0.001,1000,,1 +ig,CADJPY,forex,,JPY,1,0.001,1000,,1 +ig,CHFJPY,forex,,JPY,1,0.001,1000,,1 +ig,NZDJPY,forex,,JPY,1,0.001,1000,,1 +ig,SPX,index,,USD,1,0.01,1,,1 +ig,DJI,index,,USD,1,0.01,1,,1 +ig,NDX,index,,USD,1,0.01,1,,1 +ig,FTSE,index,,GBP,1,0.01,1,,1 +ig,DAX,index,,EUR,1,0.01,1,,1 +ig,BTCUSD,crypto,,USD,1,0.01,0.001,,0.001 +ig,ETHUSD,crypto,,USD,1,0.01,0.01,,0.01 +ig,XAUUSD,cfd,,USD,1,0.01,1,,1 +ig,XAGUSD,cfd,,USD,1,0.001,1,,1 +ig,AAPL,equity,,USD,1,0.01,1,,1 +ig,TSLA,equity,,USD,1,0.01,1,,1 +ig,AMZN,equity,,USD,1,0.01,1,,1 diff --git a/Launcher/config.json b/Launcher/config.json index 4008a5bd9e41..5f1d0047c51c 100644 --- a/Launcher/config.json +++ b/Launcher/config.json @@ -6,10 +6,10 @@ // two predefined environments, 'backtesting' and 'live', feel free // to add more! - "environment": "backtesting", // "live-paper", "backtesting", "live-interactive", "live-interactive-iqfeed" + "environment": "live-ig", // "live-paper", "backtesting", "live-interactive", "live-interactive-iqfeed", "live-ig" // algorithm class selector - "algorithm-type-name": "BasicTemplateFrameworkAlgorithm", + "algorithm-type-name": "IGLiveTestAlgorithm", // Algorithm language selector - options CSharp, Python "algorithm-language": "CSharp", @@ -123,6 +123,14 @@ "fxcm-password": "", "fxcm-account-id": "", + // ig markets configuration + "ig-environment": "demo", // "demo" or "live" + "ig-api-url": "https://demo-api.ig.com/gateway/deal", + "ig-username": "fabo9981", + "ig-password": "Herbwert2qfst3gbhr,", + "ig-api-key": "aea342855b902e0447395a39f867c04175dd7089", + "ig-account-id": "Z684C1", + // iqfeed configuration "iqfeed-host": "127.0.0.1", "iqfeed-username": "", @@ -459,6 +467,21 @@ "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ] }, + // defines the 'live-ig' environment + "live-ig": { + "live-mode": true, + + // IG Markets brokerage implementation + "live-mode-brokerage": "IGBrokerage", + "data-queue-handler": [ "IGBrokerage" ], + "setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler", + "result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler", + "data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed", + "real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler", + "transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler", + "history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ] + }, + // defines the 'live-oanda' environment "live-oanda": { "live-mode": true,