openseries 1.9.4__py3-none-any.whl → 1.9.6__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.
@@ -7,7 +7,6 @@ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
7
  SPDX-License-Identifier: BSD-3-Clause
8
8
  """
9
9
 
10
- # mypy: disable-error-code="assignment"
11
10
  from __future__ import annotations
12
11
 
13
12
  from inspect import stack
@@ -36,7 +35,7 @@ from pandas import (
36
35
  from plotly.graph_objs import Figure # type: ignore[import-untyped]
37
36
  from plotly.io import to_html # type: ignore[import-untyped]
38
37
  from plotly.offline import plot # type: ignore[import-untyped]
39
- from scipy.optimize import minimize # type: ignore[import-untyped]
38
+ from scipy.optimize import minimize
40
39
 
41
40
  from .load_plotly import load_plotly_dict
42
41
  from .owntypes import (
@@ -143,7 +142,7 @@ def efficient_frontier(
143
142
  eframe: OpenFrame,
144
143
  num_ports: int = 5000,
145
144
  seed: int = 71,
146
- bounds: tuple[tuple[float]] | None = None,
145
+ bounds: tuple[tuple[float, float], ...] | None = None,
147
146
  frontier_points: int = 200,
148
147
  minimize_method: LiteralMinimizeMethods = "SLSQP",
149
148
  *,
@@ -159,7 +158,7 @@ def efficient_frontier(
159
158
  Number of possible portfolios to simulate
160
159
  seed: int, default: 71
161
160
  The seed for the random process
162
- bounds: tuple[tuple[float]], optional
161
+ bounds: tuple[tuple[float, float], ...], optional
163
162
  The range of minumum and maximum allowed allocations for each asset
164
163
  frontier_points: int, default: 200
165
164
  number of points along frontier to optimize
@@ -253,7 +252,7 @@ def efficient_frontier(
253
252
  bounds = tuple((0.0, 1.0) for _ in range(eframe.item_count))
254
253
  init_guess = array(eframe.weights)
255
254
 
256
- opt_results = minimize(
255
+ opt_results = minimize( # type: ignore[call-overload]
257
256
  fun=_neg_sharpe,
258
257
  x0=init_guess,
259
258
  method=minimize_method,
@@ -288,7 +287,7 @@ def efficient_frontier(
288
287
  ),
289
288
  )
290
289
 
291
- result = minimize(
290
+ result = minimize( # type: ignore[call-overload]
292
291
  fun=_minimize_volatility,
293
292
  x0=init_guess,
294
293
  method=minimize_method,
@@ -333,7 +332,7 @@ def constrain_optimized_portfolios(
333
332
  portfolioname: str = "Current Portfolio",
334
333
  simulations: int = 10000,
335
334
  curve_points: int = 200,
336
- bounds: tuple[tuple[float]] | None = None,
335
+ bounds: tuple[tuple[float, float], ...] | None = None,
337
336
  minimize_method: LiteralMinimizeMethods = "SLSQP",
338
337
  ) -> tuple[OpenFrame, OpenTimeSeries, OpenFrame, OpenTimeSeries]:
339
338
  """Constrain optimized portfolios to those that improve on the current one.
