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
|
from .index import Index
from .option import BlackSwaption, VolatilitySurface
from db import dbengine
from warnings import warn
serenitasdb = dbengine('serenitasdb')
def _key_from_index(index):
_, index_type, _, series, tenor = index.name.split()
series = int(series[1:])
tenor = tenor.lower() + 'r'
return index_type, series, tenor
class Portfolio:
def __init__(self, trades):
self.trades = trades
self.indices = [t for t in trades if isinstance(t, Index)]
self.swaptions = [t for t in trades if isinstance(t, BlackSwaption)]
self._keys = []
for index in self.indices:
self._keys.append(_key_from_index(index))
#pick the first available trade_date
trade_dates = set()
for index in self.indices:
trade_dates.add(index.trade_date)
for swaption in self.swaptions:
trade_dates.add(swaption.index.trade_date)
self._trade_date = trade_dates.pop()
if len(trade_dates) >= 1:
warn("not all instruments have the same trade date, picking {}".
format(self._trade_date))
self._vs = {}
@property
def pnl(self):
return sum(t.pnl for t in self.trades)
@property
def pnl_list(self):
return [t.pnl for t in self.trades]
@property
def pv(self):
return sum(t.pv for t in self.trades)
@property
def pv_list(self):
return [t.pv for t in self.trades]
def set_original_pv(self):
for t in self.trades:
t.set_original_pv()
@property
def trade_date(self):
return self._trade_date
@trade_date.setter
def trade_date(self, d):
#we try to keep everybody in sync
for index in self.indices:
index.trade_date = d
if len(self.indices) == 0:
for swaption in self.swaptions:
self.swaption.trade_date = d
self._trade_date = d
def mark(self, source=None, option_type=None, model=None, surface_id=None):
vs_dict = {}
for index, (index_type, series, tenor) in zip(self.indices, self._keys):
run = serenitasdb.execute("""SELECT * FROM index_quotes
WHERE index=%s AND series=%s AND tenor=%s AND date=%s""",
(index_type, series, tenor, self.trade_date))
rec = run.fetchone()
index.spread = rec.closespread
k = (self.trade_date, index_type, series, tenor)
if k not in self._vs:
vs = VolatilitySurface(index_type, series, tenor, self.trade_date)
if surface_id is None and len(vs.list(source, option_type, model)) >=1:
surface_id = vs.list(source, option_type, model)[-1]
else:
raise ValueError("No market data available for this day")
self._vs[k] = vs[surface_id]
for swaption in self.swaptions:
vol_surface = self._vs[(swaption.index.trade_date, ) + \
_key_from_index(swaption.index)]
swaption.sigma = float(self._vs[(swaption.index.trade_date, ) \
+ _key_from_index(swaption.index)].
ev(swaption.T, swaption.moneyness))
@property
def ref(self):
if len(self.indices) == 1:
return self.indices[0].ref
else:
return [index.ref for index in self.indices]
@ref.setter
def ref(self, val):
if len(self.indices) == 1:
self.indices[0].ref = val
elif len(self.indices) == 0:
##no index, so set the individual refs
for t in trades:
t.index.ref = val
elif len(self.indices) == len(val):
for index, val in zip(self.indices, val):
index.ref = val
else:
raise ValueError("The number of refs doesn't match the number of indices")
@property
def delta(self):
"""returns the equivalent protection notional
makes sense only where there is a single index."""
return sum([getattr(t, 'delta', -t._direction) * t.notional for t in self.trades])
@property
def gamma(self):
return sum([getattr(t, 'gamma', 0) * t.notional for t in self.trades])
@property
def dv01(self):
return sum(t.dv01 for t in self.trades)
|