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