-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathtypes.py
283 lines (221 loc) · 9.3 KB
/
types.py
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
"""# Types
Various Python types used in the model
"""
import numpy as np
import sys
# See https://docs.python.org/3/library/dataclasses.html
from dataclasses import dataclass
from enforce_typing import enforce_types
from typing import Union, List, Dict
from abc import ABCMeta, abstractmethod
# If Python version is greater than equal to 3.8, import from typing module
# Else also import from typing_extensions module
if sys.version_info >= (3, 8):
from typing import TypedDict, List, Callable, NamedTuple
else:
from typing import List, NamedTuple
from typing_extensions import TypedDict, Callable
# Generic types
Uninitialized = np.nan
Percentage = float
APR = float
# Simulation types
Timestep = int
Run = int
StateVariableKey = str
# Assets
USD = Union[int, float]
FEI = Union[int, float]
UNI = Union[int, float]
VolatileAssetUnits = Union[int, float]
StableAssetUnits = Union[int, float]
# Weights
CAMWeights = List[FEI]
CAMDeltas = Dict[str, FEI]
@enforce_types
@dataclass(frozen=False)
class Deposit(metaclass=ABCMeta):
"""## Generic Deposit
A generic Deposit class used for PCV and User Deposits.
Private variables with prefix _ should only be set directly in model configuration (e.g. initial state)
and be set using appropriate methods during runtime. This ensures the right assertions are made, and makes
it easy to check where "unsafe" updates are made.
"""
asset: str
"""Asset held in the deposit e.g. 'ETH'"""
deposit_location: str
"""Location of Deposit (used for State Variable naming) e.g. 'liquidity_pool', 'money_market', 'idle', 'yield_bearing'"""
_balance: Union[FEI, StableAssetUnits, VolatileAssetUnits] = 0.0
# Asset value must be initialized using appropriate method with asset price argument
_asset_value: USD = Uninitialized
_yield_accrued: Union[FEI, StableAssetUnits, VolatileAssetUnits] = 0.0
# Yield value must be initialized using appropriate method with asset price argument
_yield_value: USD = Uninitialized
_yield_rate: APR = 0.0
@property
def key(self) -> str:
return "_".join([self.asset, self.deposit_location, self._deposit_type])
@property
@abstractmethod
def _deposit_type(self) -> str:
"""Private variable, type of Deposit (used for State Variable naming) e.g. 'pcv_deposit' or 'user_deposit'"""
pass
def __add__(self, other):
"""
Add two Deposits of the same type (subclass) and asset together. Returns a new Deposit instance.
"""
assert (
self._deposit_type == other._deposit_type
), "Can't add two unlike Deposit instances together"
assert self.asset == other.asset, "Can't add two unlike Deposit instances together"
from_asset_price = other.asset_value / other.balance if other.balance else 0
to_asset_price = self.asset_value / self.balance if self.balance else 0
assert (
from_asset_price == to_asset_price
), "Can't add two Deposit instances with different implicit asset prices"
assert (
self.yield_rate == other.yield_rate
), "Can't add two Deposit instances with different yield rates"
return self.__class__(
asset=self.asset,
deposit_location=self.deposit_location,
_balance=self.balance + other.balance,
_asset_value=self.asset_value + other.asset_value,
_yield_accrued=self.yield_accrued + other.yield_accrued,
_yield_value=self.yield_value + other.yield_value,
_yield_rate=self.yield_rate,
)
def deposit(self, amount, asset_price: USD):
"""
Deposit an amount, in asset units, into Deposit balance,
and update the asset value.
"""
assert amount >= 0, "Amount must be a positive value"
assert asset_price >= 0, "Asset price must be a positive value"
self._balance += amount
self._asset_value = self._balance * asset_price
return self
def withdraw(self, amount, asset_price: USD):
"""
Withdraw an amount, in asset units, from the Deposit balance,
and update the asset value.
"""
assert amount >= 0, "Amount must be a positive value"
assert amount <= self.balance, "Amount must be less than balance"
assert asset_price >= 0, "Asset price must be a positive value"
self._balance -= amount
self._asset_value = self._balance * asset_price
return self
def transfer(self, to, amount, from_asset_price=None, to_asset_price=None):
"""
Transfer an amount from the balance of one Deposit to the balance of another.
If either asset_price is not passed as an arugment, it is calculated from the respective Deposit balance and asset_value.
"""
if not from_asset_price:
from_asset_price = self.asset_value / self.balance if self.balance else to_asset_price
if not to_asset_price:
to_asset_price = to.asset_value / to.balance if to.balance else from_asset_price
self.withdraw(amount, from_asset_price)
to.deposit(
amount * from_asset_price / to_asset_price if to_asset_price else amount,
to_asset_price,
)
return self, to
def set_balance(self, balance, asset_price: USD):
"""
Directly set the balance for the Deposit,
and update the asset value.
"""
assert balance >= 0, "Balance must be a positive value"
assert asset_price >= 0, "Asset price must be a positive value"
self._balance = balance
self._asset_value = self._balance * asset_price
return self
def accrue_yield(self, period_in_days: int, asset_price: USD):
"""
Accrue yield on balance to yield_accrued based on yield_rate with simple interest.
Args:
period_in_days (int): Requires period_in_days to convert annualized yield_rate to period yield rate
asset_price (float): Requires asset_price to update the yield_value
Returns:
The yield accrued in the current timestep
"""
assert asset_price >= 0, "Asset price must be a positive value"
assert period_in_days >= 0, "Period in days must be a positive value"
delta_yield_accrued = self._balance * (self._yield_rate * period_in_days / 365)
self._yield_accrued += delta_yield_accrued
self._yield_value = self._yield_accrued * asset_price
return delta_yield_accrued
def accrue_yield_compounded(self, period_in_days, asset_price: USD):
"""
Accrue yield on balance to yield_accrued based on yield_rate with compound interest.
Args:
period_in_days (int): Requires period_in_days to convert annualized yield_rate to period yield rate
asset_price (float): Requires asset_price to update the yield_value
Returns:
The PCVDeposit instance
"""
assert asset_price >= 0, "Asset price must be a positive value"
assert period_in_days >= 0, "Period in days must be a positive value"
self.accrue_yield(period_in_days, asset_price)
self.transfer_yield(self, self._yield_accrued, asset_price)
assert self._yield_accrued == 0
assert self._yield_value == 0
return self
def transfer_yield(self, to, amount, asset_price: USD):
"""
Transfer an amount from the yield_accrued of one Deposit to the balance of another (can include own balance).
"""
assert amount <= self._yield_accrued, "Transfer amount greater than yield accrued"
assert amount >= 0, "Amount must be a positive value"
assert asset_price >= 0, "Asset price must be a positive value"
self._yield_accrued -= amount
to.deposit(amount, asset_price)
self._yield_value = self._yield_accrued * asset_price
return self, to
def update_asset_value(self, asset_price: USD):
"""
Update the asset value based on the current asset price.
"""
self._asset_value = self._balance * asset_price
return self
@property
def balance(self):
"""Balance in asset units"""
return self._balance
@property
def asset_value(self):
"""Value of balance in USD"""
return self._asset_value
@property
def yield_accrued(self):
"""Yield accrued on balance (simple or compound interest) in asset units"""
return self._yield_accrued
@property
def yield_value(self):
"""Value of yield accrued in USD"""
return self._yield_value
@property
def yield_rate(self):
"""Annualized yield rate (as APR or simple interest rate without compounding)"""
return self._yield_rate
@yield_rate.setter
def yield_rate(self, new_yield_rate):
assert new_yield_rate >= 0, new_yield_rate
self._yield_rate = new_yield_rate
@enforce_types
@dataclass(frozen=False)
class PCVDeposit(Deposit):
"""## PCV Deposit
Inherits from Deposit class.
"""
_deposit_type = "pcv_deposit"
"""Implements abstract attribute from Deposit class"""
@enforce_types
@dataclass(frozen=False)
class UserDeposit(Deposit):
"""## User Deposit
Inherits from Deposit class.
"""
_deposit_type = "user_deposit"
"""Implements abstract attribute from Deposit class"""