Skip to content

Commit c5daf9a

Browse files
Refactor IFeeModel
- Refactoring `IFeeModel`. *This is a breaking change* for implementations inheriting directly from the interface. Deleting old and adding a new method `OrderFee GetOrderFee(OrderFeeParameters parameters)` that will use a parameter and a result object. - Refactoring `CashAmount` so it does not embed a `ICurrencyConverter` instance. - Updating unit tests - The `Security.QuoteCurrency`, a `Cash` instance, will provide access to the `AccountCurrency` as a property. - Will maintain backwards compatibility with old python custom FeeModels, Adding unit test. > Note that for now, consumers will ignore the currency, as before, and directly consume the amount
1 parent 299b467 commit c5daf9a

39 files changed

Lines changed: 767 additions & 293 deletions

Algorithm.CSharp/CustomModelsAlgorithm.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public override OrderEvent MarketFill(Security asset, MarketOrder order)
115115
}
116116
}
117117

118-
public class CustomFeeModel : IFeeModel
118+
public class CustomFeeModel : FeeModel
119119
{
120120
private readonly QCAlgorithm _algorithm;
121121

@@ -124,13 +124,16 @@ public CustomFeeModel(QCAlgorithm algorithm)
124124
_algorithm = algorithm;
125125
}
126126

127-
public decimal GetOrderFee(Security security, Order order)
127+
public override OrderFee GetOrderFee(OrderFeeParameters parameters)
128128
{
129129
// custom fee math
130-
var fee = Math.Max(1m, security.Price*order.AbsoluteQuantity*0.00001m);
130+
var fee = Math.Max(
131+
1m,
132+
parameters.Security.Price*parameters.Order.AbsoluteQuantity*0.00001m);
131133

132134
_algorithm.Log("CustomFeeModel: " + fee);
133-
return fee;
135+
return new OrderFee(new CashAmount(fee,
136+
parameters.Security.QuoteCurrency.AccountCurrency));
134137
}
135138
}
136139

Algorithm.Python/CustomModelsAlgorithm.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
from System import *
2020
from QuantConnect import *
2121
from QuantConnect.Algorithm import *
22-
from QuantConnect.Orders import OrderStatus
23-
from QuantConnect.Orders.Fills import ImmediateFillModel
22+
from QuantConnect.Orders import *
23+
from QuantConnect.Orders.Fees import *
24+
from QuantConnect.Securities import *
25+
from QuantConnect.Orders.Fills import *
2426
import numpy as np
2527
import decimal as d
2628
import random
@@ -89,15 +91,19 @@ def MarketFill(self, asset, order):
8991
self.algorithm.Log("CustomFillModel: " + str(fill))
9092
return fill
9193

92-
class CustomFeeModel:
94+
class CustomFeeModel(FeeModel):
9395
def __init__(self, algorithm):
9496
self.algorithm = algorithm
9597

96-
def GetOrderFee(self, security, order):
98+
def GetOrderFee(self, parameters):
9799
# custom fee math
98-
fee = max(1, security.Price * order.AbsoluteQuantity * d.Decimal(0.00001))
100+
fee = max(1, parameters.Security.Price
101+
* parameters.Order.AbsoluteQuantity
102+
* d.Decimal(0.00001))
99103
self.algorithm.Log("CustomFeeModel: " + str(fee))
100-
return fee
104+
return OrderFee(CashAmount(
105+
fee,
106+
parameters.Security.QuoteCurrency.AccountCurrency))
101107

102108
class CustomSlippageModel:
103109
def __init__(self, algorithm):

Brokerages/Backtesting/BacktestingBrokerage.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using QuantConnect.Logging;
2222
using QuantConnect.Orders;
2323
using QuantConnect.Orders.Fills;
24+
using QuantConnect.Orders.Fees;
2425
using QuantConnect.Securities;
2526
using QuantConnect.Securities.Option;
2627

@@ -347,7 +348,8 @@ public virtual void Scan()
347348
// TODO : This check can be removed in April, 2019 -- a 6-month window to upgrade (also, suspect small % of users, if any are impacted)
348349
if (fill.OrderFee == 0m)
349350
{
350-
fill.OrderFee = security.FeeModel.GetOrderFee(security, order);
351+
fill.OrderFee = security.FeeModel.GetOrderFee(
352+
new OrderFeeParameters(security, order)).Value.Amount;
351353
}
352354
}
353355
}

