1
|
|
|
# |
2
|
|
|
# Copyright 2014 Quantopian, Inc. |
3
|
|
|
# |
4
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
5
|
|
|
# you may not use this file except in compliance with the License. |
6
|
|
|
# You may obtain a copy of the License at |
7
|
|
|
# |
8
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0 |
9
|
|
|
# |
10
|
|
|
# Unless required by applicable law or agreed to in writing, software |
11
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS, |
12
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13
|
|
|
# See the License for the specific language governing permissions and |
14
|
|
|
# limitations under the License. |
15
|
|
|
|
16
|
|
|
|
17
|
|
|
""" |
18
|
|
|
Algorithm Protocol |
19
|
|
|
=================== |
20
|
|
|
|
21
|
|
|
For a class to be passed as a trading algorithm to the |
22
|
|
|
:py:class:`zipline.lines.SimulatedTrading` zipline it must follow an |
23
|
|
|
implementation protocol. Examples of this algorithm protocol are provided |
24
|
|
|
below. |
25
|
|
|
|
26
|
|
|
The algorithm must expose methods: |
27
|
|
|
|
28
|
|
|
- initialize: method that takes no args, no returns. Simply called to |
29
|
|
|
enable the algorithm to set any internal state needed. |
30
|
|
|
|
31
|
|
|
- get_sid_filter: method that takes no args, and returns a list of valid |
32
|
|
|
sids. List must have a length between 1 and 10. If None is returned the |
33
|
|
|
filter will block all events. |
34
|
|
|
|
35
|
|
|
- handle_data: method that accepts a :py:class:`zipline.protocol.BarData` |
36
|
|
|
of the current state of the simulation universe. An example data object: |
37
|
|
|
|
38
|
|
|
.. This outputs the table as an HTML table but for some reason there |
39
|
|
|
is no bounding box. Make the previous paraagraph ending colon a |
40
|
|
|
double-colon to turn this back into blockquoted table in ASCII art. |
41
|
|
|
|
42
|
|
|
+-----------------+--------------+----------------+-------------------+ |
43
|
|
|
| | sid(133) | sid(134) | sid(135) | |
44
|
|
|
+=================+==============+================+===================+ |
45
|
|
|
| price | $10.10 | $22.50 | $13.37 | |
46
|
|
|
+-----------------+--------------+----------------+-------------------+ |
47
|
|
|
| volume | 10,000 | 5,000 | 50,000 | |
48
|
|
|
+-----------------+--------------+----------------+-------------------+ |
49
|
|
|
| mvg_avg_30 | $9.97 | $22.61 | $13.37 | |
50
|
|
|
+-----------------+--------------+----------------+-------------------+ |
51
|
|
|
| dt | 6/30/2012 | 6/30/2011 | 6/29/2012 | |
52
|
|
|
+-----------------+--------------+----------------+-------------------+ |
53
|
|
|
|
54
|
|
|
- set_order: method that accepts a callable. Will be set as the value of the |
55
|
|
|
order method of trading_client. An algorithm can then place orders with a |
56
|
|
|
valid sid and a number of shares:: |
57
|
|
|
|
58
|
|
|
self.order(sid(133), share_count) |
59
|
|
|
|
60
|
|
|
- set_performance: property which can be set equal to the |
61
|
|
|
cumulative_trading_performance property of the trading_client. An |
62
|
|
|
algorithm can then check position information with the |
63
|
|
|
Portfolio object:: |
64
|
|
|
|
65
|
|
|
self.Portfolio[sid(133)]['cost_basis'] |
66
|
|
|
|
67
|
|
|
- set_transact_setter: method that accepts a callable. Will |
68
|
|
|
be set as the value of the set_transact_setter method of |
69
|
|
|
the trading_client. This allows an algorithm to change the |
70
|
|
|
slippage model used to predict transactions based on orders |
71
|
|
|
and trade events. |
72
|
|
|
|
73
|
|
|
""" |
74
|
|
|
import numpy as np |
75
|
|
|
|
76
|
|
|
from nose.tools import assert_raises |
77
|
|
|
|
78
|
|
|
from six.moves import range |
79
|
|
|
from six import itervalues |
80
|
|
|
|
81
|
|
|
from zipline.algorithm import TradingAlgorithm |
82
|
|
|
from zipline.api import ( |
83
|
|
|
FixedSlippage, |
84
|
|
|
order, |
85
|
|
|
set_slippage, |
86
|
|
|
record, |
87
|
|
|
sid, |
88
|
|
|
) |
89
|
|
|
from zipline.errors import UnsupportedOrderParameters |
90
|
|
|
from zipline.assets import Future, Equity |
91
|
|
|
from zipline.finance.commission import PerShare |
92
|
|
|
from zipline.finance.execution import ( |
93
|
|
|
LimitOrder, |
94
|
|
|
MarketOrder, |
95
|
|
|
StopLimitOrder, |
96
|
|
|
StopOrder, |
97
|
|
|
) |
98
|
|
|
from zipline.finance.controls import AssetDateBounds |
99
|
|
|
from zipline.utils.math_utils import round_if_near_integer |
100
|
|
|
|
101
|
|
|
|
102
|
|
|
class TestAlgorithm(TradingAlgorithm): |
103
|
|
|
""" |
104
|
|
|
This algorithm will send a specified number of orders, to allow unit tests |
105
|
|
|
to verify the orders sent/received, transactions created, and positions |
106
|
|
|
at the close of a simulation. |
107
|
|
|
""" |
108
|
|
|
|
109
|
|
|
def initialize(self, |
110
|
|
|
sid, |
111
|
|
|
amount, |
112
|
|
|
order_count, |
113
|
|
|
sid_filter=None, |
114
|
|
|
slippage=None, |
115
|
|
|
commission=None): |
116
|
|
|
self.count = order_count |
117
|
|
|
self.asset = self.sid(sid) |
118
|
|
|
self.amount = amount |
119
|
|
|
self.incr = 0 |
120
|
|
|
|
121
|
|
|
if sid_filter: |
122
|
|
|
self.sid_filter = sid_filter |
123
|
|
|
else: |
124
|
|
|
self.sid_filter = [self.asset.sid] |
125
|
|
|
|
126
|
|
|
if slippage is not None: |
127
|
|
|
self.set_slippage(slippage) |
128
|
|
|
|
129
|
|
|
if commission is not None: |
130
|
|
|
self.set_commission(commission) |
131
|
|
|
|
132
|
|
|
def handle_data(self, data): |
133
|
|
|
# place an order for amount shares of sid |
134
|
|
|
if self.incr < self.count: |
135
|
|
|
self.order(self.asset, self.amount) |
136
|
|
|
self.incr += 1 |
137
|
|
|
|
138
|
|
|
|
139
|
|
|
class HeavyBuyAlgorithm(TradingAlgorithm): |
140
|
|
|
""" |
141
|
|
|
This algorithm will send a specified number of orders, to allow unit tests |
142
|
|
|
to verify the orders sent/received, transactions created, and positions |
143
|
|
|
at the close of a simulation. |
144
|
|
|
""" |
145
|
|
|
|
146
|
|
|
def initialize(self, sid, amount): |
147
|
|
|
self.asset = self.sid(sid) |
148
|
|
|
self.amount = amount |
149
|
|
|
self.incr = 0 |
150
|
|
|
|
151
|
|
|
def handle_data(self, data): |
152
|
|
|
# place an order for 100 shares of sid |
153
|
|
|
self.order(self.asset, self.amount) |
154
|
|
|
self.incr += 1 |
155
|
|
|
|
156
|
|
|
|
157
|
|
|
class NoopAlgorithm(TradingAlgorithm): |
158
|
|
|
""" |
159
|
|
|
Dolce fa niente. |
160
|
|
|
""" |
161
|
|
|
def initialize(self): |
162
|
|
|
pass |
163
|
|
|
|
164
|
|
|
def handle_data(self, data): |
165
|
|
|
pass |
166
|
|
|
|
167
|
|
|
|
168
|
|
|
class ExceptionAlgorithm(TradingAlgorithm): |
169
|
|
|
""" |
170
|
|
|
Throw an exception from the method name specified in the |
171
|
|
|
constructor. |
172
|
|
|
""" |
173
|
|
|
|
174
|
|
|
def initialize(self, throw_from, sid): |
175
|
|
|
|
176
|
|
|
self.throw_from = throw_from |
177
|
|
|
self.asset = self.sid(sid) |
178
|
|
|
|
179
|
|
|
if self.throw_from == "initialize": |
180
|
|
|
raise Exception("Algo exception in initialize") |
181
|
|
|
else: |
182
|
|
|
pass |
183
|
|
|
|
184
|
|
|
def set_portfolio(self, portfolio): |
185
|
|
|
if self.throw_from == "set_portfolio": |
186
|
|
|
raise Exception("Algo exception in set_portfolio") |
187
|
|
|
else: |
188
|
|
|
pass |
189
|
|
|
|
190
|
|
|
def handle_data(self, data): |
191
|
|
|
if self.throw_from == "handle_data": |
192
|
|
|
raise Exception("Algo exception in handle_data") |
193
|
|
|
else: |
194
|
|
|
pass |
195
|
|
|
|
196
|
|
|
def get_sid_filter(self): |
197
|
|
|
if self.throw_from == "get_sid_filter": |
198
|
|
|
raise Exception("Algo exception in get_sid_filter") |
199
|
|
|
else: |
200
|
|
|
return [self.asset] |
201
|
|
|
|
202
|
|
|
def set_transact_setter(self, txn_sim_callable): |
203
|
|
|
pass |
204
|
|
|
|
205
|
|
|
|
206
|
|
|
class DivByZeroAlgorithm(TradingAlgorithm): |
207
|
|
|
|
208
|
|
|
def initialize(self, sid): |
209
|
|
|
self.asset = self.sid(sid) |
210
|
|
|
self.incr = 0 |
211
|
|
|
|
212
|
|
|
def handle_data(self, data): |
213
|
|
|
self.incr += 1 |
214
|
|
|
if self.incr > 1: |
215
|
|
|
5 / 0 |
216
|
|
|
pass |
217
|
|
|
|
218
|
|
|
|
219
|
|
|
class TooMuchProcessingAlgorithm(TradingAlgorithm): |
220
|
|
|
|
221
|
|
|
def initialize(self, sid): |
222
|
|
|
self.asset = self.sid(sid) |
223
|
|
|
|
224
|
|
|
def handle_data(self, data): |
225
|
|
|
# Unless we're running on some sort of |
226
|
|
|
# supercomputer this will hit timeout. |
227
|
|
|
for i in range(1000000000): |
228
|
|
|
self.foo = i |
229
|
|
|
|
230
|
|
|
|
231
|
|
|
class TimeoutAlgorithm(TradingAlgorithm): |
232
|
|
|
|
233
|
|
|
def initialize(self, sid): |
234
|
|
|
self.asset = self.sid(sid) |
235
|
|
|
self.incr = 0 |
236
|
|
|
|
237
|
|
|
def handle_data(self, data): |
238
|
|
|
if self.incr > 4: |
239
|
|
|
import time |
240
|
|
|
time.sleep(100) |
241
|
|
|
pass |
242
|
|
|
|
243
|
|
|
|
244
|
|
|
class RecordAlgorithm(TradingAlgorithm): |
245
|
|
|
def initialize(self): |
246
|
|
|
self.incr = 0 |
247
|
|
|
|
248
|
|
|
def handle_data(self, data): |
249
|
|
|
self.incr += 1 |
250
|
|
|
self.record(incr=self.incr) |
251
|
|
|
name = 'name' |
252
|
|
|
self.record(name, self.incr) |
253
|
|
|
record(name, self.incr, 'name2', 2, name3=self.incr) |
254
|
|
|
|
255
|
|
|
|
256
|
|
|
class TestOrderAlgorithm(TradingAlgorithm): |
257
|
|
|
def initialize(self): |
258
|
|
|
self.incr = 0 |
259
|
|
|
|
260
|
|
|
def handle_data(self, data): |
261
|
|
|
if self.incr == 0: |
|
|
|
|
262
|
|
|
assert 0 not in self.portfolio.positions |
263
|
|
|
else: |
264
|
|
|
assert self.portfolio.positions[0]['amount'] == \ |
265
|
|
|
self.incr, "Orders not filled immediately." |
266
|
|
|
assert self.portfolio.positions[0]['last_sale_price'] == \ |
267
|
|
|
data[0].price, "Orders not filled at current price." |
268
|
|
|
self.incr += 1 |
269
|
|
|
self.order(self.sid(0), 1) |
270
|
|
|
|
271
|
|
|
|
272
|
|
|
class TestOrderInstantAlgorithm(TradingAlgorithm): |
273
|
|
|
def initialize(self): |
274
|
|
|
self.incr = 0 |
275
|
|
|
self.last_price = None |
276
|
|
|
|
277
|
|
|
def handle_data(self, data): |
278
|
|
|
if self.incr == 0: |
279
|
|
|
assert 0 not in self.portfolio.positions |
280
|
|
|
else: |
281
|
|
|
assert self.portfolio.positions[0]['amount'] == \ |
282
|
|
|
self.incr, "Orders not filled immediately." |
283
|
|
|
assert self.portfolio.positions[0]['last_sale_price'] == \ |
284
|
|
|
self.last_price, "Orders was not filled at last price." |
285
|
|
|
self.incr += 1 |
286
|
|
|
self.order_value(self.sid(0), data[0].price) |
287
|
|
|
self.last_price = data[0].price |
288
|
|
|
|
289
|
|
|
|
290
|
|
|
class TestOrderStyleForwardingAlgorithm(TradingAlgorithm): |
291
|
|
|
""" |
292
|
|
|
Test Algorithm for verifying that ExecutionStyles are properly forwarded by |
293
|
|
|
order API helper methods. Pass the name of the method to be tested as a |
294
|
|
|
string parameter to this algorithm's constructor. |
295
|
|
|
""" |
296
|
|
|
|
297
|
|
|
def __init__(self, *args, **kwargs): |
298
|
|
|
self.method_name = kwargs.pop('method_name') |
299
|
|
|
super(TestOrderStyleForwardingAlgorithm, self)\ |
300
|
|
|
.__init__(*args, **kwargs) |
301
|
|
|
|
302
|
|
|
def initialize(self): |
303
|
|
|
self.incr = 0 |
304
|
|
|
self.last_price = None |
305
|
|
|
|
306
|
|
|
def handle_data(self, data): |
307
|
|
|
if self.incr == 0: |
308
|
|
|
assert len(self.portfolio.positions.keys()) == 0 |
309
|
|
|
|
310
|
|
|
method_to_check = getattr(self, self.method_name) |
311
|
|
|
method_to_check(self.sid(133), |
312
|
|
|
data[0].price, |
313
|
|
|
style=StopLimitOrder(10, 10)) |
314
|
|
|
|
315
|
|
|
assert len(self.blotter.open_orders[self.sid(133)]) == 1 |
316
|
|
|
result = self.blotter.open_orders[self.sid(133)][0] |
317
|
|
|
assert result.limit == 10 |
318
|
|
|
assert result.stop == 10 |
319
|
|
|
|
320
|
|
|
self.incr += 1 |
321
|
|
|
|
322
|
|
|
|
323
|
|
|
class TestOrderValueAlgorithm(TradingAlgorithm): |
324
|
|
|
def initialize(self): |
325
|
|
|
self.incr = 0 |
326
|
|
|
self.sale_price = None |
327
|
|
|
|
328
|
|
|
def handle_data(self, data): |
329
|
|
|
if self.incr == 0: |
|
|
|
|
330
|
|
|
assert 0 not in self.portfolio.positions |
331
|
|
|
else: |
332
|
|
|
assert self.portfolio.positions[0]['amount'] == \ |
333
|
|
|
self.incr, "Orders not filled immediately." |
334
|
|
|
assert self.portfolio.positions[0]['last_sale_price'] == \ |
335
|
|
|
data[0].price, "Orders not filled at current price." |
336
|
|
|
self.incr += 2 |
337
|
|
|
|
338
|
|
|
multiplier = 2. |
339
|
|
|
if isinstance(self.sid(0), Future): |
340
|
|
|
multiplier *= self.sid(0).contract_multiplier |
341
|
|
|
|
342
|
|
|
self.order_value(self.sid(0), data[0].price * multiplier) |
343
|
|
|
|
344
|
|
|
|
345
|
|
|
class TestTargetAlgorithm(TradingAlgorithm): |
346
|
|
|
def initialize(self): |
347
|
|
|
self.set_slippage(FixedSlippage()) |
348
|
|
|
self.target_shares = 0 |
349
|
|
|
self.sale_price = None |
350
|
|
|
|
351
|
|
|
def handle_data(self, data): |
352
|
|
|
if self.target_shares == 0: |
|
|
|
|
353
|
|
|
assert 0 not in self.portfolio.positions |
354
|
|
|
else: |
355
|
|
|
assert self.portfolio.positions[0]['amount'] == \ |
356
|
|
|
self.target_shares, "Orders not filled immediately." |
357
|
|
|
assert self.portfolio.positions[0]['last_sale_price'] == \ |
358
|
|
|
data[0].price, "Orders not filled at current price." |
359
|
|
|
self.target_shares = 10 |
360
|
|
|
self.order_target(self.sid(0), self.target_shares) |
361
|
|
|
|
362
|
|
|
|
363
|
|
|
class TestOrderPercentAlgorithm(TradingAlgorithm): |
364
|
|
|
def initialize(self): |
365
|
|
|
self.set_slippage(FixedSlippage()) |
366
|
|
|
self.target_shares = 0 |
367
|
|
|
self.sale_price = None |
368
|
|
|
|
369
|
|
|
def handle_data(self, data): |
370
|
|
|
if self.target_shares == 0: |
|
|
|
|
371
|
|
|
assert 0 not in self.portfolio.positions |
372
|
|
|
self.order(self.sid(0), 10) |
373
|
|
|
self.target_shares = 10 |
374
|
|
|
return |
375
|
|
|
else: |
376
|
|
|
|
377
|
|
|
assert self.portfolio.positions[0]['amount'] == \ |
378
|
|
|
self.target_shares, "Orders not filled immediately." |
379
|
|
|
assert self.portfolio.positions[0]['last_sale_price'] == \ |
380
|
|
|
data[0].price, "Orders not filled at current price." |
381
|
|
|
|
382
|
|
|
self.order_percent(self.sid(0), .001) |
383
|
|
|
|
384
|
|
|
if isinstance(self.sid(0), Equity): |
385
|
|
|
price = data[0].price |
386
|
|
|
new_shares = (.001 * self.portfolio.portfolio_value) / price |
387
|
|
|
elif isinstance(self.sid(0), Future): |
388
|
|
|
new_shares = (.001 * self.portfolio.portfolio_value) / \ |
389
|
|
|
(data[0].price * self.sid(0).contract_multiplier) |
390
|
|
|
|
391
|
|
|
new_shares = int(round_if_near_integer(new_shares)) |
392
|
|
|
self.target_shares += new_shares |
393
|
|
|
|
394
|
|
|
|
395
|
|
|
class TestTargetPercentAlgorithm(TradingAlgorithm): |
396
|
|
|
def initialize(self): |
397
|
|
|
self.ordered = False |
398
|
|
|
self.sale_price = None |
399
|
|
|
|
400
|
|
|
# this makes the math easier to check |
401
|
|
|
self.set_slippage(FixedSlippage()) |
402
|
|
|
self.set_commission(PerShare(0)) |
403
|
|
|
|
404
|
|
|
def handle_data(self, data): |
405
|
|
|
if not self.ordered: |
406
|
|
|
assert 0 not in self.portfolio.positions |
407
|
|
|
else: |
408
|
|
|
# Since you can't own fractional shares (at least in this |
409
|
|
|
# example), we want to make sure that our target amount is |
410
|
|
|
# no more than a share's value away from our current |
411
|
|
|
# holdings. |
412
|
|
|
target_value = self.portfolio.portfolio_value * 0.002 |
413
|
|
|
position_value = self.portfolio.positions[0]['amount'] * \ |
414
|
|
|
self.sale_price |
415
|
|
|
|
416
|
|
|
assert abs(target_value - position_value) <= self.sale_price, \ |
417
|
|
|
"Orders not filled correctly" |
418
|
|
|
|
419
|
|
|
assert self.portfolio.positions[0]['last_sale_price'] == \ |
420
|
|
|
data[0].price, "Orders not filled at current price." |
421
|
|
|
|
422
|
|
|
self.sale_price = data[0].price |
423
|
|
|
self.order_target_percent(self.sid(0), .002) |
424
|
|
|
self.ordered = True |
425
|
|
|
|
426
|
|
|
|
427
|
|
|
class TestTargetValueAlgorithm(TradingAlgorithm): |
428
|
|
|
def initialize(self): |
429
|
|
|
self.set_slippage(FixedSlippage()) |
430
|
|
|
self.target_shares = 0 |
431
|
|
|
self.sale_price = None |
432
|
|
|
|
433
|
|
|
def handle_data(self, data): |
434
|
|
|
if self.target_shares == 0: |
|
|
|
|
435
|
|
|
assert 0 not in self.portfolio.positions |
436
|
|
|
self.order(self.sid(0), 10) |
437
|
|
|
self.target_shares = 10 |
438
|
|
|
return |
439
|
|
|
else: |
440
|
|
|
assert self.portfolio.positions[0]['amount'] == \ |
441
|
|
|
self.target_shares, "Orders not filled immediately." |
442
|
|
|
assert self.portfolio.positions[0]['last_sale_price'] == \ |
443
|
|
|
data[0].price, "Orders not filled at current price." |
444
|
|
|
|
445
|
|
|
self.order_target_value(self.sid(0), 20) |
446
|
|
|
self.target_shares = np.round(20 / data[0].price) |
447
|
|
|
|
448
|
|
|
if isinstance(self.sid(0), Equity): |
449
|
|
|
self.target_shares = np.round(20 / data[0].price) |
450
|
|
|
if isinstance(self.sid(0), Future): |
451
|
|
|
self.target_shares = np.round( |
452
|
|
|
20 / (data[0].price * self.sid(0).contract_multiplier)) |
453
|
|
|
|
454
|
|
|
|
455
|
|
|
class FutureFlipAlgo(TestAlgorithm): |
456
|
|
|
def handle_data(self, data): |
457
|
|
|
if len(self.portfolio.positions) > 0: |
458
|
|
|
if self.portfolio.positions[self.asset.sid]["amount"] > 0: |
459
|
|
|
self.order_target(self.asset, -self.amount) |
460
|
|
|
else: |
461
|
|
|
self.order_target(self.asset, 0) |
462
|
|
|
else: |
463
|
|
|
self.order_target(self.asset, self.amount) |
464
|
|
|
|
465
|
|
|
############################ |
466
|
|
|
# AccountControl Test Algos# |
467
|
|
|
############################ |
468
|
|
|
|
469
|
|
|
|
470
|
|
|
class SetMaxLeverageAlgorithm(TradingAlgorithm): |
471
|
|
|
def initialize(self, max_leverage=None): |
472
|
|
|
self.set_max_leverage(max_leverage=max_leverage) |
473
|
|
|
|
474
|
|
|
|
475
|
|
|
############################ |
476
|
|
|
# TradingControl Test Algos# |
477
|
|
|
############################ |
478
|
|
|
|
479
|
|
|
|
480
|
|
|
class SetMaxPositionSizeAlgorithm(TradingAlgorithm): |
481
|
|
|
def initialize(self, sid=None, max_shares=None, max_notional=None): |
482
|
|
|
self.set_slippage(FixedSlippage()) |
483
|
|
|
self.order_count = 0 |
484
|
|
|
self.set_max_position_size(sid=sid, |
485
|
|
|
max_shares=max_shares, |
486
|
|
|
max_notional=max_notional) |
487
|
|
|
|
488
|
|
|
|
489
|
|
|
class SetMaxOrderSizeAlgorithm(TradingAlgorithm): |
490
|
|
|
def initialize(self, sid=None, max_shares=None, max_notional=None): |
491
|
|
|
self.order_count = 0 |
492
|
|
|
self.set_max_order_size(sid=sid, |
493
|
|
|
max_shares=max_shares, |
494
|
|
|
max_notional=max_notional) |
495
|
|
|
|
496
|
|
|
|
497
|
|
|
class SetDoNotOrderListAlgorithm(TradingAlgorithm): |
498
|
|
|
def initialize(self, sid=None, restricted_list=None): |
499
|
|
|
self.order_count = 0 |
500
|
|
|
self.set_do_not_order_list(restricted_list) |
501
|
|
|
|
502
|
|
|
|
503
|
|
|
class SetMaxOrderCountAlgorithm(TradingAlgorithm): |
504
|
|
|
def initialize(self, count): |
505
|
|
|
self.order_count = 0 |
506
|
|
|
self.set_max_order_count(count) |
507
|
|
|
self.minute_count = 0 |
508
|
|
|
|
509
|
|
|
|
510
|
|
|
class SetLongOnlyAlgorithm(TradingAlgorithm): |
511
|
|
|
def initialize(self): |
512
|
|
|
self.order_count = 0 |
513
|
|
|
self.set_long_only() |
514
|
|
|
|
515
|
|
|
|
516
|
|
|
class SetAssetDateBoundsAlgorithm(TradingAlgorithm): |
517
|
|
|
""" |
518
|
|
|
Algorithm that tries to order 1 share of sid 0 on every bar and has an |
519
|
|
|
AssetDateBounds() trading control in place. |
520
|
|
|
""" |
521
|
|
|
def initialize(self): |
522
|
|
|
self.register_trading_control(AssetDateBounds()) |
523
|
|
|
|
524
|
|
|
def handle_data(algo, data): |
525
|
|
|
algo.order(algo.sid(0), 1) |
526
|
|
|
|
527
|
|
|
|
528
|
|
|
class TestRegisterTransformAlgorithm(TradingAlgorithm): |
529
|
|
|
def initialize(self, *args, **kwargs): |
530
|
|
|
self.set_slippage(FixedSlippage()) |
531
|
|
|
|
532
|
|
|
def handle_data(self, data): |
533
|
|
|
pass |
534
|
|
|
|
535
|
|
|
|
536
|
|
|
class AmbitiousStopLimitAlgorithm(TradingAlgorithm): |
537
|
|
|
""" |
538
|
|
|
Algorithm that tries to buy with extremely low stops/limits and tries to |
539
|
|
|
sell with extremely high versions of same. Should not end up with any |
540
|
|
|
positions for reasonable data. |
541
|
|
|
""" |
542
|
|
|
|
543
|
|
|
def initialize(self, *args, **kwargs): |
544
|
|
|
self.asset = self.sid(kwargs.pop('sid')) |
545
|
|
|
|
546
|
|
|
def handle_data(self, data): |
547
|
|
|
|
548
|
|
|
######## |
549
|
|
|
# Buys # |
550
|
|
|
######## |
551
|
|
|
|
552
|
|
|
# Buy with low limit, shouldn't trigger. |
553
|
|
|
self.order(self.asset, 100, limit_price=1) |
554
|
|
|
|
555
|
|
|
# But with high stop, shouldn't trigger |
556
|
|
|
self.order(self.asset, 100, stop_price=10000000) |
557
|
|
|
|
558
|
|
|
# Buy with high limit (should trigger) but also high stop (should |
559
|
|
|
# prevent trigger). |
560
|
|
|
self.order(self.asset, 100, limit_price=10000000, stop_price=10000000) |
561
|
|
|
|
562
|
|
|
# Buy with low stop (should trigger), but also low limit (should |
563
|
|
|
# prevent trigger). |
564
|
|
|
self.order(self.asset, 100, limit_price=1, stop_price=1) |
565
|
|
|
|
566
|
|
|
######### |
567
|
|
|
# Sells # |
568
|
|
|
######### |
569
|
|
|
|
570
|
|
|
# Sell with high limit, shouldn't trigger. |
571
|
|
|
self.order(self.asset, -100, limit_price=1000000) |
572
|
|
|
|
573
|
|
|
# Sell with low stop, shouldn't trigger. |
574
|
|
|
self.order(self.asset, -100, stop_price=1) |
575
|
|
|
|
576
|
|
|
# Sell with low limit (should trigger), but also high stop (should |
577
|
|
|
# prevent trigger). |
578
|
|
|
self.order(self.asset, -100, limit_price=1000000, stop_price=1000000) |
579
|
|
|
|
580
|
|
|
# Sell with low limit (should trigger), but also low stop (should |
581
|
|
|
# prevent trigger). |
582
|
|
|
self.order(self.asset, -100, limit_price=1, stop_price=1) |
583
|
|
|
|
584
|
|
|
################### |
585
|
|
|
# Rounding Checks # |
586
|
|
|
################### |
587
|
|
|
self.order(self.asset, 100, limit_price=.00000001) |
588
|
|
|
self.order(self.asset, -100, stop_price=.00000001) |
589
|
|
|
|
590
|
|
|
|
591
|
|
|
class SetPortfolioAlgorithm(TradingAlgorithm): |
592
|
|
|
""" |
593
|
|
|
An algorithm that tries to set the portfolio directly. |
594
|
|
|
|
595
|
|
|
The portfolio should be treated as a read-only object |
596
|
|
|
within the algorithm. |
597
|
|
|
""" |
598
|
|
|
|
599
|
|
|
def initialize(self, *args, **kwargs): |
600
|
|
|
pass |
601
|
|
|
|
602
|
|
|
def handle_data(self, data): |
603
|
|
|
self.portfolio = 3 |
604
|
|
|
|
605
|
|
|
|
606
|
|
|
class TALIBAlgorithm(TradingAlgorithm): |
607
|
|
|
""" |
608
|
|
|
An algorithm that applies a TA-Lib transform. The transform object can be |
609
|
|
|
passed at initialization with the 'talib' keyword argument. The results are |
610
|
|
|
stored in the talib_results array. |
611
|
|
|
""" |
612
|
|
|
def initialize(self, *args, **kwargs): |
613
|
|
|
|
614
|
|
|
if 'talib' not in kwargs: |
615
|
|
|
raise KeyError('No TA-LIB transform specified ' |
616
|
|
|
'(use keyword \'talib\').') |
617
|
|
|
elif not isinstance(kwargs['talib'], (list, tuple)): |
618
|
|
|
self.talib_transforms = (kwargs['talib'],) |
619
|
|
|
else: |
620
|
|
|
self.talib_transforms = kwargs['talib'] |
621
|
|
|
|
622
|
|
|
self.talib_results = dict((t, []) for t in self.talib_transforms) |
623
|
|
|
|
624
|
|
|
def handle_data(self, data): |
625
|
|
|
for t in self.talib_transforms: |
626
|
|
|
result = t.handle_data(data) |
627
|
|
|
if result is None: |
628
|
|
|
if len(t.talib_fn.output_names) == 1: |
629
|
|
|
result = np.nan |
630
|
|
|
else: |
631
|
|
|
result = (np.nan,) * len(t.talib_fn.output_names) |
632
|
|
|
self.talib_results[t].append(result) |
633
|
|
|
|
634
|
|
|
|
635
|
|
|
class EmptyPositionsAlgorithm(TradingAlgorithm): |
636
|
|
|
""" |
637
|
|
|
An algorithm that ensures that 'phantom' positions do not appear in |
638
|
|
|
portfolio.positions in the case that a position has been entered |
639
|
|
|
and fully exited. |
640
|
|
|
""" |
641
|
|
|
def initialize(self, sids, *args, **kwargs): |
642
|
|
|
self.ordered = False |
643
|
|
|
self.exited = False |
644
|
|
|
self.sids = sids |
645
|
|
|
|
646
|
|
|
def handle_data(self, data): |
647
|
|
|
if not self.ordered: |
648
|
|
|
for s in self.sids: |
649
|
|
|
self.order(self.sid(s), 1) |
650
|
|
|
self.ordered = True |
651
|
|
|
|
652
|
|
|
if not self.exited: |
653
|
|
|
amounts = [pos.amount for pos |
654
|
|
|
in itervalues(self.portfolio.positions)] |
655
|
|
|
|
656
|
|
|
if ( |
657
|
|
|
len(amounts) > 0 and |
658
|
|
|
all([(amount == 1) for amount in amounts]) |
659
|
|
|
): |
660
|
|
|
for stock in self.portfolio.positions: |
661
|
|
|
self.order(self.sid(stock), -1) |
662
|
|
|
self.exited = True |
663
|
|
|
|
664
|
|
|
# Should be 0 when all positions are exited. |
665
|
|
|
self.record(num_positions=len(self.portfolio.positions)) |
666
|
|
|
|
667
|
|
|
|
668
|
|
|
class InvalidOrderAlgorithm(TradingAlgorithm): |
669
|
|
|
""" |
670
|
|
|
An algorithm that tries to make various invalid order calls, verifying that |
671
|
|
|
appropriate exceptions are raised. |
672
|
|
|
""" |
673
|
|
|
def initialize(self, *args, **kwargs): |
674
|
|
|
self.asset = self.sid(kwargs.pop('sids')[0]) |
675
|
|
|
|
676
|
|
|
def handle_data(self, data): |
677
|
|
|
from zipline.api import ( |
678
|
|
|
order_percent, |
679
|
|
|
order_target, |
680
|
|
|
order_target_percent, |
681
|
|
|
order_target_value, |
682
|
|
|
order_value, |
683
|
|
|
) |
684
|
|
|
|
685
|
|
|
for style in [MarketOrder(), LimitOrder(10), |
686
|
|
|
StopOrder(10), StopLimitOrder(10, 10)]: |
687
|
|
|
|
688
|
|
|
with assert_raises(UnsupportedOrderParameters): |
689
|
|
|
order(self.asset, 10, limit_price=10, style=style) |
690
|
|
|
|
691
|
|
|
with assert_raises(UnsupportedOrderParameters): |
692
|
|
|
order(self.asset, 10, stop_price=10, style=style) |
693
|
|
|
|
694
|
|
|
with assert_raises(UnsupportedOrderParameters): |
695
|
|
|
order_value(self.asset, 300, limit_price=10, style=style) |
696
|
|
|
|
697
|
|
|
with assert_raises(UnsupportedOrderParameters): |
698
|
|
|
order_value(self.asset, 300, stop_price=10, style=style) |
699
|
|
|
|
700
|
|
|
with assert_raises(UnsupportedOrderParameters): |
701
|
|
|
order_percent(self.asset, .1, limit_price=10, style=style) |
702
|
|
|
|
703
|
|
|
with assert_raises(UnsupportedOrderParameters): |
704
|
|
|
order_percent(self.asset, .1, stop_price=10, style=style) |
705
|
|
|
|
706
|
|
|
with assert_raises(UnsupportedOrderParameters): |
707
|
|
|
order_target(self.asset, 100, limit_price=10, style=style) |
708
|
|
|
|
709
|
|
|
with assert_raises(UnsupportedOrderParameters): |
710
|
|
|
order_target(self.asset, 100, stop_price=10, style=style) |
711
|
|
|
|
712
|
|
|
with assert_raises(UnsupportedOrderParameters): |
713
|
|
|
order_target_value(self.asset, 100, |
714
|
|
|
limit_price=10, |
715
|
|
|
style=style) |
716
|
|
|
|
717
|
|
|
with assert_raises(UnsupportedOrderParameters): |
718
|
|
|
order_target_value(self.asset, 100, |
719
|
|
|
stop_price=10, |
720
|
|
|
style=style) |
721
|
|
|
|
722
|
|
|
with assert_raises(UnsupportedOrderParameters): |
723
|
|
|
order_target_percent(self.asset, .2, |
724
|
|
|
limit_price=10, |
725
|
|
|
style=style) |
726
|
|
|
|
727
|
|
|
with assert_raises(UnsupportedOrderParameters): |
728
|
|
|
order_target_percent(self.asset, .2, |
729
|
|
|
stop_price=10, |
730
|
|
|
style=style) |
731
|
|
|
|
732
|
|
|
|
733
|
|
|
############################## |
734
|
|
|
# Quantopian style algorithms |
735
|
|
|
|
736
|
|
|
# Noop algo |
737
|
|
|
def initialize_noop(context): |
738
|
|
|
pass |
739
|
|
|
|
740
|
|
|
|
741
|
|
|
def handle_data_noop(context, data): |
742
|
|
|
pass |
743
|
|
|
|
744
|
|
|
|
745
|
|
|
# API functions |
746
|
|
|
def initialize_api(context): |
747
|
|
|
context.incr = 0 |
748
|
|
|
context.sale_price = None |
749
|
|
|
set_slippage(FixedSlippage()) |
750
|
|
|
|
751
|
|
|
|
752
|
|
|
def handle_data_api(context, data): |
753
|
|
|
if context.incr == 0: |
|
|
|
|
754
|
|
|
assert 0 not in context.portfolio.positions |
755
|
|
|
else: |
756
|
|
|
assert context.portfolio.positions[0]['amount'] == \ |
757
|
|
|
context.incr, "Orders not filled immediately." |
758
|
|
|
assert context.portfolio.positions[0]['last_sale_price'] == \ |
759
|
|
|
data[0].price, "Orders not filled at current price." |
760
|
|
|
context.incr += 1 |
761
|
|
|
order(sid(0), 1) |
762
|
|
|
|
763
|
|
|
record(incr=context.incr) |
764
|
|
|
|
765
|
|
|
########################### |
766
|
|
|
# AlgoScripts as strings |
767
|
|
|
noop_algo = """ |
768
|
|
|
# Noop algo |
769
|
|
|
def initialize(context): |
770
|
|
|
pass |
771
|
|
|
|
772
|
|
|
def handle_data(context, data): |
773
|
|
|
pass |
774
|
|
|
""" |
775
|
|
|
|
776
|
|
|
api_algo = """ |
777
|
|
|
from zipline.api import (order, |
778
|
|
|
set_slippage, |
779
|
|
|
FixedSlippage, |
780
|
|
|
record, |
781
|
|
|
sid) |
782
|
|
|
|
783
|
|
|
def initialize(context): |
784
|
|
|
context.incr = 0 |
785
|
|
|
context.sale_price = None |
786
|
|
|
set_slippage(FixedSlippage()) |
787
|
|
|
|
788
|
|
|
def handle_data(context, data): |
789
|
|
|
if context.incr == 0: |
790
|
|
|
assert 0 not in context.portfolio.positions |
791
|
|
|
else: |
792
|
|
|
assert context.portfolio.positions[0]['amount'] == \ |
793
|
|
|
context.incr, "Orders not filled immediately." |
794
|
|
|
assert context.portfolio.positions[0]['last_sale_price'] == \ |
795
|
|
|
data[0].price, "Orders not filled at current price." |
796
|
|
|
context.incr += 1 |
797
|
|
|
order(sid(0), 1) |
798
|
|
|
|
799
|
|
|
record(incr=context.incr) |
800
|
|
|
""" |
801
|
|
|
|
802
|
|
|
api_get_environment_algo = """ |
803
|
|
|
from zipline.api import get_environment, order, symbol |
804
|
|
|
|
805
|
|
|
|
806
|
|
|
def initialize(context): |
807
|
|
|
context.environment = get_environment() |
808
|
|
|
|
809
|
|
|
def handle_data(context, data): |
810
|
|
|
pass |
811
|
|
|
""" |
812
|
|
|
|
813
|
|
|
api_symbol_algo = """ |
814
|
|
|
from zipline.api import (order, |
815
|
|
|
symbol) |
816
|
|
|
|
817
|
|
|
def initialize(context): |
818
|
|
|
pass |
819
|
|
|
|
820
|
|
|
def handle_data(context, data): |
821
|
|
|
order(symbol('TEST'), 1) |
822
|
|
|
""" |
823
|
|
|
|
824
|
|
|
call_order_in_init = """ |
825
|
|
|
from zipline.api import (order) |
826
|
|
|
|
827
|
|
|
def initialize(context): |
828
|
|
|
order(0, 10) |
829
|
|
|
pass |
830
|
|
|
|
831
|
|
|
def handle_data(context, data): |
832
|
|
|
pass |
833
|
|
|
""" |
834
|
|
|
|
835
|
|
|
access_portfolio_in_init = """ |
836
|
|
|
def initialize(context): |
837
|
|
|
var = context.portfolio.cash |
838
|
|
|
pass |
839
|
|
|
|
840
|
|
|
def handle_data(context, data): |
841
|
|
|
pass |
842
|
|
|
""" |
843
|
|
|
|
844
|
|
|
access_account_in_init = """ |
845
|
|
|
def initialize(context): |
846
|
|
|
var = context.account.settled_cash |
847
|
|
|
pass |
848
|
|
|
|
849
|
|
|
def handle_data(context, data): |
850
|
|
|
pass |
851
|
|
|
""" |
852
|
|
|
|
853
|
|
|
call_all_order_methods = """ |
854
|
|
|
from zipline.api import (order, |
855
|
|
|
order_value, |
856
|
|
|
order_percent, |
857
|
|
|
order_target, |
858
|
|
|
order_target_value, |
859
|
|
|
order_target_percent, |
860
|
|
|
sid) |
861
|
|
|
|
862
|
|
|
def initialize(context): |
863
|
|
|
pass |
864
|
|
|
|
865
|
|
|
def handle_data(context, data): |
866
|
|
|
order(sid(0), 10) |
867
|
|
|
order_value(sid(0), 300) |
868
|
|
|
order_percent(sid(0), .1) |
869
|
|
|
order_target(sid(0), 100) |
870
|
|
|
order_target_value(sid(0), 100) |
871
|
|
|
order_target_percent(sid(0), .2) |
872
|
|
|
""" |
873
|
|
|
|
874
|
|
|
record_variables = """ |
875
|
|
|
from zipline.api import record |
876
|
|
|
|
877
|
|
|
def initialize(context): |
878
|
|
|
context.stocks = [0, 1] |
879
|
|
|
context.incr = 0 |
880
|
|
|
|
881
|
|
|
def handle_data(context, data): |
882
|
|
|
context.incr += 1 |
883
|
|
|
record(incr=context.incr) |
884
|
|
|
""" |
885
|
|
|
|
886
|
|
|
record_float_magic = """ |
887
|
|
|
from zipline.api import record |
888
|
|
|
|
889
|
|
|
def initialize(context): |
890
|
|
|
context.stocks = [0, 1] |
891
|
|
|
context.incr = 0 |
892
|
|
|
|
893
|
|
|
def handle_data(context, data): |
894
|
|
|
context.incr += 1 |
895
|
|
|
record(data=float('%s')) |
896
|
|
|
""" |
897
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.