-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodels.py
More file actions
159 lines (113 loc) · 4.98 KB
/
models.py
File metadata and controls
159 lines (113 loc) · 4.98 KB
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
from __future__ import annotations
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Dict, Iterable, List, Optional
class Instrument(ABC):
"""Base domain object representing a financial instrument."""
def __init__(self, symbol: str, price: float, sector: Optional[str] = None) -> None:
self.symbol = symbol
self.price = price
self.sector = sector
def get_metrics(self) -> Dict[str, float]:
"""Return base metrics shared by every instrument."""
return {"price": self.price}
def __repr__(self) -> str: # pragma: no cover - debug helper
return f"{self.__class__.__name__}(symbol={self.symbol}, price={self.price})"
class Stock(Instrument):
def __init__(self, symbol: str, price: float, sector: Optional[str], issuer: Optional[str]) -> None:
super().__init__(symbol, price, sector)
self.issuer = issuer
class Bond(Instrument):
def __init__(
self,
symbol: str,
price: float,
sector: Optional[str],
issuer: Optional[str],
maturity: Optional[datetime],
) -> None:
super().__init__(symbol, price, sector)
self.issuer = issuer
self.maturity = maturity
class ETF(Instrument):
def __init__(self, symbol: str, price: float, sector: Optional[str], issuer: Optional[str]) -> None:
super().__init__(symbol, price, sector)
self.issuer = issuer
class InstrumentDecorator(Instrument):
"""Base decorator for extending instrument analytics without altering the core class."""
def __init__(self, instrument: Instrument) -> None:
super().__init__(instrument.symbol, instrument.price, instrument.sector)
self._instrument = instrument
def get_metrics(self) -> Dict[str, float]:
return self._instrument.get_metrics()
def __getattr__(self, item: str):
return getattr(self._instrument, item)
class VolatilityDecorator(InstrumentDecorator):
def __init__(self, instrument: Instrument, price_history: Iterable[float]) -> None:
super().__init__(instrument)
self._price_history = list(price_history)
def get_metrics(self) -> Dict[str, float]:
from analytics import calculate_volatility
metrics = super().get_metrics()
metrics["volatility"] = calculate_volatility(self._price_history)
return metrics
class BetaDecorator(InstrumentDecorator):
def __init__(
self, instrument: Instrument, asset_prices: Iterable[float], benchmark_prices: Iterable[float]
) -> None:
super().__init__(instrument)
self._asset_prices = list(asset_prices)
self._benchmark_prices = list(benchmark_prices)
def get_metrics(self) -> Dict[str, float]:
from analytics import calculate_beta
metrics = super().get_metrics()
metrics["beta"] = calculate_beta(self._asset_prices, self._benchmark_prices)
return metrics
class DrawdownDecorator(InstrumentDecorator):
def __init__(self, instrument: Instrument, price_history: Iterable[float]) -> None:
super().__init__(instrument)
self._price_history = list(price_history)
def get_metrics(self) -> Dict[str, float]:
from analytics import calculate_max_drawdown
metrics = super().get_metrics()
metrics["max_drawdown"] = calculate_max_drawdown(self._price_history)
return metrics
class PortfolioComponent(ABC):
"""Composite root for positions and nested portfolios."""
@abstractmethod
def get_value(self) -> float:
raise NotImplementedError
@abstractmethod
def get_positions(self) -> List["Position"]:
raise NotImplementedError
class Position(PortfolioComponent):
def __init__(self, symbol: str, quantity: float, price: float) -> None:
self.symbol = symbol
self.quantity = quantity
self.price = price
def get_value(self) -> float:
return self.quantity * self.price
def get_positions(self) -> List["Position"]:
return [self]
def __repr__(self) -> str: # pragma: no cover - debug helper
return f"Position(symbol={self.symbol}, quantity={self.quantity}, price={self.price})"
class PortfolioGroup(PortfolioComponent):
def __init__(self, name: str) -> None:
self.name = name
self._components: List[PortfolioComponent] = []
def add(self, component: PortfolioComponent) -> None:
self._components.append(component)
def get_value(self) -> float:
return sum(component.get_value() for component in self._components)
def get_positions(self) -> List[Position]:
positions: List[Position] = []
for component in self._components:
positions.extend(component.get_positions())
return positions
@property
def components(self) -> List[PortfolioComponent]:
return list(self._components)
class Portfolio(PortfolioGroup):
def __init__(self, name: str, owner: Optional[str]) -> None:
super().__init__(name)
self.owner = owner