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
|
|
|
Performance Period |
19
|
|
|
================== |
20
|
|
|
|
21
|
|
|
Performance Periods are updated with every trade. When calling |
22
|
|
|
code needs a portfolio object that fulfills the algorithm |
23
|
|
|
protocol, use the PerformancePeriod.as_portfolio method. See that |
24
|
|
|
method for comments on the specific fields provided (and |
25
|
|
|
omitted). |
26
|
|
|
|
27
|
|
|
+---------------+------------------------------------------------------+ |
28
|
|
|
| key | value | |
29
|
|
|
+===============+======================================================+ |
30
|
|
|
| ending_value | the total market value of the positions held at the | |
31
|
|
|
| | end of the period | |
32
|
|
|
+---------------+------------------------------------------------------+ |
33
|
|
|
| cash_flow | the cash flow in the period (negative means spent) | |
34
|
|
|
| | from buying and selling assets in the period. | |
35
|
|
|
| | Includes dividend payments in the period as well. | |
36
|
|
|
+---------------+------------------------------------------------------+ |
37
|
|
|
| starting_value| the total market value of the positions held at the | |
38
|
|
|
| | start of the period | |
39
|
|
|
+---------------+------------------------------------------------------+ |
40
|
|
|
| starting_cash | cash on hand at the beginning of the period | |
41
|
|
|
+---------------+------------------------------------------------------+ |
42
|
|
|
| ending_cash | cash on hand at the end of the period | |
43
|
|
|
+---------------+------------------------------------------------------+ |
44
|
|
|
| positions | a list of dicts representing positions, see | |
45
|
|
|
| | :py:meth:`Position.to_dict()` | |
46
|
|
|
| | for details on the contents of the dict | |
47
|
|
|
+---------------+------------------------------------------------------+ |
48
|
|
|
| pnl | Dollar value profit and loss, for both realized and | |
49
|
|
|
| | unrealized gains. | |
50
|
|
|
+---------------+------------------------------------------------------+ |
51
|
|
|
| returns | percentage returns for the entire portfolio over the | |
52
|
|
|
| | period | |
53
|
|
|
+---------------+------------------------------------------------------+ |
54
|
|
|
| cumulative\ | The net capital used (positive is spent) during | |
55
|
|
|
| _capital_used | the period | |
56
|
|
|
+---------------+------------------------------------------------------+ |
57
|
|
|
| max_capital\ | The maximum amount of capital deployed during the | |
58
|
|
|
| _used | period. | |
59
|
|
|
+---------------+------------------------------------------------------+ |
60
|
|
|
| period_close | The last close of the market in period. datetime in | |
61
|
|
|
| | pytz.utc timezone. | |
62
|
|
|
+---------------+------------------------------------------------------+ |
63
|
|
|
| period_open | The first open of the market in period. datetime in | |
64
|
|
|
| | pytz.utc timezone. | |
65
|
|
|
+---------------+------------------------------------------------------+ |
66
|
|
|
| transactions | all the transactions that were acrued during this | |
67
|
|
|
| | period. Unset/missing for cumulative periods. | |
68
|
|
|
+---------------+------------------------------------------------------+ |
69
|
|
|
|
70
|
|
|
|
71
|
|
|
""" |
72
|
|
|
|
73
|
|
|
from __future__ import division |
74
|
|
|
import logbook |
75
|
|
|
|
76
|
|
|
import numpy as np |
77
|
|
|
|
78
|
|
|
from collections import namedtuple |
79
|
|
|
from zipline.assets import Future |
80
|
|
|
|
81
|
|
|
try: |
82
|
|
|
# optional cython based OrderedDict |
83
|
|
|
from cyordereddict import OrderedDict |
84
|
|
|
except ImportError: |
85
|
|
|
from collections import OrderedDict |
86
|
|
|
|
87
|
|
|
from six import itervalues, iteritems |
88
|
|
|
|
89
|
|
|
import zipline.protocol as zp |
90
|
|
|
|
91
|
|
|
from zipline.utils.serialization_utils import ( |
92
|
|
|
VERSION_LABEL |
93
|
|
|
) |
94
|
|
|
|
95
|
|
|
log = logbook.Logger('Performance') |
96
|
|
|
TRADE_TYPE = zp.DATASOURCE_TYPE.TRADE |
97
|
|
|
|
98
|
|
|
|
99
|
|
|
PeriodStats = namedtuple('PeriodStats', |
100
|
|
|
['net_liquidation', |
101
|
|
|
'gross_leverage', |
102
|
|
|
'net_leverage', |
103
|
|
|
'ending_cash', |
104
|
|
|
'pnl', |
105
|
|
|
'returns', |
106
|
|
|
'portfolio_value']) |
107
|
|
|
|
108
|
|
|
|
109
|
|
|
def calc_net_liquidation(ending_cash, long_value, short_value): |
110
|
|
|
return ending_cash + long_value + short_value |
111
|
|
|
|
112
|
|
|
|
113
|
|
|
def calc_leverage(exposure, net_liq): |
114
|
|
|
if net_liq != 0: |
115
|
|
|
return exposure / net_liq |
116
|
|
|
|
117
|
|
|
return np.inf |
118
|
|
|
|
119
|
|
|
|
120
|
|
|
def calc_period_stats(pos_stats, starting_cash, starting_value, |
121
|
|
|
period_cash_flow, payout): |
122
|
|
|
total_at_start = starting_cash + starting_value |
123
|
|
|
ending_cash = starting_cash + period_cash_flow + payout |
124
|
|
|
total_at_end = ending_cash + pos_stats.net_value |
125
|
|
|
|
126
|
|
|
pnl = total_at_end - total_at_start |
127
|
|
|
if total_at_start != 0: |
128
|
|
|
returns = pnl / total_at_start |
129
|
|
|
else: |
130
|
|
|
returns = 0.0 |
131
|
|
|
|
132
|
|
|
portfolio_value = ending_cash + pos_stats.net_value + payout |
133
|
|
|
|
134
|
|
|
net_liq = calc_net_liquidation(ending_cash, |
135
|
|
|
pos_stats.long_value, |
136
|
|
|
pos_stats.short_value) |
137
|
|
|
gross_leverage = calc_leverage(pos_stats.gross_exposure, net_liq) |
138
|
|
|
net_leverage = calc_leverage(pos_stats.net_exposure, net_liq) |
139
|
|
|
|
140
|
|
|
return PeriodStats( |
141
|
|
|
net_liquidation=net_liq, |
142
|
|
|
gross_leverage=gross_leverage, |
143
|
|
|
net_leverage=net_leverage, |
144
|
|
|
ending_cash=ending_cash, |
145
|
|
|
pnl=pnl, |
146
|
|
|
returns=returns, |
147
|
|
|
portfolio_value=portfolio_value |
148
|
|
|
) |
149
|
|
|
|
150
|
|
|
|
151
|
|
|
class PerformancePeriod(object): |
152
|
|
|
|
153
|
|
|
def __init__( |
154
|
|
|
self, |
155
|
|
|
starting_cash, |
156
|
|
|
asset_finder, |
157
|
|
|
data_frequency, |
158
|
|
|
period_open=None, |
159
|
|
|
period_close=None, |
160
|
|
|
keep_transactions=True, |
161
|
|
|
keep_orders=False, |
162
|
|
|
serialize_positions=True, |
163
|
|
|
name=None): |
164
|
|
|
|
165
|
|
|
self.asset_finder = asset_finder |
166
|
|
|
self.data_frequency = data_frequency |
167
|
|
|
|
168
|
|
|
self.period_open = period_open |
169
|
|
|
self.period_close = period_close |
170
|
|
|
|
171
|
|
|
self.period_cash_flow = 0.0 |
172
|
|
|
|
173
|
|
|
self.starting_cash = starting_cash |
174
|
|
|
self.starting_value = 0.0 |
175
|
|
|
self.starting_exposure = 0.0 |
176
|
|
|
|
177
|
|
|
self.keep_transactions = keep_transactions |
178
|
|
|
self.keep_orders = keep_orders |
179
|
|
|
|
180
|
|
|
self.processed_transactions = {} |
181
|
|
|
self.orders_by_modified = {} |
182
|
|
|
self.orders_by_id = OrderedDict() |
183
|
|
|
|
184
|
|
|
self.name = name |
185
|
|
|
|
186
|
|
|
# An object to recycle via assigning new values |
187
|
|
|
# when returning portfolio information. |
188
|
|
|
# So as not to avoid creating a new object for each event |
189
|
|
|
self._portfolio_store = zp.Portfolio() |
190
|
|
|
self._account_store = zp.Account() |
191
|
|
|
self.serialize_positions = serialize_positions |
192
|
|
|
|
193
|
|
|
# This dict contains the known cash flow multipliers for sids and is |
194
|
|
|
# keyed on sid |
195
|
|
|
self._execution_cash_flow_multipliers = {} |
196
|
|
|
|
197
|
|
|
def rollover(self, pos_stats, prev_period_stats): |
198
|
|
|
self.starting_value = pos_stats.net_value |
199
|
|
|
self.starting_exposure = pos_stats.net_exposure |
200
|
|
|
self.starting_cash = prev_period_stats.ending_cash |
201
|
|
|
self.period_cash_flow = 0.0 |
202
|
|
|
self.processed_transactions = {} |
203
|
|
|
self.orders_by_modified = {} |
204
|
|
|
self.orders_by_id = OrderedDict() |
205
|
|
|
|
206
|
|
|
def handle_dividends_paid(self, net_cash_payment): |
207
|
|
|
if net_cash_payment: |
208
|
|
|
self.handle_cash_payment(net_cash_payment) |
209
|
|
|
|
210
|
|
|
def handle_cash_payment(self, payment_amount): |
211
|
|
|
self.adjust_cash(payment_amount) |
212
|
|
|
|
213
|
|
|
def handle_commission(self, cost): |
214
|
|
|
# Deduct from our total cash pool. |
215
|
|
|
self.adjust_cash(-cost) |
216
|
|
|
|
217
|
|
|
def adjust_cash(self, amount): |
218
|
|
|
self.period_cash_flow += amount |
219
|
|
|
|
220
|
|
|
def adjust_field(self, field, value): |
221
|
|
|
setattr(self, field, value) |
222
|
|
|
|
223
|
|
|
def record_order(self, order): |
224
|
|
|
if self.keep_orders: |
225
|
|
|
try: |
226
|
|
|
dt_orders = self.orders_by_modified[order.dt] |
227
|
|
|
if order.id in dt_orders: |
228
|
|
|
del dt_orders[order.id] |
229
|
|
|
except KeyError: |
230
|
|
|
self.orders_by_modified[order.dt] = dt_orders = OrderedDict() |
231
|
|
|
dt_orders[order.id] = order |
232
|
|
|
# to preserve the order of the orders by modified date |
233
|
|
|
# we delete and add back. (ordered dictionary is sorted by |
234
|
|
|
# first insertion date). |
235
|
|
|
if order.id in self.orders_by_id: |
236
|
|
|
del self.orders_by_id[order.id] |
237
|
|
|
self.orders_by_id[order.id] = order |
238
|
|
|
|
239
|
|
|
def handle_execution(self, txn): |
240
|
|
|
self.period_cash_flow += self._calculate_execution_cash_flow(txn) |
241
|
|
|
|
242
|
|
|
if self.keep_transactions: |
243
|
|
|
try: |
244
|
|
|
self.processed_transactions[txn.dt].append(txn) |
245
|
|
|
except KeyError: |
246
|
|
|
self.processed_transactions[txn.dt] = [txn] |
247
|
|
|
|
248
|
|
|
def _calculate_execution_cash_flow(self, txn): |
249
|
|
|
""" |
250
|
|
|
Calculates the cash flow from executing the given transaction |
251
|
|
|
""" |
252
|
|
|
# Check if the multiplier is cached. If it is not, look up the asset |
253
|
|
|
# and cache the multiplier. |
254
|
|
|
try: |
255
|
|
|
multiplier = self._execution_cash_flow_multipliers[txn.sid] |
256
|
|
|
except KeyError: |
257
|
|
|
asset = self.asset_finder.retrieve_asset(txn.sid) |
258
|
|
|
# Futures experience no cash flow on transactions |
259
|
|
|
if isinstance(asset, Future): |
260
|
|
|
multiplier = 0 |
261
|
|
|
else: |
262
|
|
|
multiplier = 1 |
263
|
|
|
self._execution_cash_flow_multipliers[txn.sid] = multiplier |
264
|
|
|
|
265
|
|
|
# Calculate and return the cash flow given the multiplier |
266
|
|
|
return -1 * txn.price * txn.amount * multiplier |
267
|
|
|
|
268
|
|
|
def stats(self, positions, pos_stats, data_portal): |
269
|
|
|
# TODO: passing positions here seems off, since we have already |
270
|
|
|
# calculated pos_stats. |
271
|
|
|
futures_payouts = [] |
272
|
|
|
for sid, pos in iteritems(positions): |
273
|
|
|
asset = self.asset_finder.retrieve_asset(sid) |
274
|
|
|
if isinstance(asset, Future): |
275
|
|
|
old_price_dt = max(pos.last_sale_date, self.period_open) |
276
|
|
|
|
277
|
|
|
if old_price_dt == pos.last_sale_date: |
278
|
|
|
continue |
279
|
|
|
|
280
|
|
|
old_price = data_portal.get_previous_value( |
281
|
|
|
sid, 'close', old_price_dt, self.data_frequency |
282
|
|
|
) |
283
|
|
|
|
284
|
|
|
price = data_portal.get_spot_value( |
285
|
|
|
sid, 'close', self.period_close, self.data_frequency, |
286
|
|
|
) |
287
|
|
|
|
288
|
|
|
payout = ( |
289
|
|
|
(price - old_price) |
290
|
|
|
* |
291
|
|
|
asset.contract_multiplier |
292
|
|
|
* |
293
|
|
|
pos.amount |
294
|
|
|
) |
295
|
|
|
futures_payouts.append(payout) |
296
|
|
|
|
297
|
|
|
futures_payout = sum(futures_payouts) |
298
|
|
|
|
299
|
|
|
return calc_period_stats( |
300
|
|
|
pos_stats, |
301
|
|
|
self.starting_cash, |
302
|
|
|
self.starting_value, |
303
|
|
|
self.period_cash_flow, |
304
|
|
|
futures_payout |
305
|
|
|
) |
306
|
|
|
|
307
|
|
|
def __core_dict(self, pos_stats, period_stats): |
308
|
|
|
rval = { |
309
|
|
|
'ending_value': pos_stats.net_value, |
310
|
|
|
'ending_exposure': pos_stats.net_exposure, |
311
|
|
|
# this field is renamed to capital_used for backward |
312
|
|
|
# compatibility. |
313
|
|
|
'capital_used': self.period_cash_flow, |
314
|
|
|
'starting_value': self.starting_value, |
315
|
|
|
'starting_exposure': self.starting_exposure, |
316
|
|
|
'starting_cash': self.starting_cash, |
317
|
|
|
'ending_cash': period_stats.ending_cash, |
318
|
|
|
'portfolio_value': period_stats.portfolio_value, |
319
|
|
|
'pnl': period_stats.pnl, |
320
|
|
|
'returns': period_stats.returns, |
321
|
|
|
'period_open': self.period_open, |
322
|
|
|
'period_close': self.period_close, |
323
|
|
|
'gross_leverage': period_stats.gross_leverage, |
324
|
|
|
'net_leverage': period_stats.net_leverage, |
325
|
|
|
'short_exposure': pos_stats.short_exposure, |
326
|
|
|
'long_exposure': pos_stats.long_exposure, |
327
|
|
|
'short_value': pos_stats.short_value, |
328
|
|
|
'long_value': pos_stats.long_value, |
329
|
|
|
'longs_count': pos_stats.longs_count, |
330
|
|
|
'shorts_count': pos_stats.shorts_count, |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
return rval |
334
|
|
|
|
335
|
|
|
def to_dict(self, pos_stats, period_stats, position_tracker, dt=None): |
336
|
|
|
""" |
337
|
|
|
Creates a dictionary representing the state of this performance |
338
|
|
|
period. See header comments for a detailed description. |
339
|
|
|
|
340
|
|
|
Kwargs: |
341
|
|
|
dt (datetime): If present, only return transactions for the dt. |
342
|
|
|
""" |
343
|
|
|
rval = self.__core_dict(pos_stats, period_stats) |
344
|
|
|
|
345
|
|
|
if self.serialize_positions: |
346
|
|
|
positions = position_tracker.get_positions_list() |
347
|
|
|
rval['positions'] = positions |
348
|
|
|
|
349
|
|
|
# we want the key to be absent, not just empty |
350
|
|
|
if self.keep_transactions: |
351
|
|
|
if dt: |
352
|
|
|
# Only include transactions for given dt |
353
|
|
|
try: |
354
|
|
|
transactions = [x.to_dict() |
355
|
|
|
for x in self.processed_transactions[dt]] |
356
|
|
|
except KeyError: |
357
|
|
|
transactions = [] |
358
|
|
|
else: |
359
|
|
|
transactions = \ |
360
|
|
|
[y.to_dict() |
361
|
|
|
for x in itervalues(self.processed_transactions) |
362
|
|
|
for y in x] |
363
|
|
|
rval['transactions'] = transactions |
364
|
|
|
|
365
|
|
|
if self.keep_orders: |
366
|
|
|
if dt: |
367
|
|
|
# only include orders modified as of the given dt. |
368
|
|
|
try: |
369
|
|
|
orders = [x.to_dict() |
370
|
|
|
for x in itervalues(self.orders_by_modified[dt])] |
371
|
|
|
except KeyError: |
372
|
|
|
orders = [] |
373
|
|
|
else: |
374
|
|
|
orders = [x.to_dict() for x in itervalues(self.orders_by_id)] |
375
|
|
|
rval['orders'] = orders |
376
|
|
|
|
377
|
|
|
return rval |
378
|
|
|
|
379
|
|
|
def as_portfolio(self, pos_stats, period_stats, position_tracker, dt): |
380
|
|
|
""" |
381
|
|
|
The purpose of this method is to provide a portfolio |
382
|
|
|
object to algorithms running inside the same trading |
383
|
|
|
client. The data needed is captured raw in a |
384
|
|
|
PerformancePeriod, and in this method we rename some |
385
|
|
|
fields for usability and remove extraneous fields. |
386
|
|
|
""" |
387
|
|
|
# Recycles containing objects' Portfolio object |
388
|
|
|
# which is used for returning values. |
389
|
|
|
# as_portfolio is called in an inner loop, |
390
|
|
|
# so repeated object creation becomes too expensive |
391
|
|
|
portfolio = self._portfolio_store |
392
|
|
|
# maintaining the old name for the portfolio field for |
393
|
|
|
# backward compatibility |
394
|
|
|
portfolio.capital_used = self.period_cash_flow |
395
|
|
|
portfolio.starting_cash = self.starting_cash |
396
|
|
|
portfolio.portfolio_value = period_stats.portfolio_value |
397
|
|
|
portfolio.pnl = period_stats.pnl |
398
|
|
|
portfolio.returns = period_stats.returns |
399
|
|
|
portfolio.cash = period_stats.ending_cash |
400
|
|
|
portfolio.start_date = self.period_open |
401
|
|
|
portfolio.positions = position_tracker.get_positions() |
402
|
|
|
portfolio.positions_value = pos_stats.net_value |
403
|
|
|
portfolio.positions_exposure = pos_stats.net_exposure |
404
|
|
|
return portfolio |
405
|
|
|
|
406
|
|
|
def as_account(self, pos_stats, period_stats): |
407
|
|
|
account = self._account_store |
408
|
|
|
|
409
|
|
|
# If no attribute is found on the PerformancePeriod resort to the |
410
|
|
|
# following default values. If an attribute is found use the existing |
411
|
|
|
# value. For instance, a broker may provide updates to these |
412
|
|
|
# attributes. In this case we do not want to over write the broker |
413
|
|
|
# values with the default values. |
414
|
|
|
account.settled_cash = \ |
415
|
|
|
getattr(self, 'settled_cash', period_stats.ending_cash) |
416
|
|
|
account.accrued_interest = \ |
417
|
|
|
getattr(self, 'accrued_interest', 0.0) |
418
|
|
|
account.buying_power = \ |
419
|
|
|
getattr(self, 'buying_power', float('inf')) |
420
|
|
|
account.equity_with_loan = \ |
421
|
|
|
getattr(self, 'equity_with_loan', period_stats.portfolio_value) |
422
|
|
|
account.total_positions_value = \ |
423
|
|
|
getattr(self, 'total_positions_value', pos_stats.net_value) |
424
|
|
|
account.total_positions_value = \ |
425
|
|
|
getattr(self, 'total_positions_exposure', pos_stats.net_exposure) |
426
|
|
|
account.regt_equity = \ |
427
|
|
|
getattr(self, 'regt_equity', period_stats.ending_cash) |
428
|
|
|
account.regt_margin = \ |
429
|
|
|
getattr(self, 'regt_margin', float('inf')) |
430
|
|
|
account.initial_margin_requirement = \ |
431
|
|
|
getattr(self, 'initial_margin_requirement', 0.0) |
432
|
|
|
account.maintenance_margin_requirement = \ |
433
|
|
|
getattr(self, 'maintenance_margin_requirement', 0.0) |
434
|
|
|
account.available_funds = \ |
435
|
|
|
getattr(self, 'available_funds', period_stats.ending_cash) |
436
|
|
|
account.excess_liquidity = \ |
437
|
|
|
getattr(self, 'excess_liquidity', period_stats.ending_cash) |
438
|
|
|
account.cushion = \ |
439
|
|
|
getattr(self, 'cushion', |
440
|
|
|
period_stats.ending_cash / period_stats.portfolio_value) |
441
|
|
|
account.day_trades_remaining = \ |
442
|
|
|
getattr(self, 'day_trades_remaining', float('inf')) |
443
|
|
|
account.leverage = getattr(self, 'leverage', |
444
|
|
|
period_stats.gross_leverage) |
445
|
|
|
account.net_leverage = period_stats.net_leverage |
446
|
|
|
|
447
|
|
|
account.net_liquidation = getattr(self, 'net_liquidation', |
448
|
|
|
period_stats.net_liquidation) |
449
|
|
|
return account |
450
|
|
|
|
451
|
|
|
def __getstate__(self): |
452
|
|
|
state_dict = {k: v for k, v in iteritems(self.__dict__) |
453
|
|
|
if not k.startswith('_')} |
454
|
|
|
|
455
|
|
|
state_dict['_portfolio_store'] = self._portfolio_store |
456
|
|
|
state_dict['_account_store'] = self._account_store |
457
|
|
|
state_dict['data_frequency'] = self.data_frequency |
458
|
|
|
|
459
|
|
|
state_dict['processed_transactions'] = \ |
460
|
|
|
dict(self.processed_transactions) |
461
|
|
|
state_dict['orders_by_id'] = \ |
462
|
|
|
dict(self.orders_by_id) |
463
|
|
|
state_dict['orders_by_modified'] = \ |
464
|
|
|
dict(self.orders_by_modified) |
465
|
|
|
|
466
|
|
|
STATE_VERSION = 3 |
467
|
|
|
state_dict[VERSION_LABEL] = STATE_VERSION |
468
|
|
|
return state_dict |
469
|
|
|
|
470
|
|
|
def __setstate__(self, state): |
471
|
|
|
|
472
|
|
|
OLDEST_SUPPORTED_STATE = 3 |
473
|
|
|
version = state.pop(VERSION_LABEL) |
474
|
|
|
|
475
|
|
|
if version < OLDEST_SUPPORTED_STATE: |
476
|
|
|
raise BaseException("PerformancePeriod saved state is too old.") |
477
|
|
|
|
478
|
|
|
processed_transactions = {} |
479
|
|
|
processed_transactions.update(state.pop('processed_transactions')) |
480
|
|
|
|
481
|
|
|
orders_by_id = OrderedDict() |
482
|
|
|
orders_by_id.update(state.pop('orders_by_id')) |
483
|
|
|
|
484
|
|
|
orders_by_modified = {} |
485
|
|
|
orders_by_modified.update(state.pop('orders_by_modified')) |
486
|
|
|
self.processed_transactions = processed_transactions |
487
|
|
|
self.orders_by_id = orders_by_id |
488
|
|
|
self.orders_by_modified = orders_by_modified |
489
|
|
|
|
490
|
|
|
self._execution_cash_flow_multipliers = {} |
491
|
|
|
|
492
|
|
|
self.__dict__.update(state) |
493
|
|
|
|