investos
Advanced tools
| from investos.utils.hydrate import * |
| import os | ||
| import pickle | ||
| import investos as inv | ||
| class HydrateMixin: | ||
| _excluded_attrs = {"strategy", "benchmark", "risk_free"} | ||
| def _implied_attributes(self): | ||
| props = { | ||
| name | ||
| for name in dir(type(self)) | ||
| if isinstance(getattr(type(self), name), property) | ||
| } | ||
| return self._excluded_attrs.union(props) | ||
| def __getstate__(self): | ||
| clean_state = {} | ||
| self.__version__ = inv.__version__ | ||
| skip_keys = self._implied_attributes() | ||
| for k, v in self.__dict__.items(): | ||
| if k in skip_keys: | ||
| continue | ||
| try: | ||
| pickle.dumps(v) | ||
| clean_state[k] = v | ||
| except Exception as e: | ||
| print(f"⚠️ Skipping non-pickleable attribute: {k} ({type(v)}): {e}") | ||
| return clean_state | ||
| def __setstate__(self, state): | ||
| self.__dict__.update(state) | ||
| def dehydrate_to_disk(self, path: str, obj_name: str = "object.pkl"): | ||
| os.makedirs(path, exist_ok=True) | ||
| with open(os.path.join(path, obj_name), "wb") as f: | ||
| pickle.dump(self, f) | ||
| @classmethod | ||
| def rehydrate_from_disk(cls, path: str, obj_name: str = "object.pkl"): | ||
| with open(os.path.join(path, obj_name), "rb") as f: | ||
| return pickle.load(f) |
| import investos.portfolio | ||
| __version__ = "0.6.2" | ||
| __version__ = "0.6.3" | ||
@@ -5,0 +5,0 @@ import os |
@@ -9,5 +9,6 @@ import collections | ||
| from investos.util import clip_for_dates | ||
| from investos.utils import HydrateMixin | ||
| class BaseResult(SaveResult): | ||
| class BaseResult(SaveResult, HydrateMixin): | ||
| """The `Result` class captures portfolio data and performance for each asset and period over time. | ||
@@ -146,2 +147,43 @@ | ||
| @property | ||
| def returns_by_year(self) -> pd.Series: | ||
| """Returns a pandas Series of the cumulative returns, grouped by year.""" | ||
| grouped_returns = ( | ||
| (1 + self.returns) | ||
| .groupby([self.returns.index.year]) | ||
| .apply(lambda x: x.prod() - 1) | ||
| ) | ||
| # Create a proper DatetimeIndex | ||
| grouped_returns.index = pd.to_datetime(grouped_returns.index, format="%Y") | ||
| return grouped_returns | ||
| @property | ||
| def returns_by_quarter(self) -> pd.Series: | ||
| """Returns a pandas Series of the cumulative returns, grouped by quarter.""" | ||
| grouped_returns = ( | ||
| (1 + self.returns) | ||
| .groupby(self.returns.index.to_period("Q")) | ||
| .apply(lambda x: x.prod() - 1) | ||
| ) | ||
| # Create a proper DatetimeIndex | ||
| grouped_returns.index = grouped_returns.index.to_timestamp(how="start") | ||
| return grouped_returns | ||
| @property | ||
| def returns_by_month(self) -> pd.Series: | ||
| """Returns a pandas Series of the cumulative returns, grouped by month.""" | ||
| grouped_returns = ( | ||
| (1 + self.returns) | ||
| .groupby([self.returns.index.year, self.returns.index.month]) | ||
| .apply(lambda x: x.prod() - 1) | ||
| ) | ||
| # Convert MultiIndex (year, month) to DatetimeIndex | ||
| grouped_returns.index = pd.to_datetime( | ||
| [f"{year}-{month:02d}" for year, month in grouped_returns.index] | ||
| ) | ||
| return grouped_returns | ||
| @property | ||
| def portfolio_hit_rate(self): | ||
@@ -148,0 +190,0 @@ return (self.returns > 0).mean() |
@@ -68,5 +68,6 @@ import requests | ||
| self.save_chart_historical_value() | ||
| self.save_chart_grouped_return() | ||
| self.save_chart_historical_leverage() | ||
| self.save_cumulative_returns() | ||
| self.save_chart_rolling_sharpe() | ||
| self.save_chart_historical_leverage() | ||
@@ -98,2 +99,31 @@ def save_chart_historical_value(self): | ||
| def save_chart_grouped_return(self): | ||
| json_body = { | ||
| "chart": { | ||
| "title": "Returns by period", | ||
| "type": "bar", | ||
| "chartable_type": "Backtest", | ||
| "chartable_id": self.backtest_id, | ||
| "chart_traces": [ | ||
| { | ||
| "x_name": "Dates", | ||
| "y_name": "Month", | ||
| "x_values": [str(el) for el in self.returns_by_month.index], | ||
| "y_values": list(self.returns_by_month.values), | ||
| "config": { | ||
| "type": "bar", | ||
| }, | ||
| }, | ||
| { | ||
| "x_name": "Dates", | ||
| "y_name": "Year", | ||
| "x_values": [str(el) for el in self.returns_by_year.index], | ||
| "y_values": list(self.returns_by_year.values), | ||
| }, | ||
| ], | ||
| } | ||
| } | ||
| self._save_chart(json_body) | ||
| def save_chart_rolling_sharpe(self): | ||
@@ -100,0 +130,0 @@ num_periods = self.v.shape[0] - 1 |
+1
-1
| Metadata-Version: 2.1 | ||
| Name: investos | ||
| Version: 0.6.2 | ||
| Version: 0.6.3 | ||
| Summary: Reliable backtesting and portfolio optimization for investors who want to focus on generating alpha | ||
@@ -5,0 +5,0 @@ Home-page: https://investos.io/ |
+1
-1
| [tool.poetry] | ||
| name = "investos" | ||
| version = "0.6.2" | ||
| version = "0.6.3" | ||
| description = "Reliable backtesting and portfolio optimization for investors who want to focus on generating alpha" | ||
@@ -5,0 +5,0 @@ authors = ["Charlie Reese", "ForecastOS"] |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
142273
2.87%37
5.71%2915
3.55%