Skip to content

Commit

Permalink
Merge pull request #263 from cvxgrp/262-futures-on-subgrid
Browse files Browse the repository at this point in the history
262 futures on subgrid
  • Loading branch information
tschm authored Dec 19, 2023
2 parents 9030bc5 + 4847ea2 commit 58ac1f4
Show file tree
Hide file tree
Showing 15 changed files with 14,138 additions and 346 deletions.
20 changes: 19 additions & 1 deletion cvx/simulator/_abc/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ def __post_init__(self) -> None:
assert self.prices.index.is_monotonic_increasing
assert self.prices.index.is_unique

self._state = State()

self._units = pd.DataFrame(
index=self.prices.index,
columns=self.prices.columns,
Expand Down Expand Up @@ -131,14 +133,15 @@ def position(self) -> pd.Series:
return self._units.loc[self._state.time]

@position.setter
@abstractmethod
def position(self, position: pd.Series) -> None:
"""
The position property returns the current position of the portfolio.
It returns a pandas Series object containing the current position of the portfolio.
Returns: pd.Series: a pandas Series object containing the current position of the portfolio.
"""
self._units.loc[self._state.time, self._state.assets] = position
self._state.position = position

@property
def cashposition(self):
Expand Down Expand Up @@ -177,3 +180,18 @@ class with the attributes (prices, units, initial_cash, trading_cost_model) equa
"""

# return EquityPortfolio(prices=self.prices, units=self.units, cash=self.cash)

@property
def weights(self) -> np.array:
"""
Get the current weights from the state
"""
return self._state.weights[self._state.assets].values

@weights.setter
def weights(self, weights: np.array) -> None:
"""
The weights property sets the current weights of the portfolio.
We convert the weights to positions using the current prices and the NAV
"""
self.position = self._state.nav * weights / self.current_prices
89 changes: 80 additions & 9 deletions cvx/simulator/_abc/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime

Expand All @@ -20,13 +19,36 @@


@dataclass()
class State(ABC):
prices: pd.Series = None

class State:
_prices: pd.Series = None
_position: pd.Series = None
_trades: pd.Series = None
_time: datetime = None
_days: int = 0
_profit: float = 0.0
_aum: float = 0.0
_cash: float = 1e6

@property
def cash(self):
return self._cash

@cash.setter
def cash(self, cash: float):
self._cash = cash
self.aum = cash + self.value

@property
def nav(self) -> float:
"""
The nav property computes the net asset value (NAV) of the portfolio,
which is the sum of the current value of the
portfolio as determined by the value property,
and the current amount of cash available in the portfolio.
"""
# assert np.isclose(self.value + self.cash, self.aum), f"{self.value + self.cash} != {self.aum}"
# return self.value + self.cash
return self.aum

@property
def value(self) -> float:
Expand Down Expand Up @@ -54,12 +76,19 @@ def position(self):
return self._position

@position.setter
@abstractmethod
def position(self, position: np.array):
"""
Update the position of the state. Computes the required trades
and updates other quantities (e.g. cash) accordingly.
"""
# update the position
position = pd.Series(index=self.assets, data=position)

# compute the trades (can be fractional)
self._trades = position.subtract(self.position, fill_value=0.0)

# update only now as otherwise the trades would be wrong
self._position = position

@property
def gmv(self):
Expand Down Expand Up @@ -110,8 +139,50 @@ def mask(self):
"""construct true/false mask for assets with missing prices"""
return np.isfinite(self.prices.values)

@property
def prices(self):
return self._prices

# if __name__ == '__main__':
# mask = np.isfinite([np.infty, 2.0, np.NaN, 3.0])
# x = np.array([1,2,3,4])
# print(x[mask])
@prices.setter
def prices(self, prices):
value_before = (self.prices * self.position).sum() # self.cashposition.sum()
value_after = (prices * self.position).sum()

self._prices = prices
self._profit = value_after - value_before
self.aum += self.profit

@property
def profit(self):
return self._profit

@property
def aum(self):
return self._aum

@aum.setter
def aum(self, aum):
self._aum = aum

@property
def weights(self) -> pd.Series:
"""
The weights property computes the weighting of each asset in the current
portfolio as a fraction of the total portfolio value (nav).
Returns:
a pandas series object containing the weighting of each asset as a
fraction of the total portfolio value. If the positions are still
missing, then a series of zeroes is returned.
"""
assert np.isclose(self.nav, self.aum), f"{self.nav} != {self.aum}"
return self.cashposition / self.nav

@property
def leverage(self) -> float:
"""
The `leverage` property computes the leverage of the portfolio,
which is the sum of the absolute values of the portfolio weights.
"""
return float(self.weights.abs().sum())
22 changes: 2 additions & 20 deletions cvx/simulator/equity/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@

from .._abc.builder import Builder
from .portfolio import EquityPortfolio
from .state import EquityState


@dataclass
class EquityBuilder(Builder):
initial_cash: float = 1e6
_state: EquityState = None
_cash: pd.Series = None

def __post_init__(self) -> None:
Expand All @@ -46,24 +44,8 @@ def __post_init__(self) -> None:
super().__post_init__()

self._cash = pd.Series(index=self.index, data=np.NaN)
self._state = EquityState()
self._state.cash = self.initial_cash

@property
def weights(self) -> np.array:
"""
Get the current weights from the state
"""
return self._state.weights[self._state.assets].values

@weights.setter
def weights(self, weights: np.array) -> None:
"""
The weights property sets the current weights of the portfolio.
We convert the weights to positions using the current prices and the NAV
"""
self.position = self._state.nav * weights / self.current_prices

@Builder.position.setter
def position(self, position: pd.Series) -> None:
"""
Expand All @@ -72,9 +54,9 @@ def position(self, position: pd.Series) -> None:
Returns: pd.Series: a pandas Series object containing the current position of the portfolio.
"""
self._units.loc[self._state.time, self._state.assets] = position
self._state.position = position
Builder.position.__set__(self, position)

self._state.cash -= self._state.gross.sum()
self._cash[self._state.time] = self._state.cash

@property
Expand Down
100 changes: 0 additions & 100 deletions cvx/simulator/equity/state.py

This file was deleted.

40 changes: 6 additions & 34 deletions cvx/simulator/futures/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,23 @@
# limitations under the License.
from dataclasses import dataclass

import pandas as pd

from cvx.simulator.futures.portfolio import FuturesPortfolio
from cvx.simulator.futures.state import FuturesState
from cvx.simulator.utils.rescale import returns2prices

from .._abc.builder import Builder


@dataclass
class FuturesBuilder(Builder):
def build(self):
"""Build Futures Portfolio"""
return FuturesPortfolio(prices=self.prices, units=self.units, aum=self.aum)

aum: float = 1e6
_state: FuturesState = None

def __post_init__(self) -> None:
"""
The __post_init__ method is a special method of initialized instances
of the _Builder class and is called after initialization.
It sets the initial amount of cash in the portfolio to be equal to the input initial_cash parameter.
The method takes no input parameter. It initializes the cash attribute in the internal
_State object with the initial amount of cash in the portfolio, self.initial_cash.
Note that this method is often used in Python classes for additional initialization routines
that can only be performed after the object is fully initialized. __post_init__
is called automatically after the object initialization.
"""

def __post_init__(self):
super().__post_init__()
self._state = FuturesState()
self._state.cash = self.aum

def build(self):
"""Build Futures Portfolio"""
return FuturesPortfolio(prices=self.prices, units=self.units, aum=self.aum)

@classmethod
def from_returns(cls, returns):
Expand All @@ -55,14 +38,3 @@ def from_returns(cls, returns):

prices = returns2prices(returns)
return cls(prices=prices)

@Builder.position.setter
def position(self, position: pd.Series) -> None:
"""
The position property returns the current position of the portfolio.
It returns a pandas Series object containing the current position of the portfolio.
Returns: pd.Series: a pandas Series object containing the current position of the portfolio.
"""
self._units.loc[self._state.time, self._state.assets] = position
self._state.position = position
Loading

0 comments on commit 58ac1f4

Please sign in to comment.