Skip to content

Commit 5e9901c

Browse files
Shortable modeling improvements (#7579)
* Shortable modeling improvements - Do not limit live trading because of shortable model, will send algorithm warning - Add Interactive brokers shortable provider - Minor LocalDiskShortableProvider API changes * Fix shorted order update - Fix shorted order update. Updating regression algorithm * Minor improvement * Address reviews
1 parent 1a66846 commit 5e9901c

30 files changed

Lines changed: 280 additions & 216 deletions

‎Algorithm.CSharp/AllShortableSymbolsCoarseSelectionRegressionAlgorithm.cs‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,16 @@ private class AllShortableSymbolsRegressionAlgorithmBrokerageModel : DefaultBrok
171171
{
172172
public AllShortableSymbolsRegressionAlgorithmBrokerageModel() : base()
173173
{
174-
ShortableProvider = new RegressionTestShortableProvider();
174+
}
175+
public override IShortableProvider GetShortableProvider(Security security)
176+
{
177+
return new RegressionTestShortableProvider();
175178
}
176179
}
177180

178181
private class RegressionTestShortableProvider : LocalDiskShortableProvider
179182
{
180-
public RegressionTestShortableProvider() : base(SecurityType.Equity, "testbrokerage", Market.USA)
183+
public RegressionTestShortableProvider() : base("testbrokerage")
181184
{
182185
}
183186

@@ -188,14 +191,15 @@ public RegressionTestShortableProvider() : base(SecurityType.Equity, "testbroker
188191
/// <returns>Symbol/quantity shortable as a Dictionary. Returns null if no entry data exists for this date or brokerage</returns>
189192
public Dictionary<Symbol, long> AllShortableSymbols(DateTime localTime)
190193
{
194+
var shortableDataDirectory = Path.Combine(Globals.DataFolder, SecurityType.Equity.SecurityTypeToLower(), Market.USA, "shortable", Brokerage);
191195
var allSymbols = new Dictionary<Symbol, long>();
192196

193197
// Check backwards up to one week to see if we can source a previous file.
194198
// If not, then we return a list of all Symbols with quantity set to zero.
195199
var i = 0;
196200
while (i <= 7)
197201
{
198-
var shortableListFile = Path.Combine(ShortableDataDirectory.FullName, "dates", $"{localTime.AddDays(-i):yyyyMMdd}.csv");
202+
var shortableListFile = Path.Combine(shortableDataDirectory, "dates", $"{localTime.AddDays(-i):yyyyMMdd}.csv");
199203

200204
foreach (var line in DataProvider.ReadLines(shortableListFile))
201205
{

Algorithm.CSharp/BasicTemplateAtreyuAlgorithm.cs renamed to Algorithm.CSharp/BasicTemplateAxosAlgorithm.cs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,19 @@
1414
*/
1515

1616
using QuantConnect.Data;
17-
using QuantConnect.Orders;
1817
using QuantConnect.Interfaces;
1918
using QuantConnect.Brokerages;
2019
using System.Collections.Generic;
2120

2221
namespace QuantConnect.Algorithm.CSharp
2322
{
2423
/// <summary>
25-
/// Basic template algorithm for the Atreyu brokerage
24+
/// Basic template algorithm for the Axos brokerage
2625
/// </summary>
2726
/// <meta name="tag" content="using data" />
2827
/// <meta name="tag" content="using quantconnect" />
2928
/// <meta name="tag" content="trading and orders" />
30-
public class BasicTemplateAtreyuAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
29+
public class BasicTemplateAxosAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
3130
{
3231
/// <summary>
3332
/// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
@@ -38,14 +37,8 @@ public override void Initialize()
3837
SetEndDate(2013, 10, 11);
3938
SetCash(100000);
4039

41-
SetBrokerageModel(BrokerageName.Atreyu);
40+
SetBrokerageModel(BrokerageName.Axos);
4241
AddEquity("SPY", Resolution.Minute);
43-
44-
DefaultOrderProperties = new AtreyuOrderProperties
45-
{
46-
// Currently only support order for the day
47-
TimeInForce = TimeInForce.Day
48-
};
4942
}
5043

5144
/// <summary>
@@ -111,7 +104,7 @@ public override void OnData(Slice data)
111104
{"Estimated Strategy Capacity", "$150000000.00"},
112105
{"Lowest Capacity Asset", "SPY R735QTJ8XC9X"},
113106
{"Portfolio Turnover", "4.98%"},
114-
{"OrderListHash", "d549c64ee7f5e3866712b3c7dbd64caa"}
107+
{"OrderListHash", "86f2942f2fcfc7ee9e44521f86adc07d"}
115108
};
116109
}
117110
}

‎Algorithm.CSharp/ShortableProviderOrdersRejectedRegressionAlgorithm.cs‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
using System;
1818
using System.Collections.Generic;
19-
using QuantConnect.Brokerages;
2019
using QuantConnect.Data;
2120
using QuantConnect.Securities;
2221
using QuantConnect.Data.Shortable;
@@ -57,8 +56,15 @@ public override void OnData(Slice data)
5756
if (!_initialize)
5857
{
5958
HandleOrder(LimitOrder(_spy.Symbol, -1001, 10000m)); // Should be canceled, exceeds the max shortable quantity
60-
HandleOrder(LimitOrder(_spy.Symbol, -1000, 10000m)); // Allowed, orders at or below 1000 should be accepted
59+
var orderTicket = LimitOrder(_spy.Symbol, -1000, 10000m);
60+
HandleOrder(orderTicket); // Allowed, orders at or below 1000 should be accepted
6161
HandleOrder(LimitOrder(_spy.Symbol, -10, 0.01m)); // Should be canceled, the total quantity we would be short would exceed the max shortable quantity.
62+
63+
var response = orderTicket.UpdateQuantity(-999); // should be allowed, we are reducing the quantity we want to short
64+
if(!response.IsSuccess)
65+
{
66+
throw new Exception("Order update should of succeeded!");
67+
}
6268
_initialize = true;
6369
return;
6470
}
@@ -152,7 +158,7 @@ private void HandleOrder(OrderTicket orderTicket)
152158

153159
private class RegressionTestShortableProvider : LocalDiskShortableProvider
154160
{
155-
public RegressionTestShortableProvider() : base(SecurityType.Equity, "testbrokerage", Market.USA)
161+
public RegressionTestShortableProvider() : base("testbrokerage")
156162
{
157163
}
158164
}
@@ -205,7 +211,7 @@ public RegressionTestShortableProvider() : base(SecurityType.Equity, "testbroker
205211
{"Estimated Strategy Capacity", "$99000000.00"},
206212
{"Lowest Capacity Asset", "AIG R735QTJ8XC9X"},
207213
{"Portfolio Turnover", "0.23%"},
208-
{"OrderListHash", "3ac2d7a61f71c1345eb569e30cc2834c"}
214+
{"OrderListHash", "3e2d90c243496d7dc43d667eeacba7fb"}
209215
};
210216
}
211217
}

Algorithm.Python/BasicTemplateAtreyuAlgorithm.py renamed to Algorithm.Python/BasicTemplateAxosAlgorithm.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from AlgorithmImports import *
1515

1616
### <summary>
17-
### Basic template algorithm for the Atreyu brokerage
17+
### Basic template algorithm for the Axos brokerage
1818
### </summary>
1919
### <meta name="tag" content="using data" />
2020
### <meta name="tag" content="using quantconnect" />
@@ -29,13 +29,9 @@ def Initialize(self):
2929
self.SetEndDate(2013,10,11) #Set End Date
3030
self.SetCash(100000) #Set Strategy Cash
3131

32-
self.SetBrokerageModel(BrokerageName.Atreyu)
32+
self.SetBrokerageModel(BrokerageName.Axos)
3333
self.AddEquity("SPY", Resolution.Minute)
3434

35-
self.DefaultOrderProperties = AtreyuOrderProperties()
36-
# Currently only support order for the day
37-
self.DefaultOrderProperties.TimeInForce = TimeInForce.Day
38-
3935
def OnData(self, data):
4036
'''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
4137

‎Algorithm.Python/ShortableProviderOrdersRejectedRegressionAlgorithm.py‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
class RegressionTestShortableProvider(LocalDiskShortableProvider):
1717
def __init__(self):
18-
super().__init__(SecurityType.Equity, "testbrokerage", Market.USA)
18+
super().__init__("testbrokerage")
1919

2020
### <summary>
2121
### Tests that orders are denied if they exceed the max shortable quantity.
@@ -41,8 +41,14 @@ def Initialize(self):
4141
def OnData(self, data):
4242
if not self.initialize:
4343
self.HandleOrder(self.LimitOrder(self.spy.Symbol, -1001, 10000)) # Should be canceled, exceeds the max shortable quantity
44-
self.HandleOrder(self.LimitOrder(self.spy.Symbol, -1000, 10000)) # Allowed, orders at or below 1000 should be accepted
44+
orderTicket = self.LimitOrder(self.spy.Symbol, -1000, 10000)
45+
self.HandleOrder(orderTicket) # Allowed, orders at or below 1000 should be accepted
4546
self.HandleOrder(self.LimitOrder(self.spy.Symbol, -10, 0.01)) # Should be canceled, the total quantity we would be short would exceed the max shortable quantity.
47+
48+
response = orderTicket.UpdateQuantity(-999) # should be allowed, we are reducing the quantity we want to short
49+
if not response.IsSuccess:
50+
raise ValueError("Order update should of succeeded!");
51+
4652
self.initialize = True
4753
return
4854

‎Algorithm/QCAlgorithm.cs‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2969,18 +2969,25 @@ public bool Shortable(Symbol symbol)
29692969
/// </summary>
29702970
/// <param name="symbol">Symbol to check if shortable</param>
29712971
/// <param name="shortQuantity">Order's quantity to check if it is currently shortable, taking into account current holdings and open orders</param>
2972-
/// <returns>True if shortable</returns>
2972+
/// <param name="updateOrderId">Optionally the id of the order being updated. When updating an order
2973+
/// we want to ignore it's submitted short quantity and use the new provided quantity to determine if we
2974+
/// can perform the update</param>
2975+
/// <returns>True if the symbol can be shorted by the requested quantity</returns>
29732976
[DocumentationAttribute(TradingAndOrders)]
2974-
public bool Shortable(Symbol symbol, decimal shortQuantity)
2977+
public bool Shortable(Symbol symbol, decimal shortQuantity, int? updateOrderId = null)
29752978
{
2976-
var shortableQuantity = Securities[symbol].ShortableProvider.ShortableQuantity(symbol, Time);
2979+
var security = Securities[symbol];
2980+
var shortableQuantity = security.ShortableProvider.ShortableQuantity(symbol, security.LocalTime);
29772981
if (shortableQuantity == null)
29782982
{
29792983
return true;
29802984
}
29812985

2982-
var openOrderQuantity = Transactions.GetOpenOrdersRemainingQuantity(symbol);
2983-
var portfolioQuantity = Portfolio.ContainsKey(symbol) ? Portfolio[symbol].Quantity : 0;
2986+
var openOrderQuantity = Transactions.GetOpenOrdersRemainingQuantity(
2987+
// if 'updateOrderId' was given, ignore that orders quantity
2988+
order => order.Symbol == symbol && (!updateOrderId.HasValue || order.OrderId != updateOrderId.Value));
2989+
2990+
var portfolioQuantity = security.Holdings.Quantity;
29842991
// We check portfolio and open orders beforehand to ensure that orderQuantity == 0 case does not return
29852992
// a true result whenever we have no more shares left to short.
29862993
if (portfolioQuantity + openOrderQuantity <= -shortableQuantity)
@@ -3002,7 +3009,8 @@ public bool Shortable(Symbol symbol, decimal shortQuantity)
30023009
[DocumentationAttribute(TradingAndOrders)]
30033010
public long ShortableQuantity(Symbol symbol)
30043011
{
3005-
return Securities[symbol].ShortableProvider.ShortableQuantity(symbol, Time) ?? 0;
3012+
var security = Securities[symbol];
3013+
return security.ShortableProvider.ShortableQuantity(symbol, security.LocalTime) ?? 0;
30063014
}
30073015

30083016
/// <summary>

‎AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs‎

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,14 +1092,29 @@ public override string ToString()
10921092
public void SetObjectStore(IObjectStore objectStore) => _baseAlgorithm.SetObjectStore(objectStore);
10931093

10941094
/// <summary>
1095-
/// Checks if the asset is shortable at the brokerage
1095+
/// Determines if the Symbol is shortable at the brokerage
1096+
/// </summary>
1097+
/// <param name="symbol">Symbol to check if shortable</param>
1098+
/// <param name="shortQuantity">Order's quantity to check if it is currently shortable, taking into account current holdings and open orders</param>
1099+
/// <param name="updateOrderId">Optionally the id of the order being updated. When updating an order
1100+
/// we want to ignore it's submitted short quantity and use the new provided quantity to determine if we
1101+
/// can perform the update</param>
1102+
/// <returns>True if the symbol can be shorted by the requested quantity</returns>
1103+
public bool Shortable(Symbol symbol, decimal shortQuantity, int? updateOrderId = null)
1104+
{
1105+
return _baseAlgorithm.Shortable(symbol, shortQuantity, updateOrderId);
1106+
}
1107+
1108+
/// <summary>
1109+
/// Gets the quantity shortable for the given asset
10961110
/// </summary>
1097-
/// <param name="symbol">Symbol to check if it is shortable</param>
1098-
/// <param name="quantity">Quantity to short</param>
1099-
/// <returns>True if shortable at the brokerage</returns>
1100-
public bool Shortable(Symbol symbol, decimal quantity)
1111+
/// <returns>
1112+
/// Quantity shortable for the given asset. Zero if not
1113+
/// shortable, or a number greater than zero if shortable.
1114+
/// </returns>
1115+
public long ShortableQuantity(Symbol symbol)
11011116
{
1102-
return _baseAlgorithm.Shortable(symbol, quantity);
1117+
return _baseAlgorithm.ShortableQuantity(symbol);
11031118
}
11041119

11051120
/// <summary>

Common/Brokerages/AtreyuBrokerageModel.cs renamed to Common/Brokerages/AxosClearingBrokerageModel.cs

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,10 @@
2727
namespace QuantConnect.Brokerages
2828
{
2929
/// <summary>
30-
/// Provides Atreyu specific properties
30+
/// Provides the Axos clearing brokerage model specific properties
3131
/// </summary>
32-
public class AtreyuBrokerageModel : DefaultBrokerageModel
32+
public class AxosClearingBrokerageModel : DefaultBrokerageModel
3333
{
34-
private readonly IShortableProvider _shortableProvider;
35-
private readonly System.Type[] _supportedTimeInForces =
36-
{
37-
typeof(DayTimeInForce)
38-
};
39-
4034
private readonly HashSet<OrderType> _supportedOrderTypes = new()
4135
{
4236
OrderType.Limit,
@@ -55,9 +49,8 @@ public class AtreyuBrokerageModel : DefaultBrokerageModel
5549
/// <summary>
5650
/// Creates a new instance
5751
/// </summary>
58-
public AtreyuBrokerageModel(AccountType accountType = AccountType.Margin) : base(accountType)
52+
public AxosClearingBrokerageModel(AccountType accountType = AccountType.Margin) : base(accountType)
5953
{
60-
_shortableProvider = new LocalDiskShortableProvider(SecurityType.Equity, "quantconnect", Market.USA);
6154
}
6255

6356
/// <summary>
@@ -66,22 +59,26 @@ public AtreyuBrokerageModel(AccountType accountType = AccountType.Margin) : base
6659
public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets => DefaultMarketMap;
6760

6861
/// <summary>
69-
/// Provides Atreyu fee model
62+
/// Provides Axos fee model
7063
/// </summary>
7164
/// <param name="security">The security to get a fee model for</param>
7265
/// <returns>The new fee model for this brokerage</returns>
7366
public override IFeeModel GetFeeModel(Security security)
7467
{
75-
return new AtreyuFeeModel();
68+
return new AxosFeeModel();
7669
}
7770

7871
/// <summary>
7972
/// Gets the shortable provider
8073
/// </summary>
8174
/// <returns>Shortable provider</returns>
82-
public override IShortableProvider GetShortableProvider()
75+
public override IShortableProvider GetShortableProvider(Security security)
8376
{
84-
return _shortableProvider;
77+
if(security.Type == SecurityType.Equity)
78+
{
79+
return new LocalDiskShortableProvider("axos");
80+
}
81+
return base.GetShortableProvider(security);
8582
}
8683

8784
/// <summary>
@@ -124,20 +121,11 @@ public override bool CanSubmitOrder(Security security, Order order, out Brokerag
124121
return false;
125122
}
126123

127-
// validate time in force
128-
if (!_supportedTimeInForces.Contains(order.TimeInForce.GetType()))
129-
{
130-
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
131-
Messages.DefaultBrokerageModel.UnsupportedTimeInForce(this, order));
132-
133-
return false;
134-
}
135-
136124
// validate orders quantity
137125
if (order.AbsoluteQuantity % 1 != 0)
138126
{
139127
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
140-
Messages.AtreyuBrokerageModel.NonIntegerOrderQuantity(order));
128+
Messages.AxosBrokerageModel.NonIntegerOrderQuantity(order));
141129

142130
return false;
143131
}

‎Common/Brokerages/BrokerageName.cs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,5 +156,9 @@ public enum BrokerageName
156156
/// </summary>
157157
Eze,
158158

159+
/// <summary>
160+
/// Transaction and submit/execution rules will use Axos models
161+
/// </summary>
162+
Axos,
159163
}
160164
}

‎Common/Brokerages/DefaultBrokerageModel.cs‎

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,6 @@ public class DefaultBrokerageModel : IBrokerageModel
5656
{SecurityType.IndexOption, Market.USA}
5757
}.ToReadOnlyDictionary();
5858

59-
/// <summary>
60-
/// Determines whether the asset you want to short is shortable.
61-
/// The default is set to <see cref="NullShortableProvider"/>,
62-
/// which allows for infinite shorting of any asset. You can limit the
63-
/// quantity you can short for an asset class by setting this variable to
64-
/// your own implementation of <see cref="IShortableProvider"/>.
65-
/// </summary>
66-
protected IShortableProvider ShortableProvider { get; set; }
67-
6859
/// <summary>
6960
/// Gets or sets the account type used by this model
7061
/// </summary>
@@ -96,11 +87,6 @@ public virtual IReadOnlyDictionary<SecurityType, string> DefaultMarkets
9687
public DefaultBrokerageModel(AccountType accountType = AccountType.Margin)
9788
{
9889
AccountType = accountType;
99-
100-
// Shortable provider, responsible for loading the data that indicates how much
101-
// quantity we can short for a given asset. The NullShortableProvider default will
102-
// allow for infinite quantities of any asset to be shorted.
103-
ShortableProvider = new NullShortableProvider();
10490
}
10591

10692
/// <summary>
@@ -366,9 +352,12 @@ IBuyingPowerModel getCurrencyBuyingPowerModel() =>
366352
/// Gets the shortable provider
367353
/// </summary>
368354
/// <returns>Shortable provider</returns>
369-
public virtual IShortableProvider GetShortableProvider()
355+
public virtual IShortableProvider GetShortableProvider(Security security)
370356
{
371-
return ShortableProvider;
357+
// Shortable provider, responsible for loading the data that indicates how much
358+
// quantity we can short for a given asset. The NullShortableProvider default will
359+
// allow for infinite quantities of any asset to be shorted.
360+
return NullShortableProvider.Instance;
372361
}
373362

374363
/// <summary>

0 commit comments

Comments
 (0)