Skip to content

Commit

Permalink
Feature: support Index and IndexOption (#39)
Browse files Browse the repository at this point in the history
* feat: Index and IndexOption in SymbolMapper

* fix:  direction for Option
feat: add support Index and IndexOption to collection

* test:feat: IndexOption DQH

* feat: log all WS response when debugging enabled

* fix: TryGetLeanSymbol in Handle WS message

* test:feat: add Index and IndexOption History Request
test:feat: increase speed of HistoryRequest
refactor: create brokerage instance in one TestSetup class

* fix: handle error in ReplaceOrder Response API

* test:refactor: trading test cases (with dynamic stop/limit price)

* feat: handle Index in SymbolMapper
test:refactor: GetLean TestCases

* feat: Map IndexOption
test:remove: GetLeanSymbol not used

* test:refactor:remove: clean and more readable

* remove: handle try/catch in Replace Order API

* refactor: use inheritance for ReplaceOrder models
test:feat: DQH of Weekly indexes

* remove: public TryGetLeanSymbolByBrokerageAssetType

* remove: extra TradeStationReplaceOrderResponse
test:refactor: change option expiration Date

* test:feat: add unsupported security types SymbolMapper

* test:feat: subtract amount in ShortFromLong
test:refactor: StopLimit custom class

* fix: handle of PlaceOrder Error
fix: LookupSymbol in DQUH
refactor: exception message when Parse Option
refactor: throw out Parsing of Option Symbols
test:remove: static instance of TS
test:refactor: TearDown of history instance
test:refactor: SymbolMapper
test:feat: LookupSymbol for Option and Index

* feat: update json add missed SecurityTypes

* refactor: LookupSymbols in DQUP

* refactor: get rid TryGetLeanSymbol in SymbolMapper class

* feat: skip not valid ticker in Lookupsymbol

* refactor: SymbolMapper more cleaner
  • Loading branch information
Romazes authored Dec 11, 2024
1 parent 352ab5c commit 600172f
Show file tree
Hide file tree
Showing 20 changed files with 1,028 additions and 376 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.Orders;
using QuantConnect.Interfaces;
using QuantConnect.Tests.Brokerages;

namespace QuantConnect.Brokerages.TradeStation.Tests;

public class CustomLimitOrderTestParameters : LimitOrderTestParameters
{
public CustomLimitOrderTestParameters(Symbol symbol, decimal highLimit, decimal lowLimit, IOrderProperties properties = null, OrderSubmissionData orderSubmissionData = null) : base(symbol, highLimit, lowLimit, properties, orderSubmissionData)
{
}

public override bool ModifyOrderToFill(IBrokerage brokerage, Order order, decimal lastMarketPrice)
{
var symbolProperties = SPDB.GetSymbolProperties(order.Symbol.ID.Market, order.Symbol, order.SecurityType, order.PriceCurrency);
var roundOffPlaces = symbolProperties.MinimumPriceVariation.GetDecimalPlaces();

var price = default(decimal);
if (order.Quantity > 0)
{
// for limit buys we need to increase the limit price
price = Math.Round(lastMarketPrice * 1.02m, roundOffPlaces);
}
else
{
// for limit sells we need to decrease the limit price
price = Math.Round(lastMarketPrice / 1.02m, roundOffPlaces);
}

price = order.SecurityType == SecurityType.IndexOption ? Round(price) : Round(lastMarketPrice);

order.ApplyUpdateOrderRequest(new UpdateOrderRequest(DateTime.UtcNow, order.Id, new UpdateOrderFields() { LimitPrice = Math.Round(price, roundOffPlaces) }));

return true;
}

private static decimal Round(decimal price, decimal increment = 0.05m)
{
return Math.Round(price / increment) * increment;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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.Orders;
using QuantConnect.Interfaces;
using QuantConnect.Tests.Brokerages;

namespace QuantConnect.Brokerages.TradeStation.Tests;

public class CustomStopLimitOrderTestParameters : StopLimitOrderTestParameters
{
public CustomStopLimitOrderTestParameters(Symbol symbol, decimal highLimit, decimal lowLimit, IOrderProperties properties = null, OrderSubmissionData orderSubmissionData = null) : base(symbol, highLimit, lowLimit, properties, orderSubmissionData)
{
}

public override bool ModifyOrderToFill(IBrokerage brokerage, Order order, decimal lastMarketPrice)
{
var symbolProperties = SPDB.GetSymbolProperties(order.Symbol.ID.Market, order.Symbol, order.SecurityType, order.PriceCurrency);
var roundOffPlaces = symbolProperties.MinimumPriceVariation.GetDecimalPlaces();

var newStopPrice = default(decimal);
var newLimitPrice = default(decimal);
var previousStopPrice = (order as StopLimitOrder).StopPrice;
if (order.Quantity > 0)
{
switch (order.SecurityType)
{
case SecurityType.Equity:
// Invalid Stop Price - Stop Price must be above current market.
newStopPrice = lastMarketPrice + 0.02m;
// Invalid Limit Price - Limit Price must be at or above Stop Price.
newLimitPrice = newStopPrice + 0.03m;
break;
default:
newStopPrice = lastMarketPrice + 0.01m;
newLimitPrice = newStopPrice + 0.02m;
break;
}
}
else
{
switch (order.SecurityType)
{
case SecurityType.Equity:
// for stop sells we need to increase the stop price
newStopPrice = lastMarketPrice - 0.02m;
newLimitPrice = newStopPrice - 0.03m;
break;
default:
newStopPrice = lastMarketPrice - 0.01m;
newLimitPrice = newStopPrice - 0.02m;
break;
}

}

newLimitPrice = order.SecurityType == SecurityType.IndexOption ? Round(newLimitPrice) : newLimitPrice;
newStopPrice = order.SecurityType == SecurityType.IndexOption ? Round(newStopPrice) : newStopPrice;

order.ApplyUpdateOrderRequest(
new UpdateOrderRequest(
DateTime.UtcNow,
order.Id,
new UpdateOrderFields()
{
LimitPrice = newLimitPrice,
StopPrice = newStopPrice
}));

return newStopPrice != previousStopPrice;
}

private static decimal Round(decimal price, decimal increment = 0.05m)
{
return Math.Round(price / increment) * increment;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.Orders;
using QuantConnect.Interfaces;
using QuantConnect.Tests.Brokerages;

namespace QuantConnect.Brokerages.TradeStation.Tests;

public class CustomStopMarketOrderTestParameters : StopMarketOrderTestParameters
{
public CustomStopMarketOrderTestParameters(Symbol symbol, decimal highLimit, decimal lowLimit, IOrderProperties properties = null, OrderSubmissionData orderSubmissionData = null) : base(symbol, highLimit, lowLimit, properties, orderSubmissionData)
{
}

public override bool ModifyOrderToFill(IBrokerage brokerage, Order order, decimal lastMarketPrice)
{
var newStopPrice = default(decimal);
var previousStop = (order as StopMarketOrder).StopPrice;
if (order.Quantity > 0)
{
newStopPrice = lastMarketPrice + 0.01m;
}
else
{
newStopPrice = lastMarketPrice - 0.01m;
}

newStopPrice = order.SecurityType == SecurityType.IndexOption ? Round(newStopPrice) : newStopPrice;

order.ApplyUpdateOrderRequest(new UpdateOrderRequest(DateTime.UtcNow, order.Id, new UpdateOrderFields() { StopPrice = newStopPrice }));

return newStopPrice != previousStop;
}

private static decimal Round(decimal price, decimal increment = 0.05m)
{
return Math.Round(price / increment) * increment;
}
}
35 changes: 35 additions & 0 deletions QuantConnect.TradeStationBrokerage.Tests/Models/LegSymbol.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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.Brokerages.TradeStation.Models;

namespace QuantConnect.Brokerages.TradeStation.Tests;

/// <summary>
/// Combines a TradeStation <see cref="Models.Leg"/> with its corresponding Lean <see cref="QuantConnect.Symbol"/>.
/// </summary>
/// <param name="Leg">The financial instrument or component (e.g., option leg).</param>
/// <param name="Symbol">The corresponding Lean symbol.</param>
public sealed record LegSymbol(Leg Leg, Symbol Symbol)
{
/// <summary>
/// Returns a detailed string representation of the leg and its associated symbol.
/// </summary>
/// <returns>A formatted string summarizing the leg's details and the Lean symbol.</returns>
public override string ToString()
{
return $"Leg: {Leg.AssetType}, Symbol = {Leg.Symbol}, Underlying = {Leg.Underlying}, LeanSymbol = {Symbol}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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;

namespace QuantConnect.Brokerages.TradeStation.Tests;

/// <summary>
/// Represents the parameters required for testing an order, including order type, symbol, and price limits.
/// </summary>
/// <param name="OrderType">The type of order being tested (e.g., Market, Limit, Stop).</param>
/// <param name="Symbol">The financial symbol for the order, such as a stock or option ticker.</param>
public record OrderTestMetaData(OrderType OrderType, Symbol Symbol);
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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.Linq;
using QuantConnect.Orders;
using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Logging;

namespace QuantConnect.Brokerages.TradeStation.Tests;

public class TradeStationBrokerageTest : TradeStationBrokerage
{
/// <summary>
/// Constructor for the TradeStation brokerage.
/// </summary>
/// <remarks>
/// This constructor initializes a new instance of the TradeStationBrokerage class with the provided parameters.
/// </remarks>
/// <param name="apiKey">The API key for authentication.</param>
/// <param name="apiKeySecret">The API key secret for authentication.</param>
/// <param name="restApiUrl">The URL of the REST API.</param>
/// <param name="redirectUrl">The redirect URL to generate great link to get right "authorizationCodeFromUrl"</param>
/// <param name="authorizationCode">The authorization code obtained from the URL.</param>
/// <param name="refreshToken">The refresh token used to obtain new access tokens for authentication.</param>
/// <param name="accountType">The type of TradeStation account for the current session.
/// For <see cref="TradeStationAccountType.Cash"/> or <seealso cref="TradeStationAccountType.Margin"/> accounts, it is used for trading <seealso cref="SecurityType.Equity"/> and <seealso cref="SecurityType.Option"/>.
/// For <seealso cref="TradeStationAccountType.Futures"/> accounts, it is used for trading <seealso cref="SecurityType.Future"/> contracts.</param>
/// <param name="orderProvider">The order provider.</param>
/// <param name="accountId">The specific user account id.</param>
public TradeStationBrokerageTest(string apiKey, string apiKeySecret, string restApiUrl, string redirectUrl,
string authorizationCode, string refreshToken, string accountType, IOrderProvider orderProvider, ISecurityProvider securityProvider, string accountId = "")
: base(apiKey, apiKeySecret, restApiUrl, redirectUrl, authorizationCode, refreshToken, accountType, orderProvider, securityProvider, accountId)
{ }

/// <summary>
/// Retrieves the last price of the specified symbol.
/// </summary>
/// <param name="symbol">The symbol for which to retrieve the last price.</param>
/// <returns>The last price of the specified symbol as a decimal.</returns>
public Models.Quote GetPrice(Symbol symbol)
{
var quotes = GetQuote(symbol).Quotes.Single();
Log.Trace($"{nameof(TradeStationBrokerageTest)}.{nameof(GetPrice)}: {symbol}: Ask = {quotes.Ask}, Bid = {quotes.Bid}, Last = {quotes.Last}");
return quotes;
}

public bool GetTradeStationOrderRouteIdByOrder(TradeStationOrderProperties tradeStationOrderProperties, IReadOnlyCollection<SecurityType> securityTypes, out string routeId)
{
routeId = default;
return GetTradeStationOrderRouteIdByOrderSecurityTypes(tradeStationOrderProperties, securityTypes, out routeId);
}
}
30 changes: 29 additions & 1 deletion QuantConnect.TradeStationBrokerage.Tests/TestSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@

using System;
using System.IO;
using System.Linq;
using NUnit.Framework;
using System.Collections;
using QuantConnect.Logging;
using QuantConnect.Securities;
using QuantConnect.Configuration;
using static QuantConnect.Brokerages.TradeStation.Tests.TradeStationBrokerageTests;

namespace QuantConnect.Brokerages.TradeStation.Tests
{
Expand All @@ -30,6 +33,31 @@ public void TestSetupCase()
{
}

public static TradeStationBrokerageTest CreateBrokerage(IOrderProvider orderProvider, ISecurityProvider securityProvider)
{
var clientId = Config.Get("trade-station-client-id");
var clientSecret = Config.Get("trade-station-client-secret");
var restApiUrl = Config.Get("trade-station-api-url");
var accountType = Config.Get("trade-station-account-type");
var refreshToken = Config.Get("trade-station-refresh-token");

if (string.IsNullOrEmpty(refreshToken))
{
var redirectUrl = Config.Get("trade-station-redirect-url");
var authorizationCode = Config.Get("trade-station-authorization-code");

if (new string[] { redirectUrl, authorizationCode }.Any(string.IsNullOrEmpty))
{
throw new ArgumentException("RedirectUrl or AuthorizationCode cannot be empty or null. Please ensure these values are correctly set in the configuration file.");
}

return new TradeStationBrokerageTest(clientId, clientSecret, restApiUrl, redirectUrl, authorizationCode, string.Empty,
accountType, orderProvider, securityProvider);
}

return new TradeStationBrokerageTest(clientId, clientSecret, restApiUrl, string.Empty, string.Empty, refreshToken, accountType, orderProvider, securityProvider);
}

public static void ReloadConfiguration()
{
// nunit 3 sets the current folder to a temp folder we need it to be the test bin output folder
Expand Down Expand Up @@ -71,7 +99,7 @@ private static TestCaseData[] TestParameters
get
{
SetUp();
return new [] { new TestCaseData() };
return new[] { new TestCaseData() };
}
}
}
Expand Down
Loading

0 comments on commit 600172f

Please sign in to comment.