-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
/
CustomModelsAlgorithm.py
181 lines (146 loc) · 7.92 KB
/
CustomModelsAlgorithm.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# 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.
from AlgorithmImports import *
import random
### <summary>
### Demonstration of using custom fee, slippage, fill, and buying power models for modelling transactions in backtesting.
### QuantConnect allows you to model all orders as deeply and accurately as you need.
### </summary>
### <meta name="tag" content="trading and orders" />
### <meta name="tag" content="transaction fees and slippage" />
### <meta name="tag" content="custom buying power models" />
### <meta name="tag" content="custom transaction models" />
### <meta name="tag" content="custom slippage models" />
### <meta name="tag" content="custom fee models" />
class CustomModelsAlgorithm(QCAlgorithm):
'''Demonstration of using custom fee, slippage, fill, and buying power models for modelling transactions in backtesting.
QuantConnect allows you to model all orders as deeply and accurately as you need.'''
def initialize(self):
self.set_start_date(2013,10,1) # Set Start Date
self.set_end_date(2013,10,31) # Set End Date
self.security = self.add_equity("SPY", Resolution.HOUR)
self.spy = self.security.symbol
# set our models
self.security.set_fee_model(CustomFeeModel(self))
self.security.set_fill_model(CustomFillModel(self))
self.security.set_slippage_model(CustomSlippageModel(self))
self.security.set_buying_power_model(CustomBuyingPowerModel(self))
def on_data(self, data):
open_orders = self.transactions.get_open_orders(self.spy)
if len(open_orders) != 0: return
if self.time.day > 10 and self.security.holdings.quantity <= 0:
quantity = self.calculate_order_quantity(self.spy, .5)
self.log(f"MarketOrder: {quantity}")
self.market_order(self.spy, quantity, True) # async needed for partial fill market orders
elif self.time.day > 20 and self.security.holdings.quantity >= 0:
quantity = self.calculate_order_quantity(self.spy, -.5)
self.log(f"MarketOrder: {quantity}")
self.market_order(self.spy, quantity, True) # async needed for partial fill market orders
# If we want to use methods from other models, you need to inherit from one of them
class CustomFillModel(ImmediateFillModel):
def __init__(self, algorithm):
super().__init__()
self.algorithm = algorithm
self.absolute_remaining_by_order_id = {}
self.random = Random(387510346)
def market_fill(self, asset, order):
absolute_remaining = order.absolute_quantity
if order.id in self.absolute_remaining_by_order_id.keys():
absolute_remaining = self.absolute_remaining_by_order_id[order.id]
fill = super().market_fill(asset, order)
absolute_fill_quantity = int(min(absolute_remaining, self.random.next(0, 2*int(order.absolute_quantity))))
fill.fill_quantity = np.sign(order.quantity) * absolute_fill_quantity
if absolute_remaining == absolute_fill_quantity:
fill.status = OrderStatus.FILLED
if self.absolute_remaining_by_order_id.get(order.id):
self.absolute_remaining_by_order_id.pop(order.id)
else:
absolute_remaining = absolute_remaining - absolute_fill_quantity
self.absolute_remaining_by_order_id[order.id] = absolute_remaining
fill.status = OrderStatus.PARTIALLY_FILLED
self.algorithm.log(f"CustomFillModel: {fill}")
return fill
class CustomFeeModel(FeeModel):
def __init__(self, algorithm):
super().__init__()
self.algorithm = algorithm
def get_order_fee(self, parameters):
# custom fee math
fee = max(1, parameters.security.price
* parameters.order.absolute_quantity
* 0.00001)
self.algorithm.log(f"CustomFeeModel: {fee}")
return OrderFee(CashAmount(fee, "USD"))
class CustomSlippageModel:
def __init__(self, algorithm):
self.algorithm = algorithm
def get_slippage_approximation(self, asset, order):
# custom slippage math
slippage = asset.price * 0.0001 * np.log10(2*float(order.absolute_quantity))
self.algorithm.log(f"CustomSlippageModel: {slippage}")
return slippage
class CustomBuyingPowerModel(BuyingPowerModel):
def __init__(self, algorithm):
super().__init__()
self.algorithm = algorithm
def has_sufficient_buying_power_for_order(self, parameters):
# custom behavior: this model will assume that there is always enough buying power
has_sufficient_buying_power_for_order_result = HasSufficientBuyingPowerForOrderResult(True)
self.algorithm.log(f"CustomBuyingPowerModel: {has_sufficient_buying_power_for_order_result.is_sufficient}")
return has_sufficient_buying_power_for_order_result
# The simple fill model shows how to implement a simpler version of
# the most popular order fills: Market, Stop Market and Limit
class SimpleCustomFillModel(FillModel):
def __init__(self):
super().__init__()
def _create_order_event(self, asset, order):
utc_time = Extensions.convert_to_utc(asset.local_time, asset.exchange.time_zone)
return OrderEvent(order, utc_time, OrderFee.ZERO)
def _set_order_event_to_filled(self, fill, fill_price, fill_quantity):
fill.status = OrderStatus.FILLED
fill.fill_quantity = fill_quantity
fill.fill_price = fill_price
return fill
def _get_trade_bar(self, asset, order_direction):
trade_bar = asset.cache.get_data[TradeBar]()
if trade_bar: return trade_bar
# Tick-resolution data doesn't have TradeBar, use the asset price
price = asset.price
return TradeBar(asset.local_time, asset.symbol, price, price, price, price, 0)
def market_fill(self, asset, order):
fill = self._create_order_event(asset, order)
if order.status == OrderStatus.CANCELED: return fill
return self._set_order_event_to_filled(fill,
asset.cache.ask_price \
if order.direction == OrderDirection.BUY else asset.cache.bid_price,
order.quantity)
def stop_market_fill(self, asset, order):
fill = self._create_order_event(asset, order)
if order.status == OrderStatus.CANCELED: return fill
stop_price = order.stop_price
trade_bar = self._get_trade_bar(asset, order.direction)
if order.direction == OrderDirection.SELL and trade_bar.low < stop_price:
return self._set_order_event_to_filled(fill, stop_price, order.quantity)
if order.direction == OrderDirection.BUY and trade_bar.high > stop_price:
return self._set_order_event_to_filled(fill, stop_price, order.quantity)
return fill
def limit_fill(self, asset, order):
fill = self._create_order_event(asset, order)
if order.status == OrderStatus.CANCELED: return fill
limit_price = order.limit_price
trade_bar = self._get_trade_bar(asset, order.direction)
if order.direction == OrderDirection.SELL and trade_bar.high > limit_price:
return self._set_order_event_to_filled(fill, limit_price, order.quantity)
if order.direction == OrderDirection.BUY and trade_bar.low < limit_price:
return self._set_order_event_to_filled(fill, limit_price, order.quantity)
return fill