Brokerages/Backtesting/BasicOptionAssignmentSimulation.cs

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
22
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
33
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4-
*
5-
* Licensed under the Apache License, Version 2.0 (the "License");
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
77
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8-
*
8+
*
99
* Unless required by applicable law or agreed to in writing, software
1010
* distributed under the License is distributed on an "AS IS" BASIS,
1111
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,6 +20,7 @@
2020
using QuantConnect.Interfaces;
2121
using QuantConnect.Logging;
2222
using QuantConnect.Orders;
23+
using QuantConnect.Orders.Fees;
2324
using QuantConnect.Securities;
2425
using QuantConnect.Securities.Option;
2526
using QuantConnect.Util;
@@ -28,8 +29,8 @@ namespace QuantConnect.Brokerages.Backtesting
2829
{
2930
/// <summary>
3031
/// This market conditions simulator emulates exercising of short option positions in the portfolio.
31-
/// Simulator implements basic no-arb argument: when time value of the option contract is close to zero
32-
/// it assigns short legs getting profit close to expiration dates in deep ITM positions. User algorithm then receives
32+
/// Simulator implements basic no-arb argument: when time value of the option contract is close to zero
33+
/// it assigns short legs getting profit close to expiration dates in deep ITM positions. User algorithm then receives
3334
/// assignment event from LEAN. Simulator randomly scans for arbitrage opportunities every two hours or so.
3435
/// </summary>
3536
public class BasicOptionAssignmentSimulation : IBacktestingMarketSimulation
@@ -52,7 +53,7 @@ public class BasicOptionAssignmentSimulation : IBacktestingMarketSimulation
5253
private static Random _rand = new Random((int)12345);
5354

5455
/// <summary>
55-
/// We generate a list of time points when we would like to run our simulation. we then return true if the time is in the list.
56+
/// We generate a list of time points when we would like to run our simulation. we then return true if the time is in the list.
5657
/// </summary>
5758
/// <returns></returns>
5859
public bool IsReadyToSimulate(IAlgorithm algorithm)
@@ -83,10 +84,10 @@ public bool IsReadyToSimulate(IAlgorithm algorithm)
8384
}
8485
}
8586
var randomizedScans = scans
86-
.DistinctBy(x => new DateTime(x.Year, x.Month, x.Day, x.Hour, 0, 0)) // DistinctBy hour
87+
.DistinctBy(x => new DateTime(x.Year, x.Month, x.Day, x.Hour, 0, 0)) // DistinctBy hour
8788
.OrderBy(x => x)
8889
.Select(x => x.AddMinutes(_rand.NextDouble() * _assignmentScanPeriod.TotalMinutes));
89-
90+
9091
_assignmentScans = new Queue<DateTime>(randomizedScans);
9192

