openseries 1.9.6__py3-none-any.whl → 1.9.7__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.
openseries/frame.py CHANGED
@@ -16,10 +16,12 @@ from typing import TYPE_CHECKING, Any, cast
16
16
 
17
17
  from numpy import (
18
18
  array,
19
+ asarray,
19
20
  concatenate,
20
21
  cov,
21
22
  diff,
22
23
  divide,
24
+ float64,
23
25
  isinf,
24
26
  log,
25
27
  nan,
@@ -39,6 +41,7 @@ from pandas import (
39
41
  if TYPE_CHECKING: # pragma: no cover
40
42
  import datetime as dt
41
43
 
44
+ from numpy.typing import NDArray
42
45
  from pandas import Series as _Series
43
46
  from pandas import Timestamp
44
47
 
@@ -49,7 +52,7 @@ else:
49
52
  from pydantic import field_validator
50
53
  from sklearn.linear_model import LinearRegression # type: ignore[import-untyped]
51
54
 
52
- from ._common_model import _CommonModel
55
+ from ._common_model import _calculate_time_factor, _CommonModel, _get_base_column_data
53
56
  from .datefixer import _do_resample_to_business_period_ends
54
57
  from .owntypes import (
55
58
  DaysInYearType,
@@ -77,7 +80,6 @@ logger = getLogger(__name__)
77
80
  __all__ = ["OpenFrame"]
78
81
 
79
82
 
80
- # noinspection PyUnresolvedReferences,PyTypeChecker
81
83
  class OpenFrame(_CommonModel[SeriesFloat]):
82
84
  """OpenFrame objects hold OpenTimeSeries in the list constituents.
83
85
 
@@ -101,7 +103,6 @@ class OpenFrame(_CommonModel[SeriesFloat]):
101
103
  tsdf: DataFrame = DataFrame(dtype="float64")
102
104
  weights: list[float] | None = None
103
105
 
104
- # noinspection PyMethodParameters
105
106
  @field_validator("constituents")
106
107
  def _check_labels_unique(
107
108
  cls: type[OpenFrame], # noqa: N805
@@ -150,10 +151,12 @@ class OpenFrame(_CommonModel[SeriesFloat]):
150
151
  def _set_tsdf(self: Self) -> None:
151
152
  """Set the tsdf DataFrame."""
152
153
  if self.constituents is not None and len(self.constituents) != 0:
153
- self.tsdf = reduce(
154
- lambda left, right: concat([left, right], axis="columns", sort=True),
155
- [x.tsdf for x in self.constituents],
156
- )
154
+ if len(self.constituents) == 1:
155
+ self.tsdf = self.constituents[0].tsdf.copy()
156
+ else:
157
+ self.tsdf = concat(
158
+ [x.tsdf for x in self.constituents], axis="columns", sort=True
159
+ )
157
160
  else:
158
161
  logger.warning("OpenFrame() was passed an empty list.")
159
162
 
@@ -610,13 +613,12 @@ class OpenFrame(_CommonModel[SeriesFloat]):
610
613
  .std(ddof=dlta_degr_freedms)
611
614
  * sqrt(time_factor),
612
615
  ]
613
- raw_cov = [
614
- cov(
615
- m=data[(cols[0], ValueType.RTRN)].iloc[1:day_chunk].to_numpy(),
616
- y=data[(cols[1], ValueType.RTRN)].iloc[1:day_chunk].to_numpy(),
617
- ddof=dlta_degr_freedms,
618
- )[0][1],
619
- ]
616
+ rm = data[(cols[0], ValueType.RTRN)].iloc[1:day_chunk]
617
+ m: NDArray[float64] = asarray(rm, dtype=float64)
618
+ ry = data[(cols[1], ValueType.RTRN)].iloc[1:day_chunk]
619
+ y: NDArray[float64] = asarray(ry, dtype=float64)
620
+
621
+ raw_cov = [cov(m=m, y=y, ddof=dlta_degr_freedms)[0][1]]
620
622
 
621
623
  r1 = data[(cols[0], ValueType.RTRN)]
622
624
  r2 = data[(cols[1], ValueType.RTRN)]
@@ -855,36 +857,20 @@ class OpenFrame(_CommonModel[SeriesFloat]):
855
857
  from_dt=from_date,
856
858
  to_dt=to_date,
857
859
  )
858
- fraction: float = (later - earlier).days / 365.25
859
860
 
860
- msg = "base_column should be a tuple[str, ValueType] or an integer."
861
- if isinstance(base_column, tuple):
862
- shortdf = self.tsdf.loc[
863
- cast("Timestamp", earlier) : cast("Timestamp", later)
864
- ][base_column]
865
- short_item = base_column
866
- short_label = cast(
867
- "tuple[str, ValueType]",
868
- self.tsdf[base_column].name,
869
- )[0]
870
- elif isinstance(base_column, int):
871
- shortdf = self.tsdf.loc[
872
- cast("Timestamp", earlier) : cast("Timestamp", later)
873
- ].iloc[:, base_column]
874
- short_item = cast(
875
- "tuple[str, ValueType]",
876
- self.tsdf.iloc[:, base_column].name,
877
- )
878
- short_label = cast("tuple[str, str]", self.tsdf.iloc[:, base_column].name)[
879
- 0
880
- ]
881
- else:
882
- raise TypeError(msg)
861
+ shortdf, short_item, short_label = _get_base_column_data(
862
+ self=self,
863
+ base_column=base_column,
864
+ earlier=earlier,
865
+ later=later,
866
+ )
883
867
 
884
- if periods_in_a_year_fixed:
885
- time_factor = float(periods_in_a_year_fixed)
886
- else:
887
- time_factor = shortdf.count() / fraction
868
+ time_factor = _calculate_time_factor(
869
+ data=shortdf,
870
+ earlier=earlier,
871
+ later=later,
872
+ periods_in_a_year_fixed=periods_in_a_year_fixed,
873
+ )
888
874
 
889
875
  terrors = []
890
876
  for item in self.tsdf:
@@ -946,39 +932,20 @@ class OpenFrame(_CommonModel[SeriesFloat]):
946
932
  from_dt=from_date,
947
933
  to_dt=to_date,
948
934
  )
949
- fraction: float = (later - earlier).days / 365.25
950
935
 
951
- msg = "base_column should be a tuple[str, ValueType] or an integer."
952
- if isinstance(base_column, tuple):
953
- shortdf = self.tsdf.loc[
954
- cast("Timestamp", earlier) : cast("Timestamp", later)
955
- ][base_column]
956
- short_item = base_column
957
- short_label = cast(
958
- "tuple[str, str]",
959
- self.tsdf[base_column].name,
960
- )[0]
961
- elif isinstance(base_column, int):
962
- shortdf = self.tsdf.loc[
963
- cast("Timestamp", earlier) : cast("Timestamp", later)
964
- ].iloc[:, base_column]
965
- short_item = cast(
966
- "tuple[str, ValueType]",
967
- self.tsdf.iloc[
968
- :,
969
- base_column,
970
- ].name,
971
- )
972
- short_label = cast("tuple[str, str]", self.tsdf.iloc[:, base_column].name)[
973
- 0
974
- ]
975
- else:
976
- raise TypeError(msg)
936
+ shortdf, short_item, short_label = _get_base_column_data(
937
+ self=self,
938
+ base_column=base_column,
939
+ earlier=earlier,
940
+ later=later,
941
+ )
977
942
 
978
- if periods_in_a_year_fixed:
979
- time_factor = float(periods_in_a_year_fixed)
980
- else:
981
- time_factor = shortdf.count() / fraction
943
+ time_factor = _calculate_time_factor(
944
+ data=shortdf,
945
+ earlier=earlier,
946
+ later=later,
947
+ periods_in_a_year_fixed=periods_in_a_year_fixed,
948
+ )
982
949
 
983
950
  ratios = []
984
951
  for item in self.tsdf:
@@ -1451,7 +1418,7 @@ class OpenFrame(_CommonModel[SeriesFloat]):
1451
1418
  returns = self.tsdf.ffill().pct_change()
1452
1419
  returns.iloc[0] = 0
1453
1420
  elif all(vtypes):
1454
- returns = self.tsdf.copy()
1421
+ returns = self.tsdf
1455
1422
  else:
1456
1423
  msg = "Mix of series types will give inconsistent results"
1457
1424
  raise MixedValuetypesError(msg)
openseries/owntypes.py CHANGED
@@ -35,7 +35,7 @@ except ImportError: # pragma: no cover
35
35
  __all__ = ["Self", "ValueType"]
36
36
 
37
37
 
38
- Combo_co = TypeVar("Combo_co", float, SeriesFloat, covariant=True)
38
+ SeriesOrFloat_co = TypeVar("SeriesOrFloat_co", float, SeriesFloat, covariant=True)
39
39
 
40
40
 
41
41
  CountryStringType = Annotated[
@@ -16,13 +16,13 @@ from typing import TYPE_CHECKING, cast
16
16
  from numpy import (
17
17
  append,
18
18
  array,
19
+ einsum,
19
20
  float64,
20
21
  inf,
21
22
  isnan,
22
23
  linspace,
23
24
  nan,
24
25
  sqrt,
25
- zeros,
26
26
  )
27
27
  from numpy import (
28
28
  sum as npsum,
@@ -48,12 +48,9 @@ from .owntypes import (
48
48
  ValueType,
49
49
  )
50
50
  from .series import OpenTimeSeries
51
-
52
- # noinspection PyProtectedMember
53
51
  from .simulation import _random_generator
54
52
 
55
53
  if TYPE_CHECKING: # pragma: no cover
56
- # noinspection PyUnresolvedReferences
57
54
  from collections.abc import Callable
58
55
 
59
56
  from numpy.typing import NDArray
@@ -106,25 +103,16 @@ def simulate_portfolios(
106
103
 
107
104
  log_ret.columns = log_ret.columns.droplevel(level=1)
108
105
 
109
- randomizer = _random_generator(seed=seed)
110
-
111
- all_weights = zeros((num_ports, simframe.item_count))
112
- ret_arr = zeros(num_ports)
113
- vol_arr = zeros(num_ports)
114
- sharpe_arr = zeros(num_ports)
115
-
116
- for x in range(num_ports):
117
- weights = array(randomizer.random(simframe.item_count))
118
- weights = weights / npsum(weights)
119
- all_weights[x, :] = weights
106
+ cov_matrix = log_ret.cov() * simframe.periods_in_a_year
107
+ mean_returns = log_ret.mean() * simframe.periods_in_a_year
120
108
 
121
- vol_arr[x] = sqrt(
122
- weights.T @ (log_ret.cov() * simframe.periods_in_a_year @ weights),
123
- )
124
-
125
- ret_arr[x] = npsum(log_ret.mean() * weights * simframe.periods_in_a_year)
109
+ randomizer = _random_generator(seed=seed)
110
+ all_weights = randomizer.random((num_ports, simframe.item_count))
111
+ all_weights = all_weights / all_weights.sum(axis=1, keepdims=True)
126
112
 
127
- sharpe_arr[x] = ret_arr[x] / vol_arr[x]
113
+ ret_arr = all_weights @ mean_returns
114
+ vol_arr = sqrt(einsum("ij,jk,ik->i", all_weights, cov_matrix, all_weights))
115
+ sharpe_arr = ret_arr / vol_arr
128
116
 
129
117
  simdf = concat(
130
118
  [
@@ -137,7 +125,6 @@ def simulate_portfolios(
137
125
  return simdf.dropna()
138
126
 
139
127
 
140
- # noinspection PyUnusedLocal
141
128
  def efficient_frontier(
142
129
  eframe: OpenFrame,
143
130
  num_ports: int = 5000,
@@ -230,7 +217,7 @@ def efficient_frontier(
230
217
  _get_ret_vol_sr(
231
218
  lg_ret=log_ret,
232
219
  weights=weights,
233
- per_in_yr=eframe.periods_in_a_year,
220
+ per_in_yr=copi.periods_in_a_year,
234
221
  )[2]
235
222
  * -1,
236
223
  )
@@ -243,7 +230,7 @@ def efficient_frontier(
243
230
  _get_ret_vol_sr(
244
231
  lg_ret=log_ret,
245
232
  weights=weights,
246
- per_in_yr=eframe.periods_in_a_year,
233
+ per_in_yr=copi.periods_in_a_year,
247
234
  )[1],
248
235
  )
249
236
 
@@ -263,7 +250,7 @@ def efficient_frontier(
263
250
  optimal = _get_ret_vol_sr(
264
251
  lg_ret=log_ret,
265
252
  weights=opt_results.x,
266
- per_in_yr=eframe.periods_in_a_year,
253
+ per_in_yr=copi.periods_in_a_year,
267
254
  )
268
255
 
269
256
  frontier_y = linspace(start=frontier_min, stop=frontier_max, num=frontier_points)
@@ -280,7 +267,7 @@ def efficient_frontier(
280
267
  "fun": lambda w, poss_return=possible_return: _diff_return(
281
268
  lg_ret=log_ret,
282
269
  weights=w,
283
- per_in_yr=eframe.periods_in_a_year,
270
+ per_in_yr=copi.periods_in_a_year,
284
271
  poss_return=poss_return,
285
272
  ),
286
273
  },
@@ -375,7 +362,6 @@ def constrain_optimized_portfolios(
375
362
  )
376
363
 
377
364
  condition_least_ret = front_frame.ret > serie.arithmetic_ret
378
- # noinspection PyArgumentList
379
365
  least_ret_frame = front_frame[condition_least_ret].sort_values(by="stdev")
380
366
  least_ret_port: Series[float] = least_ret_frame.iloc[0]
381
367
  least_ret_port_name = f"Minimize vol & target return of {portfolioname}"
@@ -386,7 +372,6 @@ def constrain_optimized_portfolios(
386
372
  resleast = OpenTimeSeries.from_df(lr_frame.make_portfolio(least_ret_port_name))
387
373
 
388
374
  condition_most_vol = front_frame.stdev < serie.vol
389
- # noinspection PyArgumentList
390
375
  most_vol_frame = front_frame[condition_most_vol].sort_values(
391
376
  by="ret",
392
377
  ascending=False,
openseries/py.typed ADDED
File without changes
openseries/report.py CHANGED
@@ -264,7 +264,6 @@ def report_html(
264
264
  "{:.2f}",
265
265
  ]
266
266
 
267
- # noinspection PyTypeChecker
268
267
  rpt_df = copied.all_properties(
269
268
  properties=cast("list[LiteralFrameProps]", properties),
270
269
  )
@@ -353,7 +352,9 @@ def report_html(
353
352
 
354
353
  for item, f in zip(rpt_df.index, formats, strict=False):
355
354
  rpt_df.loc[item] = rpt_df.loc[item].apply(
356
- lambda x, fmt=f: x if (isinstance(x, str) or x is None) else fmt.format(x),
355
+ lambda x, fmt=f: str(x)
356
+ if (isinstance(x, str) or x is None)
357
+ else fmt.format(x),
357
358
  )
358
359
 
359
360
  rpt_df.index = Index(labels_init)
openseries/series.py CHANGED
@@ -15,7 +15,6 @@ from typing import TYPE_CHECKING, Any, TypeVar, cast
15
15
 
16
16
  if TYPE_CHECKING: # pragma: no cover
17
17
  import datetime as dt
18
- from collections.abc import Callable
19
18
 
20
19
  from numpy.typing import NDArray
21
20
  from pandas import Timestamp
@@ -41,7 +40,7 @@ from pandas import (
41
40
  )
42
41
  from pydantic import field_validator, model_validator
43
42
 
44
- from ._common_model import _CommonModel
43
+ from ._common_model import _calculate_time_factor, _CommonModel
45
44
  from .datefixer import _do_resample_to_business_period_ends, date_fix
46
45
  from .owntypes import (
47
46
  Countries,
@@ -63,9 +62,6 @@ from .owntypes import (
63
62
  ValueType,
64
63
  )
65
64
 
66
- FieldValidator = cast("Callable[..., Callable[..., Any]]", field_validator)
67
- ModelValidator = cast("Callable[..., Callable[..., Any]]", model_validator)
68
-
69
65
  logger = getLogger(__name__)
70
66
 
71
67
  __all__ = ["OpenTimeSeries", "timeseries_chain"]
@@ -73,7 +69,6 @@ __all__ = ["OpenTimeSeries", "timeseries_chain"]
73
69
  TypeOpenTimeSeries = TypeVar("TypeOpenTimeSeries", bound="OpenTimeSeries")
74
70
 
75
71
 
76
- # noinspection PyUnresolvedReferences,PyNestedDecorators
77
72
  class OpenTimeSeries(_CommonModel[float]):
78
73
  """OpenTimeSeries objects are at the core of the openseries package.
79
74
 
@@ -130,21 +125,21 @@ class OpenTimeSeries(_CommonModel[float]):
130
125
  isin: str | None = None
131
126
  label: str | None = None
132
127
 
133
- @FieldValidator("domestic", mode="before")
128
+ @field_validator("domestic", mode="before")
134
129
  @classmethod
135
130
  def _validate_domestic(cls, value: CurrencyStringType) -> CurrencyStringType:
136
131
  """Pydantic validator to ensure domestic field is validated."""
137
132
  _ = Currency(ccy=value)
138
133
  return value
139
134
 
140
- @FieldValidator("countries", mode="before")
135
+ @field_validator("countries", mode="before")
141
136
  @classmethod
142
137
  def _validate_countries(cls, value: CountriesType) -> CountriesType:
143
138
  """Pydantic validator to ensure countries field is validated."""
144
139
  _ = Countries(countryinput=value)
145
140
  return value
146
141
 
147
- @FieldValidator("markets", mode="before")
142
+ @field_validator("markets", mode="before")
148
143
  @classmethod
149
144
  def _validate_markets(
150
145
  cls,
@@ -164,7 +159,7 @@ class OpenTimeSeries(_CommonModel[float]):
164
159
  raise MarketsNotStringNorListStrError(item_msg)
165
160
  raise MarketsNotStringNorListStrError(msg)
166
161
 
167
- @ModelValidator(mode="after")
162
+ @model_validator(mode="after")
168
163
  def _dates_and_values_validate(self: Self) -> Self:
169
164
  """Pydantic validator to ensure dates and values are validated."""
170
165
  values_list_length = len(self.values)
@@ -282,7 +277,7 @@ class OpenTimeSeries(_CommonModel[float]):
282
277
  label, _ = dframe.name
283
278
  else:
284
279
  label = dframe.name
285
- values = cast("list[float]", dframe.to_numpy().tolist())
280
+ values = dframe.to_numpy().tolist()
286
281
  elif isinstance(dframe, DataFrame):
287
282
  values = dframe.iloc[:, column_nmbr].to_list()
288
283
  if isinstance(dframe.columns, MultiIndex):
@@ -301,12 +296,11 @@ class OpenTimeSeries(_CommonModel[float]):
301
296
  msg = f"valuetype missing. Adding: {valuetype.value}"
302
297
  logger.warning(msg=msg)
303
298
  else:
304
- valuetype = cast(
305
- "ValueType",
306
- dframe.columns.get_level_values(1).to_numpy()[column_nmbr],
307
- )
299
+ valuetype = dframe.columns.get_level_values(1).to_numpy()[
300
+ column_nmbr
301
+ ]
308
302
  else:
309
- label = cast("MultiIndex", dframe.columns).to_numpy()[column_nmbr]
303
+ label = dframe.columns.to_numpy()[column_nmbr]
310
304
  else:
311
305
  raise TypeError(msg)
312
306
 
@@ -472,7 +466,6 @@ class OpenTimeSeries(_CommonModel[float]):
472
466
  The returns of the values in the series
473
467
 
474
468
  """
475
- # noinspection PyCallingNonCallable
476
469
  returns = self.tsdf.ffill().pct_change()
477
470
  returns.iloc[0] = 0
478
471
  self.valuetype = ValueType.RTRN
@@ -684,16 +677,14 @@ class OpenTimeSeries(_CommonModel[float]):
684
677
  from_dt=from_date,
685
678
  to_dt=to_date,
686
679
  )
687
- if periods_in_a_year_fixed:
688
- time_factor = float(periods_in_a_year_fixed)
689
- else:
690
- how_many = (
691
- self.tsdf.loc[cast("Timestamp", earlier) : cast("Timestamp", later)]
692
- .count()
693
- .iloc[0]
694
- )
695
- fraction = (later - earlier).days / 365.25
696
- time_factor = how_many / fraction
680
+ time_factor = _calculate_time_factor(
681
+ data=self.tsdf.loc[
682
+ cast("Timestamp", earlier) : cast("Timestamp", later)
683
+ ].iloc[:, 0],
684
+ earlier=earlier,
685
+ later=later,
686
+ periods_in_a_year_fixed=periods_in_a_year_fixed,
687
+ )
697
688
 
698
689
  data = self.tsdf.loc[
699
690
  cast("Timestamp", earlier) : cast("Timestamp", later)
@@ -757,7 +748,6 @@ class OpenTimeSeries(_CommonModel[float]):
757
748
  ra_df = ra_df.dropna()
758
749
 
759
750
  prev = self.first_idx
760
- # noinspection PyTypeChecker
761
751
  dates: list[dt.date] = [prev]
762
752
 
763
753
  for idx, row in ra_df.iterrows():
@@ -878,7 +868,6 @@ def timeseries_chain(
878
868
 
879
869
  dates.extend([x.strftime("%Y-%m-%d") for x in new.tsdf.index])
880
870
 
881
- # noinspection PyTypeChecker
882
871
  return back.__class__(
883
872
  timeseries_id=new.timeseries_id,
884
873
  instrument_id=new.instrument_id,
openseries/simulation.py CHANGED
@@ -9,7 +9,13 @@ SPDX-License-Identifier: BSD-3-Clause
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- from typing import TYPE_CHECKING, cast
12
+ from functools import cached_property
13
+ from typing import TYPE_CHECKING, TypedDict, cast
14
+
15
+ try:
16
+ from typing import Unpack
17
+ except ImportError:
18
+ from typing_extensions import Unpack
13
19
 
14
20
  if TYPE_CHECKING:
15
21
  import datetime as dt # pragma: no cover
@@ -41,6 +47,14 @@ from .owntypes import (
41
47
  __all__ = ["ReturnSimulation"]
42
48
 
43
49
 
50
+ class _JumpParams(TypedDict, total=False):
51
+ """TypedDict for jump diffusion parameters."""
52
+
53
+ jumps_lamda: NonNegativeFloat
54
+ jumps_sigma: NonNegativeFloat
55
+ jumps_mu: float
56
+
57
+
44
58
  def _random_generator(seed: int | None) -> Generator:
45
59
  """Make a Numpy Random Generator object.
46
60
 
@@ -60,6 +74,58 @@ def _random_generator(seed: int | None) -> Generator:
60
74
  return Generator(bit_generator=bg)
61
75
 
62
76
 
77
+ def _create_base_simulation(
78
+ cls: type[ReturnSimulation],
79
+ returns: DataFrame,
80
+ number_of_sims: PositiveInt,
81
+ trading_days: PositiveInt,
82
+ trading_days_in_year: DaysInYearType,
83
+ mean_annual_return: float,
84
+ mean_annual_vol: PositiveFloat,
85
+ seed: int | None = None,
86
+ **kwargs: Unpack[_JumpParams],
87
+ ) -> ReturnSimulation:
88
+ """Common logic for creating simulations.
89
+
90
+ Parameters
91
+ ----------
92
+ cls: type[ReturnSimulation]
93
+ The ReturnSimulation class
94
+ returns: pandas.DataFrame
95
+ The calculated returns data
96
+ number_of_sims: PositiveInt
97
+ Number of simulations to generate
98
+ trading_days: PositiveInt
99
+ Number of trading days to simulate
100
+ trading_days_in_year: DaysInYearType
101
+ Number of trading days used to annualize
102
+ mean_annual_return: float
103
+ Mean annual return
104
+ mean_annual_vol: PositiveFloat
105
+ Mean annual volatility
106
+ seed: int, optional
107
+ Seed for random process initiation
108
+ **kwargs
109
+ Additional keyword arguments for jump parameters
110
+
111
+ Returns:
112
+ -------
113
+ ReturnSimulation
114
+ A ReturnSimulation instance
115
+
116
+ """
117
+ return cls(
118
+ number_of_sims=number_of_sims,
119
+ trading_days=trading_days,
120
+ trading_days_in_year=trading_days_in_year,
121
+ mean_annual_return=mean_annual_return,
122
+ mean_annual_vol=mean_annual_vol,
123
+ dframe=returns,
124
+ seed=seed,
125
+ **kwargs,
126
+ )
127
+
128
+
63
129
  class ReturnSimulation(BaseModel):
64
130
  """The class ReturnSimulation allows for simulating financial timeseries.
65
131
 
@@ -105,7 +171,7 @@ class ReturnSimulation(BaseModel):
105
171
  revalidate_instances="always",
106
172
  )
107
173
 
108
- @property
174
+ @cached_property
109
175
  def results(self: Self) -> DataFrame:
110
176
  """Simulation data.
111
177
 
@@ -197,13 +263,14 @@ class ReturnSimulation(BaseModel):
197
263
  size=(number_of_sims, trading_days),
198
264
  )
199
265
 
200
- return cls(
266
+ return _create_base_simulation(
267
+ cls=cls,
268
+ returns=DataFrame(data=returns, dtype="float64"),
201
269
  number_of_sims=number_of_sims,
202
270
  trading_days=trading_days,
203
271
  trading_days_in_year=trading_days_in_year,
204
272
  mean_annual_return=mean_annual_return,
205
273
  mean_annual_vol=mean_annual_vol,
206
- dframe=DataFrame(data=returns, dtype="float64"),
207
274
  seed=seed,
208
275
  )
209
276
 
@@ -255,13 +322,14 @@ class ReturnSimulation(BaseModel):
255
322
  - 1
256
323
  )
257
324
 
258
- return cls(
325
+ return _create_base_simulation(
326
+ cls=cls,
327
+ returns=DataFrame(data=returns, dtype="float64"),
259
328
  number_of_sims=number_of_sims,
260
329
  trading_days=trading_days,
261
330
  trading_days_in_year=trading_days_in_year,
262
331
  mean_annual_return=mean_annual_return,
263
332
  mean_annual_vol=mean_annual_vol,
264
- dframe=DataFrame(data=returns, dtype="float64"),
265
333
  seed=seed,
266
334
  )
267
335
 
@@ -317,13 +385,14 @@ class ReturnSimulation(BaseModel):
317
385
 
318
386
  returns = drift + wiener
319
387
 
320
- return cls(
388
+ return _create_base_simulation(
389
+ cls=cls,
390
+ returns=DataFrame(data=returns, dtype="float64"),
321
391
  number_of_sims=number_of_sims,
322
392
  trading_days=trading_days,
323
393
  trading_days_in_year=trading_days_in_year,
324
394
  mean_annual_return=mean_annual_return,
325
395
  mean_annual_vol=mean_annual_vol,
326
- dframe=DataFrame(data=returns, dtype="float64"),
327
396
  seed=seed,
328
397
  )
329
398
 
@@ -404,17 +473,18 @@ class ReturnSimulation(BaseModel):
404
473
 
405
474
  returns[:, 0] = 0.0
406
475
 
407
- return cls(
476
+ return _create_base_simulation(
477
+ cls=cls,
478
+ returns=DataFrame(data=returns, dtype="float64"),
408
479
  number_of_sims=number_of_sims,
409
480
  trading_days=trading_days,
410
481
  trading_days_in_year=trading_days_in_year,
411
482
  mean_annual_return=mean_annual_return,
412
483
  mean_annual_vol=mean_annual_vol,
484
+ seed=seed,
413
485
  jumps_lamda=jumps_lamda,
414
486
  jumps_sigma=jumps_sigma,
415
487
  jumps_mu=jumps_mu,
416
- dframe=DataFrame(data=returns, dtype="float64"),
417
- seed=seed,
418
488
  )
419
489
 
420
490
  def to_dataframe(
@@ -465,15 +535,17 @@ class ReturnSimulation(BaseModel):
465
535
  )
466
536
  return sdf
467
537
 
468
- fdf = DataFrame()
469
- for item in range(self.number_of_sims):
470
- sdf = self.dframe.iloc[item].T.to_frame()
471
- sdf.index = Index(d_range)
472
- sdf.columns = MultiIndex.from_arrays(
473
- [
474
- [f"{name}_{item}"],
475
- [ValueType.RTRN],
476
- ],
538
+ df_list = [
539
+ DataFrame(
540
+ data=self.dframe.iloc[item].values,
541
+ index=Index(d_range),
542
+ columns=MultiIndex.from_arrays(
543
+ [
544
+ [f"{name}_{item}"],
545
+ [ValueType.RTRN],
546
+ ],
547
+ ),
477
548
  )
478
- fdf = concat([fdf, sdf], axis="columns", sort=True)
479
- return fdf
549
+ for item in range(self.number_of_sims)
550
+ ]
551
+ return concat(df_list, axis="columns", sort=True)