Skip to content

Commit

Permalink
Feature: Implementation Trade Station Brokerage (#8031)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Romazes authored May 20, 2024
1 parent 549979f commit fe2110d
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 20 deletions.
7 changes: 6 additions & 1 deletion Common/Brokerages/BrokerageName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ public enum BrokerageName
/// <summary>
/// Transaction and submit/execution rules will use Coinbase broker's model
/// </summary>
Coinbase
Coinbase,

/// <summary>
/// Transaction and submit/execution rules will use TradeStation models
/// </summary>
TradeStation
}
}
6 changes: 6 additions & 0 deletions Common/Brokerages/IBrokerageModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;

Expand Down
119 changes: 119 additions & 0 deletions Common/Brokerages/TradeStationBrokerageModel.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents a brokerage model specific to TradeStation.
/// </summary>
public class TradeStationBrokerageModel : DefaultBrokerageModel
{
/// <summary>
/// HashSet containing the security types supported by TradeStation.
/// </summary>
private readonly HashSet<SecurityType> _supportSecurityTypes = new(
new[]
{
SecurityType.Equity,
SecurityType.Option,
SecurityType.Future
});

/// <summary>
/// HashSet containing the order types supported by TradeStation.
/// </summary>
private readonly HashSet<OrderType> _supportOrderTypes = new(
new[]
{
OrderType.Market,
OrderType.Limit,
OrderType.StopMarket,
OrderType.StopLimit
});

/// <summary>
/// Constructor for TradeStation brokerage model
/// </summary>
/// <param name="accountType">Cash or Margin</param>
public TradeStationBrokerageModel(AccountType accountType = AccountType.Margin)
: base(accountType)
{
}

/// <summary>
/// Provides TradeStation fee model
/// </summary>
/// <param name="security">Security</param>
/// <returns>TradeStation fee model</returns>
public override IFeeModel GetFeeModel(Security security)
{
return new TradeStationFeeModel();
}

/// <summary>
/// Returns true if the brokerage could accept this order. This takes into account
/// order type, security type, and order size limits.
/// </summary>
/// <remarks>
/// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
/// </remarks>
/// <param name="security">The security of the order</param>
/// <param name="order">The order to be processed</param>
/// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
/// <returns>True if the brokerage could process the order, false otherwise</returns>
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);
}

/// <summary>
/// TradeStation support Update Order
/// </summary>
/// <param name="security">Security</param>
/// <param name="order">Order that should be updated</param>
/// <param name="request">Update request</param>
/// <param name="message">Outgoing message</param>
/// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
{
message = null;
return true;
}
}
}
83 changes: 83 additions & 0 deletions Common/Orders/Fees/TradeStationFeeModel.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents a fee model specific to TradeStation.
/// </summary>
/// <see href="https://www.tradestation.com/pricing"/>
/// <remarks>
/// 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
/// </remarks>
public class TradeStationFeeModel : FeeModel
{
/// <summary>
/// Represents the fee associated with equity options transactions (per contract).
/// </summary>
private const decimal _equityOptionFee = 0.6m;

/// <summary>
/// Represents the fee associated with futures transactions (per contract, per side).
/// </summary>
private const decimal _futuresFee = 1.5m;

/// <summary>
/// Gets the commission per trade based on the residency status of the entity or person.
/// </summary>
private decimal CommissionPerTrade => USResident ? 0m : 5.0m;

/// <summary>
/// Gets or sets a value indicating whether the entity or person is a resident of the United States.
/// </summary>
/// <value>
/// <c>true</c> if the entity or person is a US resident; otherwise, <c>false</c>.
/// </value>
public bool USResident { get; set; } = true;

/// <summary>
/// Calculates the order fee based on the security type and order parameters.
/// </summary>
/// <param name="parameters">The parameters for the order fee calculation, which include security and order details.</param>
/// <returns>
/// An <see cref="OrderFee"/> instance representing the calculated order fee.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="parameters"/> is <c>null</c>.
/// </exception>
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));
}
}
}
}
24 changes: 24 additions & 0 deletions Common/Orders/TradeStationOrderProperties.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents the properties of an order in TradeStation.
/// </summary>
public class TradeStationOrderProperties : OrderProperties
{ }
}
44 changes: 25 additions & 19 deletions Common/SymbolRepresentation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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}";
}

/// <summary>
Expand Down Expand Up @@ -463,23 +463,29 @@ public static OptionTickerProperties ParseOptionTickerIQFeed(string ticker)
};


private static IReadOnlyDictionary<string, int> _futuresMonthCodeLookup = new Dictionary<string, int>
{
{ "F", 1 },
{ "G", 2 },
{ "H", 3 },
{ "J", 4 },
{ "K", 5 },
{ "M", 6 },
{ "N", 7 },
{ "Q", 8 },
{ "U", 9 },
{ "V", 10 },
{ "X", 11 },
{ "Z", 12 }
};
/// <summary>
/// Provides a lookup dictionary for mapping futures month codes to their corresponding numeric values.
/// </summary>
public static IReadOnlyDictionary<string, int> FuturesMonthCodeLookup { get; } = new Dictionary<string, int>
{
{ "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<int, string> _futuresMonthLookup = _futuresMonthCodeLookup.ToDictionary(kv => kv.Value, kv => kv.Key);
/// <summary>
/// Provides a lookup dictionary for mapping numeric values to their corresponding futures month codes.
/// </summary>
public static IReadOnlyDictionary<int, string> FuturesMonthLookup { get; } = FuturesMonthCodeLookup.ToDictionary(kv => kv.Value, kv => kv.Key);

/// <summary>
/// Get the expiration year from short year (two-digit integer).
Expand Down
14 changes: 14 additions & 0 deletions Launcher/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit fe2110d

Please sign in to comment.