@@ -350,7 +349,7 @@ def constrain_optimized_portfolios(
350
349
  Number of possible portfolios to simulate
351
350
  curve_points: int, default: 200
352
351
  Number of optimal portfolios on the efficient frontier
353
- bounds: tuple[tuple[float]], optional
352
+ bounds: tuple[tuple[float, float], ...], optional
354
353
  The range of minumum and maximum allowed allocations for each asset
355
354
  minimize_method: LiteralMinimizeMethods, default: SLSQP
356
355
  The method passed into the scipy.minimize function
@@ -367,7 +366,7 @@ def constrain_optimized_portfolios(
367
366
  if not bounds:
368
367
  bounds = tuple((0.0, 1.0) for _ in range(data.item_count))
369
368
 
370
- front_frame, sim_frame, optimal = efficient_frontier(
369
+ front_frame, _, _ = efficient_frontier(
371
370
  eframe=data,
372
371
  num_ports=simulations,
373
372
  frontier_points=curve_points,
@@ -441,14 +440,13 @@ def prepare_plot_data(
441
440
  for wgt, nm in zip(optimized[3:], assets.columns_lvl_zero, strict=True)
442
441
  ]
443
442
  opt_text = "<br><br>Weights:<br>" + "<br>".join(opt_text_list)
444
- vol: Series[float] = assets.vol
445
443
  plotframe = DataFrame(
446
444
  data=[
447
445
  assets.arithmetic_ret,
448
- vol,
446
+ assets.vol,
449
447
  Series(
450
448
  data=[""] * assets.item_count,
451
- index=vol.index,
449
+ index=assets.vol.index,
452
450
  ),
453
451
  ],
454
452
  index=["ret", "stdev", "text"],
openseries/report.py CHANGED
@@ -7,7 +7,6 @@ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
7
  SPDX-License-Identifier: BSD-3-Clause
8
8
  """
9
9
 
10
- # mypy: disable-error-code="assignment"
11
10
  from __future__ import annotations
12
11
 
13
12
  from inspect import stack
@@ -20,20 +19,21 @@ from warnings import catch_warnings, simplefilter
20
19
 
21
20
  if TYPE_CHECKING: # pragma: no cover
22
21
  from pandas import Series
23
- from plotly.graph_objs import Figure
22
+ from plotly.graph_objs import Figure # type: ignore[import-untyped]
24
23
 
25
24
  from .frame import OpenFrame
26
25
  from .owntypes import LiteralPlotlyJSlib, LiteralPlotlyOutput
27
26
 
28
27
 
29
- from pandas import DataFrame, Series, Timestamp, concat
30
- from plotly.io import to_html
31
- from plotly.offline import plot
32
- from plotly.subplots import make_subplots
28
+ from pandas import DataFrame, Index, Series, Timestamp, concat
29
+ from plotly.io import to_html # type: ignore[import-untyped]
30
+ from plotly.offline import plot # type: ignore[import-untyped]
31
+ from plotly.subplots import make_subplots # type: ignore[import-untyped]
33
32
 
34
33
  from .load_plotly import load_plotly_dict
35
34
  from .owntypes import (
36
35
  LiteralBizDayFreq,
36
+ LiteralFrameProps,
37
37
  ValueType,
38
38
  )
39
39
 
@@ -72,15 +72,15 @@ def calendar_period_returns(
72
72
  cldr = copied.tsdf.iloc[1:].copy()
73
73
  if relabel:
74
74
  if freq.upper() == "BYE":
75
- cldr.index = [d.year for d in cldr.index]
75
+ cldr.index = Index([d.year for d in cldr.index])
76
76
  elif freq.upper() == "BQE":
77
- cldr.index = [
78
- Timestamp(d).to_period("Q").strftime("Q%q %Y") for d in cldr.index
79
- ]
77
+ cldr.index = Index(
78
+ [Timestamp(d).to_period("Q").strftime("Q%q %Y") for d in cldr.index],
79
+ )
80
80
  else:
81
- cldr.index = [d.strftime("%b %y") for d in cldr.index]
81
+ cldr.index = Index([d.strftime("%b %y") for d in cldr.index])
82
82
 
83
- return cldr # type: ignore[no-any-return]
83
+ return cldr
84
84
 
85
85
 
86
86
  def report_html(
@@ -127,9 +127,10 @@ def report_html(
127
127
  Plotly Figure and a div section or a html filename with location
128
128
 
129
129
  """
130
- data.trunc_frame().value_nan_handle().to_cumret()
130
+ copied = data.from_deepcopy()
131
+ copied.trunc_frame().value_nan_handle().to_cumret()
131
132
 
132
- if data.yearfrac > 1.0:
133
+ if copied.yearfrac > 1.0:
133
134
  properties = [
134
135
  "geo_ret",
135
136
  "vol",
@@ -217,10 +218,10 @@ def report_html(
217
218
  ],
218
219
  )
219
220
 
220
- for item, lbl in enumerate(data.columns_lvl_zero):
221
+ for item, lbl in enumerate(copied.columns_lvl_zero):
221
222
  figure.add_scatter(
222
- x=data.tsdf.index,
223
- y=data.tsdf.iloc[:, item],
223
+ x=copied.tsdf.index,
224
+ y=copied.tsdf.iloc[:, item],
224
225
  hovertemplate="%{y:.2%}<br>%{x|%Y-%m-%d}",
225
226
  line={"width": 2.5, "dash": "solid"},
226
227
  mode="lines",
@@ -231,18 +232,19 @@ def report_html(
231
232
  )
232
233
 
233
234
  quarter_of_year = 0.25
234
- if data.yearfrac < quarter_of_year:
235
- tmp = data.from_deepcopy()
235
+ if copied.yearfrac < quarter_of_year:
236
+ tmp = copied.from_deepcopy()
236
237
  bdf = tmp.value_to_ret().tsdf.iloc[1:]
237
238
  else:
238
- bdf = calendar_period_returns(data, freq=bar_freq)
239
+ bdf = calendar_period_returns(data=copied, freq=bar_freq)
239
240
 
240
- for item in range(data.item_count):
241
+ for item in range(copied.item_count):
242
+ col_name = cast("tuple[str, ValueType]", bdf.iloc[:, item].name)
241
243
  figure.add_bar(
242
244
  x=bdf.index,
243
245
  y=bdf.iloc[:, item],
244
246
  hovertemplate="%{y:.2%}<br>%{x}",
245
- name=bdf.iloc[:, item].name[0], # type: ignore[index]
247
+ name=col_name[0],
246
248
  showlegend=False,
247
249
  row=2,
248
250
  col=1,
@@ -263,8 +265,10 @@ def report_html(
263
265
  ]
264
266
 
265
267
  # noinspection PyTypeChecker
266
- rpt_df = data.all_properties(properties=properties) # type: ignore[arg-type]
267
- alpha_frame = data.from_deepcopy()
268
+ rpt_df = copied.all_properties(
269
+ properties=cast("list[LiteralFrameProps]", properties),
270
+ )
271
+ alpha_frame = copied.from_deepcopy()
268
272
  alpha_frame.to_cumret()
269
273
  with catch_warnings():
270
274
  simplefilter("ignore")
@@ -277,18 +281,22 @@ def report_html(
277
281
  for aname in alpha_frame.columns_lvl_zero[:-1]
278
282
  ]
279
283
  alphas.append("")
280
- ar = DataFrame(data=alphas, index=data.tsdf.columns, columns=["Jensen's Alpha"]).T
284
+ ar = DataFrame(
285
+ data=alphas,
286
+ index=copied.tsdf.columns,
287
+ columns=["Jensen's Alpha"],
288
+ ).T
281
289
  rpt_df = concat([rpt_df, ar])
282
- ir = data.info_ratio_func()
290
+ ir = copied.info_ratio_func()
283
291
  ir.name = "Information Ratio"
284
292
  ir.iloc[-1] = None
285
- ir = ir.to_frame().T
286
- rpt_df = concat([rpt_df, ir])
287
- te_frame = data.from_deepcopy()
293
+ ir_df = ir.to_frame().T
294
+ rpt_df = concat([rpt_df, ir_df])
295
+ te_frame = copied.from_deepcopy()
288
296
  te_frame.resample("7D")
289
297
  with catch_warnings():
290
298
  simplefilter("ignore")
291
- te = te_frame.tracking_error_func()
299
+ te: Series[float] | Series[str] = te_frame.tracking_error_func()
292
300
  if te.hasnans:
293
301
  te = Series(
294
302
  data=[""] * te_frame.item_count,
@@ -298,11 +306,11 @@ def report_html(
298
306
  else:
299
307
  te.iloc[-1] = None
300
308
  te.name = "Tracking Error (weekly)"
301
- te = te.to_frame().T
302
- rpt_df = concat([rpt_df, te])
309
+ te_df = te.to_frame().T
310
+ rpt_df = concat([rpt_df, te_df])
303
311
 
304
- if data.yearfrac > 1.0:
305
- crm = data.from_deepcopy()
312
+ if copied.yearfrac > 1.0:
313
+ crm = copied.from_deepcopy()
306
314
  crm.resample("ME")
307
315
  cru_save = Series(
308
316
  data=[""] * crm.item_count,
@@ -312,7 +320,7 @@ def report_html(
312
320
  with catch_warnings():
313
321
  simplefilter("ignore")
314
322
  try:
315
- cru = crm.capture_ratio_func(ratio="both")
323
+ cru: Series[float] | Series[str] = crm.capture_ratio_func(ratio="both")
316
324
  except ZeroDivisionError as exc: # pragma: no cover
317
325
  msg = f"Capture ratio calculation error: {exc!s}" # pragma: no cover
318
326
  logger.warning(msg=msg) # pragma: no cover
@@ -322,10 +330,10 @@ def report_html(
322
330
  else:
323
331
  cru.iloc[-1] = None
324
332
  cru.name = "Capture Ratio (monthly)"
325
- cru = cru.to_frame().T
326
- rpt_df = concat([rpt_df, cru])
333
+ cru_df = cru.to_frame().T
334
+ rpt_df = concat([rpt_df, cru_df])
327
335
  formats.append("{:.2f}")
328
- beta_frame = data.from_deepcopy()
336
+ beta_frame = copied.from_deepcopy()
329
337
  beta_frame.resample("7D").value_nan_handle("drop")
330
338
  beta_frame.to_cumret()
331
339
  betas: list[str | float] = [
@@ -335,51 +343,45 @@ def report_html(
335
343
  )
336
344
  for bname in beta_frame.columns_lvl_zero[:-1]
337
345
  ]
338
- # noinspection PyTypeChecker
339
346
  betas.append("")
340
347
  br = DataFrame(
341
348
  data=betas,
342
- index=data.tsdf.columns,
349
+ index=copied.tsdf.columns,
343
350
  columns=["Index Beta (weekly)"],
344
351
  ).T
345
352
  rpt_df = concat([rpt_df, br])
346
353
 
347
354
  for item, f in zip(rpt_df.index, formats, strict=False):
348
355
  rpt_df.loc[item] = rpt_df.loc[item].apply(
349
- lambda x, fmt=f: x if (isinstance(x, str) or x is None) else fmt.format(x), # type: ignore[return-value]
356
+ lambda x, fmt=f: x if (isinstance(x, str) or x is None) else fmt.format(x),
350
357
  )
351
358
 
352
- rpt_df.index = labels_init
359
+ rpt_df.index = Index(labels_init)
353
360
 
354
- this_year = data.last_idx.year
355
- this_month = data.last_idx.month
356
- ytd = cast("Series[float]", data.value_ret_calendar_period(year=this_year)).map(
357
- "{:.2%}".format
358
- )
361
+ this_year = copied.last_idx.year
362
+ this_month = copied.last_idx.month
363
+ ytd = copied.value_ret_calendar_period(year=this_year).map("{:.2%}".format)
359
364
  ytd.name = "Year-to-Date"
360
- mtd = cast(
361
- "Series[float]",
362
- data.value_ret_calendar_period(year=this_year, month=this_month),
363
- ).map(
365
+ mtd = copied.value_ret_calendar_period(year=this_year, month=this_month).map(
364
366
  "{:.2%}".format,
365
367
  )
366
368
  mtd.name = "Month-to-Date"
367
- ytd = ytd.to_frame().T
368
- mtd = mtd.to_frame().T
369
- rpt_df = concat([rpt_df, ytd])
370
- rpt_df = concat([rpt_df, mtd])
369
+ ytd_df = ytd.to_frame().T
370
+ mtd_df = mtd.to_frame().T
371
+ rpt_df = concat([rpt_df, ytd_df])
372
+ rpt_df = concat([rpt_df, mtd_df])
371
373
  rpt_df = rpt_df.reindex(labels_final)
372
374
 
373
- rpt_df.index = [f"<b>{x}</b>" for x in rpt_df.index]
375
+ rpt_df.index = Index([f"<b>{x}</b>" for x in rpt_df.index])
374
376
  rpt_df = rpt_df.reset_index()
375
377
 
376
- colmns = ["", *data.columns_lvl_zero]
378
+ colmns = ["", *copied.columns_lvl_zero]
377
379
  columns = [f"<b>{x}</b>" for x in colmns]
378
380
  aligning = ["left"] + ["center"] * (len(columns) - 1)
379
381
 
380
382
  col_even_color = "lightgrey"
381
383
  col_odd_color = "white"
382
- color_lst = ["grey"] + [col_odd_color] * (data.item_count - 1) + [col_even_color]
384
+ color_lst = ["grey"] + [col_odd_color] * (copied.item_count - 1) + [col_even_color]
383
385
 
384
386
  tablevalues = rpt_df.transpose().to_numpy().tolist()
385
387
  cleanedtablevalues = list(tablevalues)[:-1]
@@ -424,7 +426,10 @@ def report_html(
424
426
  figure.add_layout_image(logo)
425
427
 
426
428
  figure.update_layout(fig.get("layout"))
427
- colorway: list[str] = cast("dict[str, list[str]]", fig["layout"]).get("colorway")
429
+ colorway: list[str] = cast("dict[str, list[str]]", fig["layout"]).get(
430
+ "colorway",
431
+ [],
432
+ )
428
433
 
429
434
  if vertical_legend:
430
435
  legend = {
@@ -445,12 +450,12 @@ def report_html(
445
450
 
446
451
  figure.update_layout(
447
452
  legend=legend,
448
- colorway=colorway[: data.item_count],
453
+ colorway=colorway[: copied.item_count],
449
454
  )
450
455
  figure.update_xaxes(gridcolor="#EEEEEE", automargin=True, tickangle=-45)
451
456
  figure.update_yaxes(tickformat=".2%", gridcolor="#EEEEEE", automargin=True)
452
457
 
453
- if isinstance(title, str):
458
+ if title:
454
459
  figure.update_layout(
455
460
  {"title": {"text": f"<b>{title}</b><br>", "font": {"size": 36}}},
456
461
  )
openseries/series.py CHANGED
@@ -9,18 +9,22 @@ SPDX-License-Identifier: BSD-3-Clause
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- from collections.abc import Iterable
13
12
  from copy import deepcopy
14
13
  from logging import getLogger
15
14
  from typing import TYPE_CHECKING, Any, TypeVar, cast
16
15
 
17
16
  if TYPE_CHECKING: # pragma: no cover
18
17
  import datetime as dt
18
+ from collections.abc import Callable
19
+
20
+ from numpy.typing import NDArray
21
+ from pandas import Timestamp
19
22
 
20
23
  from numpy import (
21
24
  append,
22
25
  array,
23
26
  cumprod,
27
+ float64,
24
28
  insert,
25
29
  isnan,
26
30
  log,
@@ -59,6 +63,9 @@ from .owntypes import (
59
63
  ValueType,
60
64
  )
61
65
 
66
+ FieldValidator = cast("Callable[..., Callable[..., Any]]", field_validator)
67
+ ModelValidator = cast("Callable[..., Callable[..., Any]]", model_validator)
68
+
62
69
  logger = getLogger(__name__)
63
70
 
64
71
  __all__ = ["OpenTimeSeries", "timeseries_chain"]
@@ -67,7 +74,7 @@ TypeOpenTimeSeries = TypeVar("TypeOpenTimeSeries", bound="OpenTimeSeries")
67
74
 
68
75
 
69
76
  # noinspection PyUnresolvedReferences,PyNestedDecorators
70
- class OpenTimeSeries(_CommonModel): # type: ignore[misc]
77
+ class OpenTimeSeries(_CommonModel[float]):
71
78
  """OpenTimeSeries objects are at the core of the openseries package.
72
79
 
73
80
  The intended use is to allow analyses of financial timeseries.
@@ -123,24 +130,25 @@ class OpenTimeSeries(_CommonModel): # type: ignore[misc]
123
130
  isin: str | None = None
124
131
  label: str | None = None
125
132
 
126
- @field_validator("domestic", mode="before") # type: ignore[misc]
133
+ @FieldValidator("domestic", mode="before")
127
134
  @classmethod
128
135
  def _validate_domestic(cls, value: CurrencyStringType) -> CurrencyStringType:
129
136
  """Pydantic validator to ensure domestic field is validated."""
130
137
  _ = Currency(ccy=value)
131
138
  return value
132
139
 
133
- @field_validator("countries", mode="before") # type: ignore[misc]
140
+ @FieldValidator("countries", mode="before")
134
141
  @classmethod
135
142
  def _validate_countries(cls, value: CountriesType) -> CountriesType:
136
143
  """Pydantic validator to ensure countries field is validated."""
137
144
  _ = Countries(countryinput=value)
138
145
  return value
139
146
 
140
- @field_validator("markets", mode="before") # type: ignore[misc]
147
+ @FieldValidator("markets", mode="before")
141
148
  @classmethod
142
149
  def _validate_markets(
143
- cls, value: list[str] | str | None
150
+ cls,
151
+ value: list[str] | str | None,
144
152
  ) -> list[str] | str | None:
145
153
  """Pydantic validator to ensure markets field is validated."""
146
154
  msg = (
@@ -156,7 +164,7 @@ class OpenTimeSeries(_CommonModel): # type: ignore[misc]
156
164
  raise MarketsNotStringNorListStrError(item_msg)
157
165
  raise MarketsNotStringNorListStrError(msg)
158
166
 
159
- @model_validator(mode="after") # type: ignore[misc,unused-ignore]
167
+ @ModelValidator(mode="after")
160
168
  def _dates_and_values_validate(self: Self) -> Self:
161
169
  """Pydantic validator to ensure dates and values are validated."""
162
170
  values_list_length = len(self.values)
@@ -165,9 +173,6 @@ class OpenTimeSeries(_CommonModel): # type: ignore[misc]
165
173
  if dates_list_length != dates_set_length:
166
174
  msg = "Dates are not unique"
167
175
  raise ValueError(msg)
168
- if values_list_length < 1:
169
- msg = "There must be at least 1 value"
170
- raise ValueError(msg)
171
176
  if (
172
177
  (dates_list_length != values_list_length)
173
178
  or (len(self.tsdf.index) != self.tsdf.shape[0])
@@ -370,19 +375,17 @@ class OpenTimeSeries(_CommonModel): # type: ignore[misc]
370
375
  An OpenTimeSeries object
371
376
 
372
377
  """
373
- if not isinstance(d_range, Iterable) and all([days, end_dt]):
374
- d_range = DatetimeIndex(
375
- [d.date() for d in date_range(periods=days, end=end_dt, freq="D")],
376
- )
377
- elif not isinstance(d_range, Iterable) and not all([days, end_dt]):
378
- msg = "If d_range is not provided both days and end_dt must be."
379
- raise IncorrectArgumentComboError(msg)
380
-
381
- deltas = array(
382
- [i.days for i in DatetimeIndex(d_range)[1:] - DatetimeIndex(d_range)[:-1]], # type: ignore[arg-type]
383
- )
378
+ if d_range is None:
379
+ if days is not None and end_dt is not None:
380
+ d_range = DatetimeIndex(
381
+ [d.date() for d in date_range(periods=days, end=end_dt, freq="D")],
382
+ )
383
+ else:
384
+ msg = "If d_range is not provided both days and end_dt must be."
385
+ raise IncorrectArgumentComboError(msg)
386
+ deltas = array([i.days for i in d_range[1:] - d_range[:-1]])
384
387
  arr: list[float] = list(cumprod(insert(1 + deltas * rate / 365, 0, 1.0)))
385
- dates = [d.strftime("%Y-%m-%d") for d in DatetimeIndex(d_range)] # type: ignore[arg-type]
388
+ dates = [d.strftime("%Y-%m-%d") for d in d_range]
386
389
 
387
390
  return cls(
388
391
  timeseries_id="",
@@ -469,12 +472,13 @@ class OpenTimeSeries(_CommonModel): # type: ignore[misc]
469
472
  The returns of the values in the series
470
473
 
471
474
  """
475
+ # noinspection PyCallingNonCallable
472
476
  returns = self.tsdf.ffill().pct_change()
473
477
  returns.iloc[0] = 0
474
478
  self.valuetype = ValueType.RTRN
475
479
  arrays = [[self.label], [self.valuetype]]
476
480
  returns.columns = MultiIndex.from_arrays(
477
- arrays=arrays # type: ignore[arg-type]
481
+ arrays=arrays, # type: ignore[arg-type]
478
482
  )
479
483
  self.tsdf = returns.copy()
480
484
  return self
@@ -549,12 +553,16 @@ class OpenTimeSeries(_CommonModel): # type: ignore[misc]
549
553
  An OpenTimeSeries object
550
554
 
551
555
  """
552
- arr = array(self.values) / divider
556
+ arr: NDArray[float64] = array(self.values) / divider
553
557
 
554
558
  deltas = array([i.days for i in self.tsdf.index[1:] - self.tsdf.index[:-1]])
555
- # noinspection PyTypeChecker
556
- arr = cumprod( # type: ignore[assignment]
557
- a=insert(arr=1.0 + deltas * arr[:-1] / days_in_year, obj=0, values=1.0)
559
+ arr = cast(
560
+ "NDArray[float64]",
561
+ cumprod(
562
+ a=insert(
563
+ arr=1.0 + deltas * arr[:-1] / days_in_year, obj=0, values=1.0
564
+ ),
565
+ ),
558
566
  )
559
567
 
560
568
  self.dates = [d.strftime("%Y-%m-%d") for d in self.tsdf.index]
@@ -672,32 +680,37 @@ class OpenTimeSeries(_CommonModel): # type: ignore[misc]
672
680
 
673
681
  """
674
682
  earlier, later = self.calc_range(
675
- months_offset=months_from_last, from_dt=from_date, to_dt=to_date
683
+ months_offset=months_from_last,
684
+ from_dt=from_date,
685
+ to_dt=to_date,
676
686
  )
677
687
  if periods_in_a_year_fixed:
678
688
  time_factor = float(periods_in_a_year_fixed)
679
689
  else:
680
- how_many = self.tsdf.loc[
681
- cast("int", earlier) : cast("int", later),
682
- self.tsdf.columns.to_numpy()[0],
683
- ].count()
690
+ how_many = (
691
+ self.tsdf.loc[cast("Timestamp", earlier) : cast("Timestamp", later)]
692
+ .count()
693
+ .iloc[0]
694
+ )
684
695
  fraction = (later - earlier).days / 365.25
685
- time_factor = cast("int", how_many) / fraction
696
+ time_factor = how_many / fraction
686
697
 
687
- data = self.tsdf.loc[cast("int", earlier) : cast("int", later)].copy()
698
+ data = self.tsdf.loc[
699
+ cast("Timestamp", earlier) : cast("Timestamp", later)
700
+ ].copy()
688
701
 
689
702
  data[self.label, ValueType.RTRN] = log(
690
- data.loc[:, self.tsdf.columns.to_numpy()[0]]
703
+ data.loc[:, self.tsdf.columns.to_numpy()[0]],
691
704
  ).diff()
692
705
 
693
706
  rawdata = [
694
- data.loc[:, cast("int", (self.label, ValueType.RTRN))]
707
+ data[(self.label, ValueType.RTRN)]
695
708
  .iloc[1:day_chunk]
696
709
  .std(ddof=dlta_degr_freedms)
697
710
  * sqrt(time_factor),
698
711
  ]
699
712
 
700
- for item in data.loc[:, cast("int", (self.label, ValueType.RTRN))].iloc[1:]:
713
+ for item in data[(self.label, ValueType.RTRN)].iloc[1:]:
701
714
  prev = rawdata[-1]
702
715
  rawdata.append(
703
716
  sqrt(
@@ -865,6 +878,7 @@ def timeseries_chain(
865
878
 
866
879
  dates.extend([x.strftime("%Y-%m-%d") for x in new.tsdf.index])
867
880
 
881
+ # noinspection PyTypeChecker
868
882
  return back.__class__(
869
883
  timeseries_id=new.timeseries_id,
870
884
  instrument_id=new.instrument_id,
openseries/simulation.py CHANGED
@@ -60,7 +60,7 @@ def _random_generator(seed: int | None) -> Generator:
60
60
  return Generator(bit_generator=bg)
61
61
 
62
62
 
63
- class ReturnSimulation(BaseModel): # type: ignore[misc]
63
+ class ReturnSimulation(BaseModel):
64
64
  """The class ReturnSimulation allows for simulating financial timeseries.
65
65
 
66
66
  Parameters
@@ -115,7 +115,7 @@ class ReturnSimulation(BaseModel): # type: ignore[misc]
115
115
  Simulation data
116
116
 
117
117
  """
118
- return self.dframe.add(1.0).cumprod(axis="columns").T # type: ignore[no-any-return]
118
+ return self.dframe.add(1.0).cumprod(axis="columns").T
119
119
 
120
120
  @property
121
121
  def realized_mean_return(self: Self) -> float:
@@ -463,7 +463,7 @@ class ReturnSimulation(BaseModel): # type: ignore[misc]
463
463
  [ValueType.RTRN],
464
464
  ],
465
465
  )
466
- return sdf # type: ignore[no-any-return]
466
+ return sdf
467
467
 
468
468
  fdf = DataFrame()
469
469
  for item in range(self.number_of_sims):
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: openseries
3
- Version: 1.9.4
3
+ Version: 1.9.6
4
4
  Summary: Tools for analyzing financial timeseries.
5
5
  License: # BSD 3-Clause License
6
6
 
@@ -29,6 +29,7 @@ License: # BSD 3-Clause License
29
29
  however caused and on any theory of liability, whether in contract, strict liability,
30
30
  or tort (including negligence or otherwise) arising in any way out of the use of this
31
31
  software, even if advised of the possibility of such damage.
32
+ License-File: LICENSE.md
32
33
  Keywords: python,finance,fintech,data-science,timeseries,timeseries-data,timeseries-analysis,investment,investment-analysis,investing
33
34
  Author: Martin Karrin
34
35
  Author-email: martin.karrin@captor.se
@@ -119,7 +120,7 @@ _,_=series.plot_series()
119
120
 
120
121
  ### Sample output using the report_html() function:
121
122
 
122
- <img src="https://raw.githubusercontent.com/CaptorAB/openseries/master/captor_plot_image.png" alt="Two Assets Compared" width="1000" />
123
+ <img src="https://raw.githubusercontent.com/CaptorAB/openseries/master/openseries_plot.png" alt="Two Assets Compared" width="1000" />
123
124
 
124
125
  ## Development Instructions
125
126
 
@@ -0,0 +1,17 @@
1
+ openseries/__init__.py,sha256=WAh79oE-ceGG_yl4nBukkp3UPvmLk4u_GySL2xOKbxE,1375
2
+ openseries/_common_model.py,sha256=oZDTfUiQ4OG0T0RsBwTwRPHQcFpdOv1X7447hssoma4,82768
3
+ openseries/_risk.py,sha256=8XKZWWXrECo0Vd9r2kbcn4dzyPuo93DAEO8eSkv4w20,2357
4
+ openseries/datefixer.py,sha256=eVhxaFj_la_XZQuPQHvinTWEzCCn8ct_AnZEYPOpY6U,15775
5
+ openseries/frame.py,sha256=BG_Qnp0PMIZ7ZiShqTjO3Koj7Gs04n4WyzApCvtcUQY,57953
6
+ openseries/load_plotly.py,sha256=C6iQyabfi5ubSONuis3yRHb3bUktBtTDlovsDIaeHNQ,2266
7
+ openseries/owntypes.py,sha256=4IZvwl_YtoUZKlmVX65j5fp1zmfmOJLvaX8vxEIzIAY,9665
8
+ openseries/plotly_captor_logo.json,sha256=F5nhMzEyxKywtjvQqMTKgKRCJQYMDIiBgDSxdte8Clo,178
9
+ openseries/plotly_layouts.json,sha256=MvDEQuiqIhMBXBelXb1sedTOlTPheizv6NZRLeE9YS4,1431
10
+ openseries/portfoliotools.py,sha256=NqSTMlVv9Szu2usXtYzt__661VJoLsAf059ThfBr99Q,19677
11
+ openseries/report.py,sha256=pnxiEfbbkDmzj-lPcWgmYao1vtv1DUG8b73QDQKJa8o,14379
12
+ openseries/series.py,sha256=NgEZ2YPiLG55Bba48XS2zSh5dRLqz8hyGm-CGG1jNmY,29122
13
+ openseries/simulation.py,sha256=8J_iDlKjDVDiHMTWQAzHdXWxNR81pOz2RfdfUMHKSqQ,14337
14
+ openseries-1.9.6.dist-info/METADATA,sha256=GrNCO5aUnS5cuboqOsyU8sTN1YyHARF3RhFT1keNM3s,48324
15
+ openseries-1.9.6.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
16
+ openseries-1.9.6.dist-info/licenses/LICENSE.md,sha256=wNupG-KLsG0aTncb_SMNDh1ExtrKXlpxSJ6RC-g-SWs,1516
17
+ openseries-1.9.6.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.3
2
+ Generator: poetry-core 2.2.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any