From fe2110de96afcfe52672a39762ec634ac6cd92f6 Mon Sep 17 00:00:00 2001 From: Roman Yavnikov <45608740+Romazes@users.noreply.github.com> Date: Mon, 20 May 2024 23:58:44 +0300 Subject: [PATCH] Feature: Implementation Trade Station Brokerage (#8031) * refactor: make public futuresMonth Collection * feat: TradeStation brokerage * feat: PositionSide for Option in TradeStationOrderProperties * feat: missed market TradeStation name * feat: missed config TradeStation * remove: extra TradeStation code * remove: pragrma warning * feat: implement TradeStationFeeModel --- Common/Brokerages/BrokerageName.cs | 7 +- Common/Brokerages/IBrokerageModel.cs | 6 + .../Brokerages/TradeStationBrokerageModel.cs | 119 ++++++++++++++++++ Common/Orders/Fees/TradeStationFeeModel.cs | 83 ++++++++++++ Common/Orders/TradeStationOrderProperties.cs | 24 ++++ Common/SymbolRepresentation.cs | 44 ++++--- Launcher/config.json | 14 +++ 7 files changed, 277 insertions(+), 20 deletions(-) create mode 100644 Common/Brokerages/TradeStationBrokerageModel.cs create mode 100644 Common/Orders/Fees/TradeStationFeeModel.cs create mode 100644 Common/Orders/TradeStationOrderProperties.cs diff --git a/Common/Brokerages/BrokerageName.cs b/Common/Brokerages/BrokerageName.cs index 70ecfb13e2fb..b9c20164395c 100644 --- a/Common/Brokerages/BrokerageName.cs +++ b/Common/Brokerages/BrokerageName.cs @@ -167,6 +167,11 @@ public enum BrokerageName /// /// Transaction and submit/execution rules will use Coinbase broker's model /// - Coinbase + Coinbase, + + /// + /// Transaction and submit/execution rules will use TradeStation models + /// + TradeStation } } diff --git a/Common/Brokerages/IBrokerageModel.cs b/Common/Brokerages/IBrokerageModel.cs index 5e6f5724e59b..35924b7fdc83 100644 --- a/Common/Brokerages/IBrokerageModel.cs +++ b/Common/Brokerages/IBrokerageModel.cs @@ -272,6 +272,9 @@ public static IBrokerageModel Create(IOrderProvider orderProvider, BrokerageName case BrokerageName.Eze: return new EzeBrokerageModel(accountType); + case BrokerageName.TradeStation: + return new TradeStationBrokerageModel(accountType); + default: throw new ArgumentOutOfRangeException(nameof(brokerage), brokerage, null); } @@ -363,6 +366,9 @@ public static BrokerageName GetBrokerageName(IBrokerageModel brokerageModel) case EzeBrokerageModel _: return BrokerageName.Eze; + case TradeStationBrokerageModel _: + return BrokerageName.TradeStation; + case DefaultBrokerageModel _: return BrokerageName.Default; diff --git a/Common/Brokerages/TradeStationBrokerageModel.cs b/Common/Brokerages/TradeStationBrokerageModel.cs new file mode 100644 index 000000000000..4401a5e5b450 --- /dev/null +++ b/Common/Brokerages/TradeStationBrokerageModel.cs @@ -0,0 +1,119 @@ +/* + * 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.Orders; +using QuantConnect.Securities; +using QuantConnect.Orders.Fees; +using System.Collections.Generic; + +namespace QuantConnect.Brokerages +{ + /// + /// Represents a brokerage model specific to TradeStation. + /// + public class TradeStationBrokerageModel : DefaultBrokerageModel + { + /// + /// HashSet containing the security types supported by TradeStation. + /// + private readonly HashSet _supportSecurityTypes = new( + new[] + { + SecurityType.Equity, + SecurityType.Option, + SecurityType.Future + }); + + /// + /// HashSet containing the order types supported by TradeStation. + /// + private readonly HashSet _supportOrderTypes = new( + new[] + { + OrderType.Market, + OrderType.Limit, + OrderType.StopMarket, + OrderType.StopLimit + }); + + /// + /// Constructor for TradeStation brokerage model + /// + /// Cash or Margin + public TradeStationBrokerageModel(AccountType accountType = AccountType.Margin) + : base(accountType) + { + } + + /// + /// Provides TradeStation fee model + /// + /// Security + /// TradeStation fee model + public override IFeeModel GetFeeModel(Security security) + { + return new TradeStationFeeModel(); + } + + /// + /// Returns true if the brokerage could accept this order. This takes into account + /// order type, security type, and order size limits. + /// + /// + /// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit + /// + /// The security of the order + /// 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 process the order, false otherwise + public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message) + { + message = default; + + if (!_supportSecurityTypes.Contains(security.Type)) + { + message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", + Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security)); + + return false; + } + + if (!_supportOrderTypes.Contains(order.Type)) + { + message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", + Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportOrderTypes)); + + return false; + } + + return base.CanSubmitOrder(security, order, out message); + } + + /// + /// TradeStation support Update Order + /// + /// Security + /// Order that should be updated + /// Update request + /// Outgoing message + /// 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; + return true; + } + } +} diff --git a/Common/Orders/Fees/TradeStationFeeModel.cs b/Common/Orders/Fees/TradeStationFeeModel.cs new file mode 100644 index 000000000000..66e2a4dafa68 --- /dev/null +++ b/Common/Orders/Fees/TradeStationFeeModel.cs @@ -0,0 +1,83 @@ +/* + * 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 +{ + /// + /// Represents a fee model specific to TradeStation. + /// + /// + /// + /// It is $0 for domestic and $5 for international clients for normal equities trades up to 10,000 shares, then $0.005 per share after. + /// Options are $0.60 per contract, per side, and an extra $1 for index options + /// + public class TradeStationFeeModel : FeeModel + { + /// + /// Represents the fee associated with equity options transactions (per contract). + /// + private const decimal _equityOptionFee = 0.6m; + + /// + /// Represents the fee associated with futures transactions (per contract, per side). + /// + private const decimal _futuresFee = 1.5m; + + /// + /// Gets the commission per trade based on the residency status of the entity or person. + /// + private decimal CommissionPerTrade => USResident ? 0m : 5.0m; + + /// + /// Gets or sets a value indicating whether the entity or person is a resident of the United States. + /// + /// + /// true if the entity or person is a US resident; otherwise, false. + /// + public bool USResident { get; set; } = true; + + /// + /// Calculates the order fee based on the security type and order parameters. + /// + /// The parameters for the order fee calculation, which include security and order details. + /// + /// An instance representing the calculated order fee. + /// + /// + /// Thrown when is null. + /// + public override OrderFee GetOrderFee(OrderFeeParameters parameters) + { + if (parameters == null) + { + throw new ArgumentNullException(nameof(parameters), "Order fee parameters cannot be null."); + } + + switch (parameters.Security.Type) + { + case SecurityType.Option: + return new OrderFee(new CashAmount(CommissionPerTrade + parameters.Order.AbsoluteQuantity * _equityOptionFee, Currencies.USD)); + case SecurityType.Future: + return new OrderFee(new CashAmount(parameters.Order.AbsoluteQuantity * _futuresFee, Currencies.USD)); + default: + return new OrderFee(new CashAmount(CommissionPerTrade, Currencies.USD)); + } + } + } +} diff --git a/Common/Orders/TradeStationOrderProperties.cs b/Common/Orders/TradeStationOrderProperties.cs new file mode 100644 index 000000000000..1bbb5a01b573 --- /dev/null +++ b/Common/Orders/TradeStationOrderProperties.cs @@ -0,0 +1,24 @@ +/* + * 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. + * +*/ + +namespace QuantConnect.Orders +{ + /// + /// Represents the properties of an order in TradeStation. + /// + public class TradeStationOrderProperties : OrderProperties + { } +} diff --git a/Common/SymbolRepresentation.cs b/Common/SymbolRepresentation.cs index 77d4b9feb8d9..9f0e5226c207 100644 --- a/Common/SymbolRepresentation.cs +++ b/Common/SymbolRepresentation.cs @@ -127,12 +127,12 @@ public static FutureTickerProperties ParseFutureTicker(string ticker) return null; } - if (!_futuresMonthCodeLookup.ContainsKey(expirationMonthString)) + if (!FuturesMonthCodeLookup.ContainsKey(expirationMonthString)) { return null; } - var expirationMonth = _futuresMonthCodeLookup[expirationMonthString]; + var expirationMonth = FuturesMonthCodeLookup[expirationMonthString]; return new FutureTickerProperties { @@ -283,7 +283,7 @@ public static string GenerateFutureTicker(string underlying, DateTime expiration var expirationDay = includeExpirationDate ? $"{expiration.Day:00}" : string.Empty; - return $"{underlying}{expirationDay}{_futuresMonthLookup[month]}{year}"; + return $"{underlying}{expirationDay}{FuturesMonthLookup[month]}{year}"; } /// @@ -463,23 +463,29 @@ public static OptionTickerProperties ParseOptionTickerIQFeed(string ticker) }; - private static IReadOnlyDictionary _futuresMonthCodeLookup = new Dictionary - { - { "F", 1 }, - { "G", 2 }, - { "H", 3 }, - { "J", 4 }, - { "K", 5 }, - { "M", 6 }, - { "N", 7 }, - { "Q", 8 }, - { "U", 9 }, - { "V", 10 }, - { "X", 11 }, - { "Z", 12 } - }; + /// + /// Provides a lookup dictionary for mapping futures month codes to their corresponding numeric values. + /// + public static IReadOnlyDictionary FuturesMonthCodeLookup { get; } = new Dictionary + { + { "F", 1 }, // January + { "G", 2 }, // February + { "H", 3 }, // March + { "J", 4 }, // April + { "K", 5 }, // May + { "M", 6 }, // June + { "N", 7 }, // July + { "Q", 8 }, // August + { "U", 9 }, // September + { "V", 10 }, // October + { "X", 11 }, // November + { "Z", 12 } // December + }; - private static IReadOnlyDictionary _futuresMonthLookup = _futuresMonthCodeLookup.ToDictionary(kv => kv.Value, kv => kv.Key); + /// + /// Provides a lookup dictionary for mapping numeric values to their corresponding futures month codes. + /// + public static IReadOnlyDictionary FuturesMonthLookup { get; } = FuturesMonthCodeLookup.ToDictionary(kv => kv.Value, kv => kv.Key); /// /// Get the expiration year from short year (two-digit integer). diff --git a/Launcher/config.json b/Launcher/config.json index b3c55effd4d6..a0f99ca28832 100644 --- a/Launcher/config.json +++ b/Launcher/config.json @@ -207,6 +207,20 @@ "tt-order-routing-port": "", "tt-log-fix-messages": false, + // Trade Station configuration + "trade-station-api-key": "", + "trade-station-api-secret": "", + "trade-station-code-from-url": "", + "trade-station-redirect-url": "http://localhost", + "trade-station-refresh-token": "", + "trade-station-api-url": "https://sim-api.tradestation.com", + "trade-station-account-type": "Cash|Margin|Futures", + // [Optional] Trade Station Proxy Settings + "trade-station-use-proxy": false, + "trade-station-proxy-address-port": "", + "trade-station-proxy-username": "", + "trade-station-proxy-password": "", + // Exante trading configuration // client-id, application-id, shared-key are required to access Exante REST API // Exante generates them at https://exante.eu/clientsarea/dashboard/ after adding an application