9293
_lastUpdate = algorithm.UtcTime;
@@ -98,7 +99,7 @@ public bool IsReadyToSimulate(IAlgorithm algorithm)
9899
// we fast forward through unused items
99100
if (algorithm.UtcTime >= _assignmentScans.Peek())
100101
{
101-
while (_assignmentScans.Count > 0 &&
102+
while (_assignmentScans.Count > 0 &&
102103
algorithm.UtcTime >= _assignmentScans.Peek())
103104
{
104105
_assignmentScans.Dequeue();
@@ -115,7 +116,7 @@ public bool IsReadyToSimulate(IAlgorithm algorithm)
115116

116117
/// <summary>
117118
/// We simulate activity of market makers on expiration. Trying to get profit close to expiration dates in deep ITM positions.
118-
/// This version of the simulator exercises short positions in full.
119+
/// This version of the simulator exercises short positions in full.
119120
/// </summary>
120121
public void SimulateMarketConditions(IBrokerage brokerage, IAlgorithm algorithm)
121122
{
@@ -153,16 +154,16 @@ public void SimulateMarketConditions(IBrokerage brokerage, IAlgorithm algorithm)
153154

154155
private decimal EstimateArbitragePnL(Option option, OptionHolding holding, Security underlying)
155156
{
156-
// no-arb argument:
157-
// if our long deep ITM position has a large B/A spread and almost no time value, it may be interesting for us
158-
// to exercise the option and close the resulting position in underlying instrument, if we want to exit now.
157+
// no-arb argument:
158+
// if our long deep ITM position has a large B/A spread and almost no time value, it may be interesting for us
159+
// to exercise the option and close the resulting position in underlying instrument, if we want to exit now.
159160

160-
// User's short option position is our long one.
161+
// User's short option position is our long one.
161162
// In order to sell ITM position we take option bid price as an input
162163
var optionPrice = option.BidPrice;
163164

164-
// we are interested in underlying bid price if we exercise calls and want to sell the underlying immediately.
165-
// we are interested in underlying ask price if we exercise puts
165+
// we are interested in underlying bid price if we exercise calls and want to sell the underlying immediately.
166+
// we are interested in underlying ask price if we exercise puts
166167
var underlyingPrice = option.Symbol.ID.OptionRight == OptionRight.Call ?
167168
underlying.BidPrice :
168169
underlying.AskPrice;
@@ -173,22 +174,28 @@ private decimal EstimateArbitragePnL(Option option, OptionHolding holding, Secur
173174

174175
// Scenario 1 (base): we just close option position
175176
var marketOrder1 = new MarketOrder(option.Symbol, -holding.Quantity, option.LocalTime.ConvertToUtc(option.Exchange.TimeZone));
176-
var orderFee1 = option.FeeModel.GetOrderFee(option, marketOrder1);
177+
var orderFee1 = option.FeeModel.GetOrderFee(
178+
new OrderFeeParameters(option, marketOrder1));
177179

178-
var basePnL = (optionPrice - holding.AveragePrice) * -holding.Quantity * option.QuoteCurrency.ConversionRate * option.SymbolProperties.ContractMultiplier - orderFee1;
180+
var basePnL = (optionPrice - holding.AveragePrice) * -holding.Quantity
181+
* option.QuoteCurrency.ConversionRate
182+
* option.SymbolProperties.ContractMultiplier
183+
- orderFee1.Value.Amount;
179184

180185
// Scenario 2 (alternative): we exercise option and then close underlying position
181186
var optionExerciseOrder2 = new OptionExerciseOrder(option.Symbol, (int)holding.AbsoluteQuantity, option.LocalTime.ConvertToUtc(option.Exchange.TimeZone));
182-
var optionOrderFee2 = option.FeeModel.GetOrderFee(option, optionExerciseOrder2);
187+
var optionOrderFee2 = option.FeeModel.GetOrderFee(
188+
new OrderFeeParameters(option, optionExerciseOrder2));
183189

184190
var undelyingMarketOrder2 = new MarketOrder(underlying.Symbol, -underlyingQuantity, underlying.LocalTime.ConvertToUtc(underlying.Exchange.TimeZone));
185-
var undelyingOrderFee2 = underlying.FeeModel.GetOrderFee(underlying, undelyingMarketOrder2);
191+
var undelyingOrderFee2 = underlying.FeeModel.GetOrderFee(
192+
new OrderFeeParameters(underlying, undelyingMarketOrder2));
186193

187194
// calculating P/L of the two transactions (exercise option and then close underlying position)
188195
var altPnL = (underlyingPrice - option.StrikePrice) * underlyingQuantity * underlying.QuoteCurrency.ConversionRate * option.ContractUnitOfTrade
189-
- undelyingOrderFee2
196+
- undelyingOrderFee2.Value.Amount
190197
- holding.AveragePrice * holding.AbsoluteQuantity * option.SymbolProperties.ContractMultiplier * option.QuoteCurrency.ConversionRate
191-
- optionOrderFee2;
198+
- optionOrderFee2.Value.Amount;
192199

193200
return altPnL - basePnL;
194201
}

Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using QuantConnect.Interfaces;
2020
using QuantConnect.Logging;
2121
using QuantConnect.Orders;
22+
using QuantConnect.Orders.Fees;
2223
using QuantConnect.Securities;
2324
using QuantConnect.Util;
2425
using RestSharp;
@@ -569,7 +570,8 @@ private void EmitFillOrder(string[] entries)
569570
if (fillQuantity != 0)
570571
{
571572
var security = _securityProvider.GetSecurity(order.Symbol);
572-
orderFee = security.FeeModel.GetOrderFee(security, order);
573+
orderFee = security.FeeModel.GetOrderFee(
574+
new OrderFeeParameters(security, order)).Value.Amount;
573575
}
574576

575577
OrderStatus status = fillQuantity == order.Quantity ? OrderStatus.Filled : OrderStatus.PartiallyFilled;

Brokerages/Fxcm/FxcmBrokerage.Messaging.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
using QuantConnect.Data.Market;
3030
using QuantConnect.Logging;
3131
using QuantConnect.Orders;
32+
using QuantConnect.Orders.Fees;
3233

3334
namespace QuantConnect.Brokerages.Fxcm
3435
{
@@ -416,7 +417,8 @@ private void OnExecutionReport(ExecutionReport message)
416417
if ((int)message.getCumQty() == (int)message.getLastQty() && message.getLastQty() > 0)
417418
{
418419
var security = _securityProvider.GetSecurity(order.Symbol);
419-
orderEvent.OrderFee = security.FeeModel.GetOrderFee(security, order);
420+
orderEvent.OrderFee = security.FeeModel.GetOrderFee(
421+
new OrderFeeParameters(security, order)).Value.Amount;
420422
}
421423

422424
_orderEventQueue.Enqueue(orderEvent);

Brokerages/Tradier/TradierBrokerage.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
using QuantConnect.Interfaces;
2828
using QuantConnect.Logging;
2929
using QuantConnect.Orders;
30+
using QuantConnect.Orders.Fees;
3031
using QuantConnect.Orders.TimeInForces;
3132
using QuantConnect.Securities;
3233
using QuantConnect.Securities.Equity;
@@ -1351,7 +1352,8 @@ private void ProcessPotentiallyUpdatedOrder(TradierCachedOpenOrder cachedOrder,
13511352
{
13521353
cachedOrder.EmittedOrderFee = true;
13531354
var security = _securityProvider.GetSecurity(qcOrder.Symbol);
1354-
fill.OrderFee = security.FeeModel.GetOrderFee(security, qcOrder);
1355+
fill.OrderFee = security.FeeModel.GetOrderFee(
1356+
new OrderFeeParameters(security, qcOrder)).Value.Amount;
13551357
}
13561358

13571359
// if we filled the order and have another contingent order waiting, submit it

Common/Orders/Fees/BitfinexFeeModel.cs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
* limitations under the License.
1414
*/
1515

16+
using QuantConnect.Securities;
17+
1618
namespace QuantConnect.Orders.Fees
1719
{
1820
/// <summary>
19-
/// Provides an implementation of <see cref="IFeeModel"/> that models Bitfinex order fees
21+
/// Provides an implementation of <see cref="FeeModel"/> that models Bitfinex order fees
2022
/// </summary>
21-
public class BitfinexFeeModel : IFeeModel
23+
public class BitfinexFeeModel : FeeModel
2224
{
2325
/// <summary>
2426
/// Tier 1 maker fees
@@ -37,16 +39,18 @@ public class BitfinexFeeModel : IFeeModel
3739
/// <summary>
3840
/// Get the fee for this order in units of the account currency
3941
/// </summary>
40-
/// <param name="security">The security matching the order</param>
41-
/// <param name="order">The order to compute fees for</param>
42+
/// <param name="parameters">A <see cref="OrderFeeParameters"/> object
43+
/// containing the security and order</param>
4244
/// <returns>The cost of the order in units of the account currency</returns>
43-
public decimal GetOrderFee(Securities.Security security, Order order)
45+
public override OrderFee GetOrderFee(OrderFeeParameters parameters)
4446
{
47+
var order = parameters.Order;
48+
var security = parameters.Security;
4549
decimal fee = TakerFee;
4650
var props = order.Properties as BitfinexOrderProperties;
47-
51+
4852
if (order.Type == OrderType.Limit &&
49-
props?.Hidden != true &&
53+
props?.Hidden != true &&
5054
(props?.PostOnly == true || !order.IsMarketable))
5155
{
5256
// limit order posted to the order book
@@ -64,7 +68,9 @@ public decimal GetOrderFee(Securities.Security security, Order order)
6468
unitPrice *= security.QuoteCurrency.ConversionRate * security.SymbolProperties.ContractMultiplier;
6569

6670
// apply fee factor, currently we do not model 30-day volume, so we use the first tier
67-
return unitPrice * order.AbsoluteQuantity * fee;
71+
return new OrderFee(new CashAmount(
72+
unitPrice * order.AbsoluteQuantity * fee,
73+
security.QuoteCurrency.AccountCurrency));
6874
}
6975
}
7076
}

Common/Orders/Fees/ConstantFeeModel.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
22
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
33
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4-
*
5-
* Licensed under the Apache License, Version 2.0 (the "License");
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
77
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8-
*
8+
*
99
* Unless required by applicable law or agreed to in writing, software
1010
* distributed under the License is distributed on an "AS IS" BASIS,
1111
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -21,7 +21,7 @@ namespace QuantConnect.Orders.Fees
2121
/// <summary>
2222
/// Provides an order fee model that always returns the same order fee.
2323
/// </summary>
24-
public class ConstantFeeModel : IFeeModel
24+
public class ConstantFeeModel : FeeModel
2525
{
2626
private readonly decimal _fee;
2727

@@ -35,14 +35,15 @@ public ConstantFeeModel(decimal fee)
3535
}
3636

3737
/// <summary>
38-
/// Returns the constant fee for the model
38+
/// Returns the constant fee for the model in units of the account currency
3939
/// </summary>
40-
/// <param name="security">The security matching the order</param>
41-
/// <param name="order">The order to compute fees for</param>
40+
/// <param name="parameters">A <see cref="OrderFeeParameters"/> object
41+
/// containing the security and order</param>
4242
/// <returns>The cost of the order in units of the account currency</returns>
43-
public decimal GetOrderFee(Security security, Order order)
43+
public override OrderFee GetOrderFee(OrderFeeParameters parameters)
4444
{
45-
return _fee;
45+
return new OrderFee(new CashAmount(_fee,
46+
parameters.Security.QuoteCurrency.AccountCurrency));
4647
}
4748
}
4849
}

0 commit comments

Comments
 (0)