mainsequence 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mainsequence/__init__.py +0 -0
- mainsequence/__main__.py +9 -0
- mainsequence/cli/__init__.py +1 -0
- mainsequence/cli/api.py +157 -0
- mainsequence/cli/cli.py +442 -0
- mainsequence/cli/config.py +78 -0
- mainsequence/cli/ssh_utils.py +126 -0
- mainsequence/client/__init__.py +17 -0
- mainsequence/client/base.py +431 -0
- mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- mainsequence/client/data_sources_interfaces/duckdb.py +1468 -0
- mainsequence/client/data_sources_interfaces/timescale.py +479 -0
- mainsequence/client/models_helpers.py +113 -0
- mainsequence/client/models_report_studio.py +412 -0
- mainsequence/client/models_tdag.py +2276 -0
- mainsequence/client/models_vam.py +1983 -0
- mainsequence/client/utils.py +387 -0
- mainsequence/dashboards/__init__.py +0 -0
- mainsequence/dashboards/streamlit/__init__.py +0 -0
- mainsequence/dashboards/streamlit/assets/config.toml +12 -0
- mainsequence/dashboards/streamlit/assets/favicon.png +0 -0
- mainsequence/dashboards/streamlit/assets/logo.png +0 -0
- mainsequence/dashboards/streamlit/core/__init__.py +0 -0
- mainsequence/dashboards/streamlit/core/theme.py +212 -0
- mainsequence/dashboards/streamlit/pages/__init__.py +0 -0
- mainsequence/dashboards/streamlit/scaffold.py +220 -0
- mainsequence/instrumentation/__init__.py +7 -0
- mainsequence/instrumentation/utils.py +101 -0
- mainsequence/instruments/__init__.py +1 -0
- mainsequence/instruments/data_interface/__init__.py +10 -0
- mainsequence/instruments/data_interface/data_interface.py +361 -0
- mainsequence/instruments/instruments/__init__.py +3 -0
- mainsequence/instruments/instruments/base_instrument.py +85 -0
- mainsequence/instruments/instruments/bond.py +447 -0
- mainsequence/instruments/instruments/european_option.py +74 -0
- mainsequence/instruments/instruments/interest_rate_swap.py +217 -0
- mainsequence/instruments/instruments/json_codec.py +585 -0
- mainsequence/instruments/instruments/knockout_fx_option.py +146 -0
- mainsequence/instruments/instruments/position.py +475 -0
- mainsequence/instruments/instruments/ql_fields.py +239 -0
- mainsequence/instruments/instruments/vanilla_fx_option.py +107 -0
- mainsequence/instruments/pricing_models/__init__.py +0 -0
- mainsequence/instruments/pricing_models/black_scholes.py +49 -0
- mainsequence/instruments/pricing_models/bond_pricer.py +182 -0
- mainsequence/instruments/pricing_models/fx_option_pricer.py +90 -0
- mainsequence/instruments/pricing_models/indices.py +350 -0
- mainsequence/instruments/pricing_models/knockout_fx_pricer.py +209 -0
- mainsequence/instruments/pricing_models/swap_pricer.py +502 -0
- mainsequence/instruments/settings.py +175 -0
- mainsequence/instruments/utils.py +29 -0
- mainsequence/logconf.py +284 -0
- mainsequence/reportbuilder/__init__.py +0 -0
- mainsequence/reportbuilder/__main__.py +0 -0
- mainsequence/reportbuilder/examples/ms_template_report.py +706 -0
- mainsequence/reportbuilder/model.py +713 -0
- mainsequence/reportbuilder/slide_templates.py +532 -0
- mainsequence/tdag/__init__.py +8 -0
- mainsequence/tdag/__main__.py +0 -0
- mainsequence/tdag/config.py +129 -0
- mainsequence/tdag/data_nodes/__init__.py +12 -0
- mainsequence/tdag/data_nodes/build_operations.py +751 -0
- mainsequence/tdag/data_nodes/data_nodes.py +1292 -0
- mainsequence/tdag/data_nodes/persist_managers.py +812 -0
- mainsequence/tdag/data_nodes/run_operations.py +543 -0
- mainsequence/tdag/data_nodes/utils.py +24 -0
- mainsequence/tdag/future_registry.py +25 -0
- mainsequence/tdag/utils.py +40 -0
- mainsequence/virtualfundbuilder/__init__.py +45 -0
- mainsequence/virtualfundbuilder/__main__.py +235 -0
- mainsequence/virtualfundbuilder/agent_interface.py +77 -0
- mainsequence/virtualfundbuilder/config_handling.py +86 -0
- mainsequence/virtualfundbuilder/contrib/__init__.py +0 -0
- mainsequence/virtualfundbuilder/contrib/apps/__init__.py +8 -0
- mainsequence/virtualfundbuilder/contrib/apps/etf_replicator_app.py +164 -0
- mainsequence/virtualfundbuilder/contrib/apps/generate_report.py +292 -0
- mainsequence/virtualfundbuilder/contrib/apps/load_external_portfolio.py +107 -0
- mainsequence/virtualfundbuilder/contrib/apps/news_app.py +437 -0
- mainsequence/virtualfundbuilder/contrib/apps/portfolio_report_app.py +91 -0
- mainsequence/virtualfundbuilder/contrib/apps/portfolio_table.py +95 -0
- mainsequence/virtualfundbuilder/contrib/apps/run_named_portfolio.py +45 -0
- mainsequence/virtualfundbuilder/contrib/apps/run_portfolio.py +40 -0
- mainsequence/virtualfundbuilder/contrib/apps/templates/base.html +147 -0
- mainsequence/virtualfundbuilder/contrib/apps/templates/report.html +77 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/__init__.py +5 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/external_weights.py +61 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/intraday_trend.py +149 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/market_cap.py +310 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/mock_signal.py +78 -0
- mainsequence/virtualfundbuilder/contrib/data_nodes/portfolio_replicator.py +269 -0
- mainsequence/virtualfundbuilder/contrib/prices/__init__.py +1 -0
- mainsequence/virtualfundbuilder/contrib/prices/data_nodes.py +810 -0
- mainsequence/virtualfundbuilder/contrib/prices/utils.py +11 -0
- mainsequence/virtualfundbuilder/contrib/rebalance_strategies/__init__.py +1 -0
- mainsequence/virtualfundbuilder/contrib/rebalance_strategies/rebalance_strategies.py +313 -0
- mainsequence/virtualfundbuilder/data_nodes.py +637 -0
- mainsequence/virtualfundbuilder/enums.py +23 -0
- mainsequence/virtualfundbuilder/models.py +282 -0
- mainsequence/virtualfundbuilder/notebook_handling.py +42 -0
- mainsequence/virtualfundbuilder/portfolio_interface.py +272 -0
- mainsequence/virtualfundbuilder/resource_factory/__init__.py +0 -0
- mainsequence/virtualfundbuilder/resource_factory/app_factory.py +170 -0
- mainsequence/virtualfundbuilder/resource_factory/base_factory.py +238 -0
- mainsequence/virtualfundbuilder/resource_factory/rebalance_factory.py +101 -0
- mainsequence/virtualfundbuilder/resource_factory/signal_factory.py +183 -0
- mainsequence/virtualfundbuilder/utils.py +381 -0
- mainsequence-2.0.0.dist-info/METADATA +105 -0
- mainsequence-2.0.0.dist-info/RECORD +110 -0
- mainsequence-2.0.0.dist-info/WHEEL +5 -0
- mainsequence-2.0.0.dist-info/licenses/LICENSE +40 -0
- mainsequence-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
def transform_frequency_to_seconds(frequency_id:str)->int:
|
2
|
+
if frequency_id in ["1min", "1m"]:
|
3
|
+
return 60
|
4
|
+
elif frequency_id in ["5min", "5m"]:
|
5
|
+
return 60 * 5
|
6
|
+
elif frequency_id in ["15min", "15m"]:
|
7
|
+
return 60 * 15
|
8
|
+
elif frequency_id in ["1days", "1d"]:
|
9
|
+
return 60 * 60 * 24
|
10
|
+
else:
|
11
|
+
raise NotImplementedError
|
@@ -0,0 +1 @@
|
|
1
|
+
from .rebalance_strategies import *
|
@@ -0,0 +1,313 @@
|
|
1
|
+
import datetime
|
2
|
+
|
3
|
+
import pandas as pd
|
4
|
+
|
5
|
+
from mainsequence.virtualfundbuilder.enums import RebalanceFrequencyStrategyName, PriceTypeNames
|
6
|
+
from mainsequence.virtualfundbuilder.resource_factory.rebalance_factory import RebalanceStrategyBase, register_rebalance_class
|
7
|
+
|
8
|
+
|
9
|
+
@register_rebalance_class(register_in_agent=True)
|
10
|
+
class VolumeParticipation(RebalanceStrategyBase):
|
11
|
+
"""
|
12
|
+
This rebalance strategy implies volume participation with no market impact.
|
13
|
+
i.e. that the execution price will be vwap and it will never execute more than max_percent_volume_in_bar
|
14
|
+
"""
|
15
|
+
|
16
|
+
def __init__(
|
17
|
+
self,
|
18
|
+
rebalance_start: str = "9:00",
|
19
|
+
rebalance_end: str = "23:00",
|
20
|
+
rebalance_frequency_strategy: RebalanceFrequencyStrategyName = RebalanceFrequencyStrategyName.DAILY,
|
21
|
+
max_percent_volume_in_bar: float = 0.01,
|
22
|
+
total_notional: float = 50000000,
|
23
|
+
*args, **kwargs
|
24
|
+
):
|
25
|
+
"""
|
26
|
+
Initializes the VolumeParticipation strategy.
|
27
|
+
|
28
|
+
Attributes:
|
29
|
+
rebalance_start (str): Start time for rebalancing, in "hh:mm" format.
|
30
|
+
rebalance_end (str): End time for rebalancing, in "hh:mm" format.
|
31
|
+
rebalance_frequency_strategy (RebalanceFrequencyStrategyName): Rebalance frequency.
|
32
|
+
max_percent_volume_in_bar (float): Maximum percentage of volume to trade in a bar.
|
33
|
+
total_notional (int): Initial notional invested in the strategy.
|
34
|
+
"""
|
35
|
+
self.rebalance_start = rebalance_start
|
36
|
+
self.rebalance_end = rebalance_end
|
37
|
+
self.rebalance_frequency_strategy = rebalance_frequency_strategy
|
38
|
+
self.max_percent_volume_in_bar = max_percent_volume_in_bar
|
39
|
+
self.total_notional = total_notional
|
40
|
+
|
41
|
+
super().__init__(*args, **kwargs)
|
42
|
+
|
43
|
+
def apply_rebalance_logic(
|
44
|
+
self,
|
45
|
+
last_rebalance_weights: pd.DataFrame,
|
46
|
+
start_date: datetime.datetime,
|
47
|
+
end_date: datetime.datetime,
|
48
|
+
signal_weights: pd.DataFrame,
|
49
|
+
prices_df: pd.DataFrame,
|
50
|
+
price_type: str,
|
51
|
+
) -> pd.DataFrame:
|
52
|
+
raise NotImplementedError
|
53
|
+
asset_list = list(signal_weights.columns)
|
54
|
+
start_time, end_time = pd.Timestamp(self.rebalance_start).time(), pd.Timestamp(self.rebalance_end).time()
|
55
|
+
|
56
|
+
rebalance_dates = self.calculate_rebalance_dates(
|
57
|
+
start=start_date,
|
58
|
+
end=end_date,
|
59
|
+
rebalance_frequency_strategy=self.rebalance_frequency_strategy,
|
60
|
+
calendar=self.calendar
|
61
|
+
)
|
62
|
+
|
63
|
+
signal_weights["day"] = signal_weights.index.floor("D")
|
64
|
+
|
65
|
+
volume_df = prices_df.reset_index().pivot(index="time_index", columns="asset_symbol", values="volume").fillna(0)
|
66
|
+
prices_df = prices_df.reset_index().pivot(index="time_index", columns="asset_symbol", values=price_type).fillna(
|
67
|
+
0)
|
68
|
+
|
69
|
+
rebalance_days = np.intersect1d(signal_weights["day"], rebalance_dates)
|
70
|
+
|
71
|
+
rebalance_weights = signal_weights[signal_weights["day"].isin(rebalance_days)]
|
72
|
+
rebalance_weights = rebalance_weights[rebalance_weights.index.time >= start_time]
|
73
|
+
rebalance_weights = rebalance_weights[rebalance_weights.index.time <= end_time]
|
74
|
+
|
75
|
+
rebalance_weights = rebalance_weights.set_index("day", append=True)
|
76
|
+
|
77
|
+
# calculcate the maximal participation volumes at each rebalancing steps
|
78
|
+
max_participation_volume = (volume_df * prices_df) * self.max_percent_volume_in_bar
|
79
|
+
max_participation_volume["day"] = max_participation_volume.index.floor("D")
|
80
|
+
max_participation_volume = max_participation_volume.set_index("day", append=True)
|
81
|
+
max_participation_volume = max_participation_volume[
|
82
|
+
max_participation_volume.index.isin(rebalance_weights.index)]
|
83
|
+
|
84
|
+
rebalance_weights = pd.concat(
|
85
|
+
objs=[rebalance_weights, max_participation_volume],
|
86
|
+
axis=1,
|
87
|
+
keys=["weights", "max_dollar_volume"]
|
88
|
+
)
|
89
|
+
|
90
|
+
if last_rebalance_weights is not None:
|
91
|
+
# get last rebalance weights
|
92
|
+
past_rebalance_weight = last_rebalance_weights["weights_current"].reset_index(level="time_index", drop=True)
|
93
|
+
else:
|
94
|
+
past_rebalance_weight = pd.Series(0, index=asset_list)
|
95
|
+
|
96
|
+
# recursively group by day
|
97
|
+
past_day_rebalance_weight = past_rebalance_weight
|
98
|
+
new_rebalance_weights = []
|
99
|
+
for day, day_df in tqdm(rebalance_weights.groupby("day"), desc="building volume participation"):
|
100
|
+
if (day_df.weights.max() - day_df.weights.min()).sum() != 0.0:
|
101
|
+
logger.warning("Signal weight in time period changes, using weights at rebalancing start")
|
102
|
+
day_df.loc[:, "weights"] = day_df["weights"].iloc[0].to_numpy()
|
103
|
+
|
104
|
+
# calculate changes in backtesting weights
|
105
|
+
weights_diff = day_df.weights - past_day_rebalance_weight
|
106
|
+
|
107
|
+
target_dollar_volume = np.abs(weights_diff) * self.total_notional
|
108
|
+
cumulative_dollar_volume = day_df.max_dollar_volume.fillna(0).cumsum()
|
109
|
+
weighted_volume_multiplier = (cumulative_dollar_volume / target_dollar_volume).replace([np.inf], np.nan)
|
110
|
+
weighted_volume_multiplier = weighted_volume_multiplier.fillna(0).map(lambda x: min(1.0, x))
|
111
|
+
|
112
|
+
new_rebalance_weights_day = past_day_rebalance_weight + weights_diff * weighted_volume_multiplier
|
113
|
+
new_rebalance_weights.append(new_rebalance_weights_day)
|
114
|
+
past_day_rebalance_weight = new_rebalance_weights_day.iloc[-1]
|
115
|
+
|
116
|
+
if len(new_rebalance_weights) == 0:
|
117
|
+
logger.info("No new rebalancing weights found - returning empty DataFrame")
|
118
|
+
return pd.DataFrame()
|
119
|
+
|
120
|
+
rebalance_weights = pd.concat(new_rebalance_weights, axis=0).reset_index("day", drop=True)
|
121
|
+
rebalance_weights_index = rebalance_weights.index
|
122
|
+
|
123
|
+
shifted_rebalance_weights = rebalance_weights.shift(1)
|
124
|
+
shifted_rebalance_weights.iloc[0] = past_rebalance_weight
|
125
|
+
rebalance_weights = pd.concat(
|
126
|
+
objs=[
|
127
|
+
rebalance_weights, shifted_rebalance_weights,
|
128
|
+
prices_df, prices_df.shift(1),
|
129
|
+
volume_df, volume_df.shift(1),
|
130
|
+
],
|
131
|
+
keys=["weights_current", "weights_before", "price_current", "price_before", "volume_current",
|
132
|
+
"volume_before"],
|
133
|
+
axis=1
|
134
|
+
)
|
135
|
+
|
136
|
+
# filter out values with no weights and set NaNs to 0 weight
|
137
|
+
rebalance_weights = rebalance_weights.loc[rebalance_weights_index].fillna(0)
|
138
|
+
|
139
|
+
logger.info(f"{len(rebalance_weights)} new rebalancing weights calculated")
|
140
|
+
return rebalance_weights
|
141
|
+
|
142
|
+
@register_rebalance_class(register_in_agent=True)
|
143
|
+
class TimeWeighted(RebalanceStrategyBase):
|
144
|
+
def __init__(
|
145
|
+
self,
|
146
|
+
rebalance_start: str = "9:00",
|
147
|
+
rebalance_end: str = "23:00",
|
148
|
+
rebalance_frequency_strategy: RebalanceFrequencyStrategyName = RebalanceFrequencyStrategyName.DAILY,
|
149
|
+
*args, **kwargs
|
150
|
+
):
|
151
|
+
"""
|
152
|
+
Initialize the time weighted rebalance strategy.
|
153
|
+
|
154
|
+
Attributes:
|
155
|
+
rebalance_start (str): Start time for rebalancing, in "hh:mm" format.
|
156
|
+
rebalance_end (str): End time for rebalancing, in "hh:mm" format.
|
157
|
+
rebalance_frequency_strategy (RebalanceFrequencyStrategyName): Rebalance frequency.
|
158
|
+
max_percent_volume_in_bar (float): Maximum percentage of volume to trade in a bar.
|
159
|
+
total_notional (int): Initial notional invested in the strategy.
|
160
|
+
"""
|
161
|
+
self.rebalance_start = rebalance_start
|
162
|
+
self.rebalance_end = rebalance_end
|
163
|
+
self.rebalance_frequency_strategy = rebalance_frequency_strategy
|
164
|
+
super().__init__(*args, **kwargs)
|
165
|
+
|
166
|
+
def apply_rebalance_logic(
|
167
|
+
self,
|
168
|
+
last_rebalance_weights: pd.DataFrame,
|
169
|
+
start_date: datetime.datetime,
|
170
|
+
end_date: datetime.datetime,
|
171
|
+
signal_weights: pd.DataFrame,
|
172
|
+
prices_df: pd.DataFrame,
|
173
|
+
price_type: PriceTypeNames,
|
174
|
+
) -> pd.DataFrame:
|
175
|
+
"""
|
176
|
+
Rebalance weights are set at start_time of rebalancing
|
177
|
+
|
178
|
+
Parameters
|
179
|
+
----------
|
180
|
+
signal_weights
|
181
|
+
rebalance_dates
|
182
|
+
|
183
|
+
Returns
|
184
|
+
-------
|
185
|
+
"""
|
186
|
+
raise NotImplementedError
|
187
|
+
# TODO change last_rebalance_weights, as the calculations are wrong if there was a past rebalance during the day (= during the current rebalance window)
|
188
|
+
asset_list = list(signal_weights.columns)
|
189
|
+
start_time, end_time = pd.Timestamp(self.rebalance_start).time(), pd.Timestamp(self.rebalance_end).time()
|
190
|
+
|
191
|
+
get_time_seconds = lambda x: x.hour * 3600 + x.minute * 60 + x.second
|
192
|
+
|
193
|
+
rebalance_dates = self.calculate_rebalance_dates(
|
194
|
+
start=start_date,
|
195
|
+
end=end_date,
|
196
|
+
rebalance_frequency_strategy=self.rebalance_frequency_strategy,
|
197
|
+
calendar=self.calendar
|
198
|
+
)
|
199
|
+
|
200
|
+
# get rebalance dates from signal weights
|
201
|
+
signal_weights["day"] = signal_weights.index.floor("D")
|
202
|
+
rebalance_days = np.intersect1d(signal_weights["day"], rebalance_dates)
|
203
|
+
rebalance_weights = signal_weights[signal_weights["day"].isin(rebalance_days)]
|
204
|
+
|
205
|
+
rebalance_weights = rebalance_weights[rebalance_weights.index.time >= start_time]
|
206
|
+
rebalance_weights = rebalance_weights[rebalance_weights.index.time <= end_time]
|
207
|
+
|
208
|
+
# get past rebalance dates to calculate rebalance steps
|
209
|
+
past_rebalance_weights = rebalance_weights.groupby("day").first().shift()
|
210
|
+
|
211
|
+
# if past backtesting weights exist, get last one; otherwise start at 0
|
212
|
+
if last_rebalance_weights is not None:
|
213
|
+
# get last rebalance weights
|
214
|
+
past_rebalance_weight = last_rebalance_weights["weights_current"].reset_index(level="time_index", drop=True)
|
215
|
+
else:
|
216
|
+
past_rebalance_weight = pd.Series(0, index=asset_list)
|
217
|
+
|
218
|
+
past_rebalance_weights.index += datetime.timedelta(seconds=get_time_seconds(start_time))
|
219
|
+
past_rebalance_weights = past_rebalance_weights.reindex(rebalance_weights.index).ffill()
|
220
|
+
|
221
|
+
# time-based multiplicator during rebalancing
|
222
|
+
time_weight = (rebalance_weights.index - rebalance_weights["day"]).dt.total_seconds()
|
223
|
+
time_weight = (time_weight - get_time_seconds(start_time)) / (
|
224
|
+
get_time_seconds(end_time) - get_time_seconds(start_time))
|
225
|
+
|
226
|
+
rebalance_weights = rebalance_weights.drop(columns=["day"])
|
227
|
+
|
228
|
+
# calculate new backtesting weights as past_weights + time_weight * diff_weights
|
229
|
+
diff_weights = rebalance_weights - past_rebalance_weights
|
230
|
+
rebalance_weights = past_rebalance_weights + diff_weights.multiply(time_weight, axis=0)
|
231
|
+
|
232
|
+
# when columns are not available weights and prices need to be set to 0
|
233
|
+
prices_df = prices_df.reset_index().pivot(index="time_index", columns="asset_symbol",
|
234
|
+
values=price_type).ffill().fillna(0)
|
235
|
+
valid_columns = rebalance_weights.columns[rebalance_weights.columns.isin(prices_df.columns)]
|
236
|
+
if len(valid_columns) != rebalance_weights.shape[1]:
|
237
|
+
rebalance_weights = rebalance_weights[valid_columns].copy()
|
238
|
+
rebalance_weights = rebalance_weights.divide(rebalance_weights.sum(axis=1), axis=0)
|
239
|
+
|
240
|
+
# make backtesting weights nan when there is no price
|
241
|
+
nan_mask = prices_df.loc[rebalance_weights.index].isna()
|
242
|
+
rebalance_weights[nan_mask] = np.nan
|
243
|
+
|
244
|
+
if len(rebalance_weights) == 0:
|
245
|
+
logger.info("No new rebalancing weights found - returning empty DataFrame")
|
246
|
+
return pd.DataFrame()
|
247
|
+
|
248
|
+
# create backtesting weights before and after rebalancing
|
249
|
+
shifted_rebalance_weights = rebalance_weights.shift(1)
|
250
|
+
shifted_rebalance_weights.iloc[0] = past_rebalance_weight
|
251
|
+
rebalance_weights = pd.concat(
|
252
|
+
objs=[shifted_rebalance_weights, rebalance_weights, prices_df, prices_df.shift(1)],
|
253
|
+
keys=["weights_before", "weights_current", "price_current", "price_before"],
|
254
|
+
axis=1
|
255
|
+
)
|
256
|
+
|
257
|
+
logger.info(f"{len(rebalance_weights)} new rebalancing weights calculated")
|
258
|
+
return rebalance_weights
|
259
|
+
|
260
|
+
@register_rebalance_class(register_in_agent=True)
|
261
|
+
class ImmediateSignal(RebalanceStrategyBase):
|
262
|
+
"""
|
263
|
+
This rebalance strategy 'immediately' rebalances the weights. This is equivalent to just using the signal weights.
|
264
|
+
"""
|
265
|
+
|
266
|
+
def __init__(self, *args, **kwargs):
|
267
|
+
"""
|
268
|
+
Initialize the immediate rebalance strategy.
|
269
|
+
"""
|
270
|
+
super().__init__(*args, **kwargs)
|
271
|
+
|
272
|
+
def get_explanation(self):
|
273
|
+
explanation=f"""<p> This rebalance strategy 'immediately' rebalances the weights. This is equivalent to just using the signal weights. </p>"""
|
274
|
+
return explanation
|
275
|
+
|
276
|
+
def apply_rebalance_logic(
|
277
|
+
self,
|
278
|
+
last_rebalance_weights: pd.DataFrame,
|
279
|
+
signal_weights: pd.DataFrame,
|
280
|
+
prices_df,
|
281
|
+
price_type: PriceTypeNames,
|
282
|
+
*args, **kwargs
|
283
|
+
) -> pd.DataFrame:
|
284
|
+
|
285
|
+
volume_df = prices_df.reset_index().pivot(index="time_index", columns=["unique_identifier"], values="volume")
|
286
|
+
prices_df = prices_df.reset_index().pivot(index="time_index", columns=["unique_identifier"], values=price_type.value)
|
287
|
+
|
288
|
+
if last_rebalance_weights is not None:
|
289
|
+
#because this function provides backtest weights we can concatenate last observation
|
290
|
+
volume_df = pd.concat([last_rebalance_weights.unstack()["volume_current"], volume_df], axis=0)
|
291
|
+
prices_df = pd.concat([last_rebalance_weights.unstack()["price_current"], prices_df], axis=0)
|
292
|
+
signal_weights = pd.concat([last_rebalance_weights.unstack()["weights_current"], signal_weights], axis=0)
|
293
|
+
rebalance_weights = pd.concat(
|
294
|
+
objs=[
|
295
|
+
signal_weights,
|
296
|
+
signal_weights.shift(1),
|
297
|
+
prices_df,
|
298
|
+
prices_df.shift(1),
|
299
|
+
volume_df,
|
300
|
+
volume_df.shift(1)
|
301
|
+
],
|
302
|
+
keys=["weights_current", "weights_before", "price_current", "price_before", "volume_current",
|
303
|
+
"volume_before"],
|
304
|
+
axis=1
|
305
|
+
)
|
306
|
+
|
307
|
+
if last_rebalance_weights is not None:
|
308
|
+
# align last rebalance weights
|
309
|
+
rebalance_weights = rebalance_weights[
|
310
|
+
rebalance_weights.index > last_rebalance_weights.index.get_level_values("time_index")[0]]
|
311
|
+
|
312
|
+
|
313
|
+
return rebalance_weights
|