2
2
3
3
import pandas as pd
4
4
5
- from tia .analysis .plots import plot_return_on_dollar
6
5
from tia .analysis .model .interface import CostCalculator , EodMarketData , PositionColumns as PC
7
6
from tia .analysis .model .pos import Positions
8
7
from tia .analysis .model .ret import RoiiRetCalculator
9
8
from tia .analysis .model .txn import Txns
9
+ from tia .analysis .util import insert_level
10
10
from tia .util .decorator import lazy_property
11
11
12
12
@@ -68,60 +68,21 @@ def __init__(self, pricer, trades, ret_calc=None):
68
68
69
69
txns = lazy_property (lambda self : Txns (self .trades , self .pricer , self .ret_calc ), 'txns' )
70
70
positions = lazy_property (lambda self : Positions (self .txns ), 'positions' )
71
+ pl = property (lambda self : self .txns .pl )
72
+ performance = property (lambda self : self .txns .performance )
73
+
74
+ # --------------------------------------------------
75
+ # direct access to common attributes
76
+ dly_pl = property (lambda self : self .pl .dly )
77
+ monthly_pl = property (lambda self : self .pl .monthly )
78
+ dly_rets = property (lambda self : self .performance .dly )
79
+ monthly_rets = property (lambda self : self .performance .monthly )
71
80
72
81
def clear_cache (self ):
73
82
for attr in ['_txns' , '_positions' , '_long' , '_short' ]:
74
83
if hasattr (self , attr ):
75
84
delattr (self , attr )
76
85
77
- # Direct access to the series
78
- ltd_pl = property (lambda self : self .txns .pl .ltd )
79
- weekly_pl = property (lambda self : self .txns .pl .weekly )
80
- monthly_pl = property (lambda self : self .txns .pl .monthly )
81
- quarterly_pl = property (lambda self : self .txns .pl .quarterly )
82
- annual_pl = property (lambda self : self .txns .pl .annual )
83
- dly_pl = property (lambda self : self .txns .pl .dly )
84
- ltd_rets = property (lambda self : self .txns .rets .ltd )
85
- weekly_rets = property (lambda self : self .txns .rets .weekly )
86
- monthly_rets = property (lambda self : self .txns .rets .monthly )
87
- quarterly_rets = property (lambda self : self .txns .rets .quarterly )
88
- annual_rets = property (lambda self : self .txns .rets .annual )
89
- dly_rets = property (lambda self : self .txns .rets .dly )
90
- # direct access to details
91
- ltd_txn_pl_frame = property (lambda self : self .txns .pl .ltd_txn_frame )
92
- dly_txn_pl_frame = property (lambda self : self .txns .pl .dly_txn_frame )
93
- ltd_pl_frame = property (lambda self : self .txns .pl .ltd_frame )
94
- dly_pl_frame = property (lambda self : self .txns .pl .dly_frame )
95
- # direct access to stats
96
- weekly_ret_stats = property (lambda self : self .txns .rets .weekly_stats )
97
- monthly_ret_stats = property (lambda self : self .txns .rets .monthly_stats )
98
- quarterly_ret_stats = property (lambda self : self .txns .rets .quarterly_stats )
99
- annual_ret_stats = property (lambda self : self .txns .rets .annual_stats )
100
- dly_ret_stats = property (lambda self : self .txns .rets .dly_stats )
101
- weekly_pl_stats = property (lambda self : self .txns .pl .weekly_stats )
102
- monthly_pl_stats = property (lambda self : self .txns .pl .monthly_stats )
103
- quarterly_pl_stats = property (lambda self : self .txns .pl .quarterly_stats )
104
- annual_pl_stats = property (lambda self : self .txns .pl .annual_stats )
105
- dly_pl_stats = property (lambda self : self .txns .pl .dly_stats )
106
-
107
- position_frame = property (lambda self : self .positions .frame )
108
-
109
- def plot_ret_on_dollar (self , freq = 'M' , title = None , show_maxdd = 1 , figsize = None , ax = None , append = 0 , label = None ,
110
- ** plot_args ):
111
- freq = freq .lower ()
112
- if freq == 'a' :
113
- rets = self .annual_rets
114
- elif freq == 'q' :
115
- rets = self .quarterly_rets
116
- elif freq == 'm' :
117
- rets = self .monthly_rets
118
- elif freq == 'w' :
119
- rets = self .weekly_rets
120
- else :
121
- rets = self .dly_rets .asfreq ('B' )
122
- plot_return_on_dollar (rets , title = title , show_maxdd = show_maxdd , figsize = figsize , ax = ax , append = append ,
123
- label = label , ** plot_args )
124
-
125
86
def subset (self , pids ):
126
87
txns = self .txns
127
88
stxns = txns .subset (pids )
@@ -147,7 +108,7 @@ def short(self):
147
108
winner = property (lambda self : PortfolioSubset .winners (self ))
148
109
loser = property (lambda self : PortfolioSubset .losers (self ))
149
110
150
- def buy_and_hold (self , qty = 1. , start = None , end = None , start_px = None , end_px = None ):
111
+ def buy_and_hold (self , qty = 1. , start_dt = None , end_dt = None , start_px = None , end_px = None ):
151
112
"""Construct a portfolio which opens a position with size qty at start (or first data in pricer) and
152
113
continues to the specified end date. It uses the end of day market prices defined by the pricer
153
114
(or prices supplied)
@@ -161,23 +122,18 @@ def buy_and_hold(self, qty=1., start=None, end=None, start_px=None, end_px=None)
161
122
"""
162
123
from tia .analysis .model .trd import TradeBlotter
163
124
164
- pricer = self .pricer
165
- eod = pricer .get_eod_frame ().close
166
- eod_start , eod_end = eod .index [0 ], eod .index [- 1 ]
167
-
168
- start = start and pd .to_datetime (start ) or eod_start
169
- end = end and pd .to_datetime (end ) or eod_end
170
-
171
- if start != eod_start or end != eod_end :
172
- pricer = pricer .truncate (start , end )
125
+ eod = self .pricer .get_eod_frame ().close
173
126
174
- start_px = start_px or eod [start ]
175
- end_px = end_px or eod [end ]
127
+ start_dt = start_dt and pd .to_datetime (start_dt ) or eod .index [0 ]
128
+ start_px = start_px or eod .asof (start_dt )
129
+ end_dt = end_dt and pd .to_datetime (end_dt ) or eod .index [- 1 ]
130
+ end_px = end_px or eod .asof (end_dt )
176
131
132
+ pricer = self .pricer .trunace (start_dt , end_dt )
177
133
blotter = TradeBlotter ()
178
- blotter .ts = start
134
+ blotter .ts = start_dt
179
135
blotter .open (qty , start_px )
180
- blotter .ts = end
136
+ blotter .ts = end_dt
181
137
blotter .close (end_px )
182
138
trds = blotter .trades
183
139
return SingleAssetPortfolio (pricer , trds , ret_calc = self .ret_calc )
@@ -240,20 +196,24 @@ def __init__(self):
240
196
self .total_key = 'All'
241
197
self .iter_fcts = []
242
198
243
- def __call__ (self , port , analyze_fct ):
244
- """
245
- analyze_fct: fct(port) which can return Series, or map of key to Series. If key to series, then
199
+ def __call__ (self , port , analyze_fct = None ):
200
+ """ analyze_fct: fct(port) which can return Series, or map of key to Series. If key to series, then
246
201
the key is used as an additional index value.
202
+
203
+ :param port: Portfolio or dict of key->Portfolio
204
+ :param analyze_fct:
205
+ :return:
247
206
"""
248
- results = []
249
207
iter_fcts = self .iter_fcts
250
208
lvls = len (iter_fcts )
251
209
252
- def _iter_all_lvls (lvl , keys , parent ):
210
+ analyze_fct = self .analyze_returns if analyze_fct is None else analyze_fct
211
+
212
+ def _iter_all_lvls (lvl , keys , parent , results ):
253
213
if lvl < (lvls - 1 ):
254
214
# exhaust combinations
255
215
for key , child in iter_fcts [lvl ](parent ):
256
- _iter_all_lvls (lvl + 1 , keys + [key ], child )
216
+ _iter_all_lvls (lvl + 1 , keys + [key ], child , results )
257
217
else :
258
218
# at the bottom
259
219
for key , child in iter_fcts [lvl ](parent ):
@@ -274,13 +234,37 @@ def _iter_all_lvls(lvl, keys, parent):
274
234
results .append (v )
275
235
276
236
if lvls == 0 :
277
- res = analyze_fct (port )
278
- if isinstance (res , pd .Series ):
279
- res = res .to_frame ().T
280
- results .append (res )
237
+ def _get_res (p ):
238
+ res = analyze_fct (p )
239
+ return res .to_frame ().T if isinstance (res , pd .Series ) else res
240
+
241
+ if hasattr (port , 'iteritems' ):
242
+ pieces = []
243
+ for k , p in port .iteritems ():
244
+ res = _get_res (p )
245
+ defidx = res .index .nlevels == 1 and (res .index == 0 ).all ()
246
+ res = insert_level (res , k , axis = 1 , level_name = 'lvl1' )
247
+ if defidx :
248
+ res .index = res .index .droplevel (1 )
249
+ pieces .append (res )
250
+ return pd .concat (pieces )
251
+ else :
252
+ return _get_res (port )
281
253
else :
282
- _iter_all_lvls (0 , [], port )
283
- return pd .concat (results )
254
+ if hasattr (port , 'iteritems' ):
255
+ pieces = []
256
+ for k , p in port .iteritems ():
257
+ results = []
258
+ _iter_all_lvls (0 , [], p , results )
259
+ tmp = pd .concat (results )
260
+ tmp .index .names = ['lvl%s' % (i + 2 ) for i in range (len (tmp .index .names ))]
261
+ tmp = insert_level (tmp , k , level_name = 'lvl1' , axis = 1 )
262
+ pieces .append (tmp )
263
+ return pd .concat (pieces )
264
+ else :
265
+ results = []
266
+ _iter_all_lvls (0 , [], port , results )
267
+ return pd .concat (results )
284
268
285
269
def add_iter_fct (self , siter ):
286
270
self .iter_fcts .append (siter )
@@ -290,8 +274,8 @@ def include_win_loss(self, total=1):
290
274
def _split_port (port ):
291
275
if total :
292
276
yield self .total_key , port
293
- yield 'Winner ' , PortfolioSubset .winners (port )
294
- yield 'Loser ' , PortfolioSubset .losers (port )
277
+ yield 'winner ' , PortfolioSubset .winners (port )
278
+ yield 'loser ' , PortfolioSubset .losers (port )
295
279
296
280
self .add_iter_fct (_split_port )
297
281
return self
@@ -300,28 +284,28 @@ def include_long_short(self, total=1):
300
284
def _split_port (port ):
301
285
if total :
302
286
yield self .total_key , port
303
- yield 'Long ' , port .long
304
- yield 'Short ' , port .short
287
+ yield 'long ' , port .long
288
+ yield 'short ' , port .short
305
289
306
290
self .add_iter_fct (_split_port )
307
291
return self
308
292
309
293
@staticmethod
310
294
def analyze_returns (port ):
311
- mstats = port .monthly_ret_stats
312
- dstats = port .dly_ret_stats
295
+ monthly = port .performance . monthly_details
296
+ dly = port .performance . dly_details
313
297
stats = port .positions .stats
314
298
data = OrderedDict ()
315
- data [('port' , 'cagr ' )] = mstats . total_ann
316
- data [('port' , 'mret avg' )] = mstats . ret_avg
317
- data [('port' , 'mret avg ann' )] = mstats . ret_avg_ann
318
- data [('port' , 'mret std ann' )] = mstats .std_ann
319
- data [('port' , 'sharpe ann' )] = mstats .sharpe_ann
320
- data [('port' , 'sortino' )] = mstats .sortino
321
- data [('port' , 'maxdd' )] = dstats .maxdd
322
- data [('port' , 'maxdd dt' )] = dstats .maxdd_dt
323
- data [('port' , 'avg dd' )] = dstats .dd_avg
324
- data [('port' , 'nmonths' )] = mstats .cnt
299
+ data [('port' , 'ltd ann ' )] = monthly . ltd_ann
300
+ data [('port' , 'mret avg' )] = monthly . mean
301
+ data [('port' , 'mret avg ann' )] = monthly . mean_ann
302
+ data [('port' , 'mret std ann' )] = monthly .std_ann
303
+ data [('port' , 'sharpe ann' )] = monthly .sharpe_ann
304
+ data [('port' , 'sortino' )] = monthly .sortino
305
+ data [('port' , 'maxdd' )] = dly .maxdd
306
+ data [('port' , 'maxdd dt' )] = dly .maxdd_dt
307
+ data [('port' , 'avg dd' )] = dly .dd_avg
308
+ data [('port' , 'nmonths' )] = monthly .cnt
325
309
# pos data
326
310
data [('pos' , 'cnt' )] = stats .cnt
327
311
data [('pos' , 'win cnt' )] = stats .win_cnt
@@ -337,20 +321,20 @@ def analyze_returns(port):
337
321
338
322
@staticmethod
339
323
def analyze_pl (port ):
340
- mstats = port .monthly_pl_stats
341
- dstats = port .dly_pl_stats
324
+ monthly = port .pl . monthly_details
325
+ dstats = port .pl . dly_details
342
326
stats = port .positions .stats
343
327
data = OrderedDict ()
344
- data [('port' , 'ltd' )] = mstats . pl .sum ()
345
- data [('port' , 'mpl avg' )] = mstats . avg
346
- data [('port' , 'mpl std' )] = mstats .std
347
- data [('port' , 'mpl std ann' )] = mstats .std_ann
348
- data [('port' , 'mpl max' )] = mstats .pl .max ()
349
- data [('port' , 'mpl min' )] = mstats .pl .min ()
328
+ data [('port' , 'ltd' )] = monthly . ltd_frame . pl .iloc [ - 1 ]
329
+ data [('port' , 'mpl avg' )] = monthly . mean
330
+ data [('port' , 'mpl std' )] = monthly .std
331
+ data [('port' , 'mpl std ann' )] = monthly .std_ann
332
+ data [('port' , 'mpl max' )] = monthly . frame .pl .max ()
333
+ data [('port' , 'mpl min' )] = monthly . frame .pl .min ()
350
334
data [('port' , 'maxdd' )] = dstats .maxdd
351
335
data [('port' , 'maxdd dt' )] = dstats .maxdd_dt
352
336
data [('port' , 'avg dd' )] = dstats .dd_avg
353
- data [('port' , 'nmonths' )] = mstats .cnt
337
+ data [('port' , 'nmonths' )] = monthly .cnt
354
338
# pos data
355
339
data [('pos' , 'cnt' )] = stats .cnt
356
340
data [('pos' , 'win cnt' )] = stats .win_cnt
@@ -361,9 +345,3 @@ def analyze_pl(port):
361
345
data [('pos' , 'pl min' )] = stats .pl_min
362
346
data [('pos' , 'pl max' )] = stats .pl_max
363
347
return pd .Series (data , index = pd .MultiIndex .from_tuples (data .keys ()))
364
-
365
-
366
-
367
-
368
-
369
-
0 commit comments