openseries 1.8.3__py3-none-any.whl → 1.9.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.
openseries/__init__.py CHANGED
@@ -1,4 +1,11 @@
1
- """openseries.openseries.__init__.py."""
1
+ """openseries.openseries.__init__.py.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  from .datefixer import (
4
11
  date_fix,
@@ -18,6 +25,7 @@ from .portfoliotools import (
18
25
  sharpeplot,
19
26
  simulate_portfolios,
20
27
  )
28
+ from .report import report_html
21
29
  from .series import OpenTimeSeries, timeseries_chain
22
30
  from .simulation import ReturnSimulation
23
31
 
@@ -37,6 +45,7 @@ __all__ = [
37
45
  "load_plotly_dict",
38
46
  "offset_business_days",
39
47
  "prepare_plot_data",
48
+ "report_html",
40
49
  "sharpeplot",
41
50
  "simulate_portfolios",
42
51
  "timeseries_chain",
@@ -1,4 +1,11 @@
1
- """Defining the _CommonModel class."""
1
+ """Defining the _CommonModel class.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  # mypy: disable-error-code="no-any-return"
4
11
  from __future__ import annotations
@@ -58,7 +65,7 @@ from plotly.figure_factory import create_distplot # type: ignore[import-untyped
58
65
  from plotly.graph_objs import Figure # type: ignore[import-untyped]
59
66
  from plotly.io import to_html # type: ignore[import-untyped]
60
67
  from plotly.offline import plot # type: ignore[import-untyped]
61
- from pydantic import BaseModel, ConfigDict, DirectoryPath
68
+ from pydantic import BaseModel, ConfigDict, DirectoryPath, ValidationError
62
69
  from scipy.stats import ( # type: ignore[import-untyped]
63
70
  kurtosis,
64
71
  norm,
@@ -377,13 +384,23 @@ class _CommonModel(BaseModel): # type: ignore[misc]
377
384
 
378
385
  """
379
386
  method: LiteralPandasReindexMethod = "nearest"
380
- countries = "SE"
387
+
388
+ try:
389
+ countries = self.countries
390
+ markets = self.markets
391
+ except AttributeError:
392
+ countries = self.constituents[0].countries
393
+ markets = self.constituents[0].markets
394
+
381
395
  wmdf = self.tsdf.copy()
396
+
382
397
  dates = _do_resample_to_business_period_ends(
383
398
  data=wmdf,
384
399
  freq="BME",
385
400
  countries=countries,
401
+ markets=markets,
386
402
  )
403
+
387
404
  wmdf = wmdf.reindex(index=[deyt.date() for deyt in dates], method=method)
388
405
  wmdf.index = DatetimeIndex(wmdf.index)
389
406
  result = wmdf.ffill().pct_change().min()
@@ -539,14 +556,20 @@ class _CommonModel(BaseModel): # type: ignore[misc]
539
556
 
540
557
  def align_index_to_local_cdays(
541
558
  self: Self,
542
- countries: CountriesType = "SE",
559
+ countries: CountriesType | None = None,
560
+ markets: list[str] | str | None = None,
561
+ custom_holidays: list[str] | str | None = None,
543
562
  ) -> Self:
544
563
  """Align the index of .tsdf with local calendar business days.
545
564
 
546
565
  Parameters
547
566
  ----------
548
- countries: CountriesType, default: "SE"
567
+ countries: CountriesType, optional
549
568
  (List of) country code(s) according to ISO 3166-1 alpha-2
569
+ markets: list[str] | str, optional
570
+ (List of) markets code(s) according to pandas-market-calendars
571
+ custom_holidays: list[str] | str, optional
572
+ Argument where missing holidays can be added
550
573
 
551
574
  Returns:
552
575
  -------
@@ -556,10 +579,37 @@ class _CommonModel(BaseModel): # type: ignore[misc]
556
579
  """
557
580
  startyear = cast("int", to_datetime(self.tsdf.index[0]).year)
558
581
  endyear = cast("int", to_datetime(self.tsdf.index[-1]).year)
582
+
583
+ if countries:
584
+ try:
585
+ self.countries = countries
586
+ except ValidationError:
587
+ for serie in self.constituents:
588
+ serie.countries = countries
589
+ else:
590
+ try:
591
+ countries = self.countries
592
+ except AttributeError:
593
+ countries = self.constituents[0].countries
594
+
595
+ if markets:
596
+ try:
597
+ self.markets = markets
598
+ except ValidationError:
599
+ for serie in self.constituents:
600
+ serie.markets = markets
601
+ else:
602
+ try:
603
+ markets = self.markets
604
+ except AttributeError:
605
+ markets = self.constituents[0].markets
606
+
559
607
  calendar = holiday_calendar(
560
608
  startyear=startyear,
561
609
  endyear=endyear,
562
610
  countries=countries,
611
+ markets=markets,
612
+ custom_holidays=custom_holidays,
563
613
  )
564
614
 
565
615
  d_range = [
openseries/_risk.py CHANGED
@@ -1,4 +1,11 @@
1
- """Various risk related functions."""
1
+ """Various risk related functions.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  from __future__ import annotations
4
11
 
openseries/datefixer.py CHANGED
@@ -1,10 +1,18 @@
1
- """Date related utilities."""
1
+ """Date related utilities.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  from __future__ import annotations
4
11
 
5
12
  import datetime as dt
6
13
  from typing import TYPE_CHECKING, cast
7
14
 
15
+ import exchange_calendars as exchcal
8
16
  from dateutil.relativedelta import relativedelta
9
17
  from holidays import (
10
18
  country_holidays,
@@ -24,6 +32,7 @@ from pandas.tseries.offsets import CustomBusinessDay
24
32
  from .owntypes import (
25
33
  BothStartAndEndError,
26
34
  CountriesNotStringNorListStrError,
35
+ MarketsNotStringNorListStrError,
27
36
  TradingDaysNotAboveZeroError,
28
37
  )
29
38
 
@@ -31,7 +40,6 @@ if TYPE_CHECKING:
31
40
  from .owntypes import ( # pragma: no cover
32
41
  CountriesType,
33
42
  DateType,
34
- HolidayType,
35
43
  LiteralBizDayFreq,
36
44
  )
37
45
 
@@ -45,11 +53,58 @@ __all__ = [
45
53
  ]
46
54
 
47
55
 
56
+ def market_holidays(
57
+ startyear: int,
58
+ endyear: int,
59
+ markets: str | list[str],
60
+ ) -> list[str]:
61
+ """Return a dict of holiday dates mapping to list of markets closed.
62
+
63
+ Parameters
64
+ ----------
65
+ startyear: int
66
+ First year (inclusive) to consider.
67
+ endyear: int
68
+ Last year (inclusive) to consider.
69
+ markets: str | list[str]
70
+ String or list of market codes supported by pandas_market_calendars.
71
+
72
+ Returns:
73
+ -------
74
+ list[str]
75
+ list of holiday dates.
76
+ """
77
+ market_list = [markets] if isinstance(markets, str) else list(markets)
78
+
79
+ supported = exchcal.get_calendar_names()
80
+
81
+ if not all(m in supported for m in market_list):
82
+ msg = (
83
+ "Argument markets must be a string market code or a list of market "
84
+ "codes supported by pandas_market_calendars."
85
+ )
86
+ raise MarketsNotStringNorListStrError(msg)
87
+
88
+ holidays: list[str] = []
89
+ for m in market_list:
90
+ cal = exchcal.get_calendar(m)
91
+ cal_hols = cal.regular_holidays.holidays()
92
+ my_hols: list[str] = [
93
+ date.date().strftime("%Y-%m-%d")
94
+ for date in cal_hols
95
+ if (startyear <= date.date().year <= endyear)
96
+ ]
97
+ holidays.extend(my_hols)
98
+
99
+ return list(set(holidays))
100
+
101
+
48
102
  def holiday_calendar(
49
103
  startyear: int,
50
104
  endyear: int,
51
105
  countries: CountriesType = "SE",
52
- custom_holidays: HolidayType | None = None,
106
+ markets: list[str] | str | None = None,
107
+ custom_holidays: list[str] | str | None = None,
53
108
  ) -> busdaycalendar:
54
109
  """Generate a business calendar.
55
110
 
@@ -61,9 +116,10 @@ def holiday_calendar(
61
116
  Last year in date range generated
62
117
  countries: CountriesType, default: "SE"
63
118
  (List of) country code(s) according to ISO 3166-1 alpha-2
64
- custom_holidays: HolidayType, optional
65
- Argument where missing holidays can be added as
66
- {"2021-02-12": "Jack's birthday"} or ["2021-02-12"]
119
+ markets: list[str] | str, optional
120
+ (List of) markets code(s) according to pandas-market-calendars
121
+ custom_holidays: list[str] | str, optional
122
+ Argument where missing holidays can be added
67
123
 
68
124
  Returns:
69
125
  -------
@@ -79,20 +135,16 @@ def holiday_calendar(
79
135
 
80
136
  if isinstance(countries, str) and countries in list_supported_countries():
81
137
  staging = country_holidays(country=countries, years=years)
82
- if custom_holidays is not None:
83
- staging.update(custom_holidays)
84
- hols = array(sorted(staging.keys()), dtype="datetime64[D]")
85
- elif isinstance(countries, list) and all(
138
+ hols = list(staging.keys())
139
+ elif isinstance(countries, (list, set)) and all(
86
140
  country in list_supported_countries() for country in countries
87
141
  ):
88
142
  country: str
89
143
  countryholidays: list[dt.date | str] = []
90
- for i, country in enumerate(countries):
144
+ for country in countries:
91
145
  staging = country_holidays(country=country, years=years)
92
- if i == 0 and custom_holidays is not None:
93
- staging.update(custom_holidays)
94
146
  countryholidays += list(staging)
95
- hols = array(sorted(set(countryholidays)), dtype="datetime64[D]")
147
+ hols = list(countryholidays)
96
148
  else:
97
149
  msg = (
98
150
  "Argument countries must be a string country code or "
@@ -100,7 +152,22 @@ def holiday_calendar(
100
152
  )
101
153
  raise CountriesNotStringNorListStrError(msg)
102
154
 
103
- return busdaycalendar(holidays=hols)
155
+ if markets:
156
+ market_hols = market_holidays(
157
+ startyear=startyear, endyear=endyear, markets=markets
158
+ )
159
+ dt_mkt_hols = [date_fix(fixerdate=ddate) for ddate in market_hols]
160
+ hols.extend(dt_mkt_hols)
161
+
162
+ if custom_holidays:
163
+ custom_list = (
164
+ [custom_holidays]
165
+ if isinstance(custom_holidays, str)
166
+ else list(custom_holidays) # type: ignore[arg-type]
167
+ )
168
+ hols.extend([date_fix(fixerdate=ddate) for ddate in custom_list])
169
+
170
+ return busdaycalendar(holidays=array(sorted(set(hols)), dtype="datetime64[D]"))
104
171
 
105
172
 
106
173
  def date_fix(
@@ -137,7 +204,8 @@ def date_offset_foll(
137
204
  raw_date: DateType,
138
205
  months_offset: int = 12,
139
206
  countries: CountriesType = "SE",
140
- custom_holidays: HolidayType | None = None,
207
+ markets: list[str] | str | None = None,
208
+ custom_holidays: list[str] | str | None = None,
141
209
  *,
142
210
  adjust: bool = False,
143
211
  following: bool = True,
@@ -152,9 +220,10 @@ def date_offset_foll(
152
220
  Number of months as integer
153
221
  countries: CountriesType, default: "SE"
154
222
  (List of) country code(s) according to ISO 3166-1 alpha-2
155
- custom_holidays: HolidayType, optional
156
- Argument where missing holidays can be added as
157
- {"2021-02-12": "Jack's birthday"} or ["2021-02-12"]
223
+ markets: list[str] | str, optional
224
+ (List of) markets code(s) according to pandas-market-calendars
225
+ custom_holidays: list[str] | str, optional
226
+ Argument where missing holidays can be added
158
227
  adjust: bool, default: False
159
228
  Determines if offset should adjust for business days
160
229
  following: bool, default: True
@@ -180,6 +249,7 @@ def date_offset_foll(
180
249
  startyear=startyear,
181
250
  endyear=endyear,
182
251
  countries=countries,
252
+ markets=markets,
183
253
  custom_holidays=custom_holidays,
184
254
  )
185
255
  while not is_busday(dates=new_date, busdaycal=calendar):
@@ -191,7 +261,8 @@ def date_offset_foll(
191
261
  def get_previous_business_day_before_today(
192
262
  today: dt.date | None = None,
193
263
  countries: CountriesType = "SE",
194
- custom_holidays: HolidayType | None = None,
264
+ markets: list[str] | str | None = None,
265
+ custom_holidays: list[str] | str | None = None,
195
266
  ) -> dt.date:
196
267
  """Bump date backwards to find the previous business day.
197
268
 
@@ -201,9 +272,10 @@ def get_previous_business_day_before_today(
201
272
  Manual input of the day from where the previous business day is found
202
273
  countries: CountriesType, default: "SE"
203
274
  (List of) country code(s) according to ISO 3166-1 alpha-2
204
- custom_holidays: HolidayType, optional
205
- Argument where missing holidays can be added as
206
- {"2021-02-12": "Jack's birthday"} or ["2021-02-12"]
275
+ markets: list[str] | str, optional
276
+ (List of) markets code(s) according to pandas-market-calendars
277
+ custom_holidays: list[str] | str, optional
278
+ Argument where missing holidays can be added
207
279
 
208
280
  Returns:
209
281
  -------
@@ -215,10 +287,11 @@ def get_previous_business_day_before_today(
215
287
  today = dt.datetime.now().astimezone().date()
216
288
 
217
289
  return date_offset_foll(
218
- today - dt.timedelta(days=1),
290
+ raw_date=today - dt.timedelta(days=1),
291
+ months_offset=0,
219
292
  countries=countries,
293
+ markets=markets,
220
294
  custom_holidays=custom_holidays,
221
- months_offset=0,
222
295
  adjust=True,
223
296
  following=False,
224
297
  )
@@ -228,7 +301,8 @@ def offset_business_days(
228
301
  ddate: dt.date,
229
302
  days: int,
230
303
  countries: CountriesType = "SE",
231
- custom_holidays: HolidayType | None = None,
304
+ markets: list[str] | str | None = None,
305
+ custom_holidays: list[str] | str | None = None,
232
306
  ) -> dt.date:
233
307
  """Bump date by business days.
234
308
 
@@ -244,9 +318,10 @@ def offset_business_days(
244
318
  If days is set as anything other than an integer its value is set to zero
245
319
  countries: CountriesType, default: "SE"
246
320
  (List of) country code(s) according to ISO 3166-1 alpha-2
247
- custom_holidays: HolidayType, optional
248
- Argument where missing holidays can be added as
249
- {"2021-02-12": "Jack's birthday"} or ["2021-02-12"]
321
+ markets: list[str] | str, optional
322
+ (List of) markets code(s) according to pandas-market-calendars
323
+ custom_holidays: list[str] | str, optional
324
+ Argument where missing holidays can be added
250
325
 
251
326
  Returns:
252
327
  -------
@@ -266,6 +341,7 @@ def offset_business_days(
266
341
  startyear=ndate.year,
267
342
  endyear=ddate.year,
268
343
  countries=countries,
344
+ markets=markets,
269
345
  custom_holidays=custom_holidays,
270
346
  )
271
347
  local_bdays: list[dt.date] = [
@@ -283,6 +359,7 @@ def offset_business_days(
283
359
  startyear=ddate.year,
284
360
  endyear=ndate.year,
285
361
  countries=countries,
362
+ markets=markets,
286
363
  custom_holidays=custom_holidays,
287
364
  )
288
365
  local_bdays = [
@@ -310,6 +387,8 @@ def generate_calendar_date_range(
310
387
  start: dt.date | None = None,
311
388
  end: dt.date | None = None,
312
389
  countries: CountriesType = "SE",
390
+ markets: list[str] | str | None = None,
391
+ custom_holidays: list[str] | str | None = None,
313
392
  ) -> list[dt.date]:
314
393
  """Generate a list of business day calendar dates.
315
394
 
@@ -323,6 +402,10 @@ def generate_calendar_date_range(
323
402
  Date when the range ends
324
403
  countries: CountriesType, default: "SE"
325
404
  (List of) country code(s) according to ISO 3166-1 alpha-2
405
+ markets: list[str] | str, optional
406
+ (List of) markets code(s) according to pandas-market-calendars
407
+ custom_holidays: list[str] | str, optional
408
+ Argument where missing holidays can be added
326
409
 
327
410
  Returns:
328
411
  -------
@@ -344,6 +427,8 @@ def generate_calendar_date_range(
344
427
  startyear=start.year,
345
428
  endyear=date_fix(tmp_range.tolist()[-1]).year,
346
429
  countries=countries,
430
+ markets=markets,
431
+ custom_holidays=custom_holidays,
347
432
  )
348
433
  return [
349
434
  d.date()
@@ -360,6 +445,8 @@ def generate_calendar_date_range(
360
445
  startyear=date_fix(tmp_range.tolist()[0]).year,
361
446
  endyear=end.year,
362
447
  countries=countries,
448
+ markets=markets,
449
+ custom_holidays=custom_holidays,
363
450
  )
364
451
  return [
365
452
  d.date()
@@ -381,6 +468,8 @@ def _do_resample_to_business_period_ends(
381
468
  data: DataFrame,
382
469
  freq: LiteralBizDayFreq,
383
470
  countries: CountriesType,
471
+ markets: list[str] | str | None = None,
472
+ custom_holidays: list[str] | str | None = None,
384
473
  ) -> DatetimeIndex:
385
474
  """Resample timeseries frequency to business calendar month end dates.
386
475
 
@@ -394,7 +483,10 @@ def _do_resample_to_business_period_ends(
394
483
  The date offset string that sets the resampled frequency
395
484
  countries: CountriesType
396
485
  (List of) country code(s) according to ISO 3166-1 alpha-2
397
- to create a business day calendar used for date adjustments
486
+ markets: list[str] | str, optional
487
+ (List of) markets code(s) according to pandas-market-calendars
488
+ custom_holidays: list[str] | str, optional
489
+ Argument where missing holidays can be added
398
490
 
399
491
  Returns:
400
492
  -------
@@ -413,12 +505,14 @@ def _do_resample_to_business_period_ends(
413
505
  dates = DatetimeIndex(
414
506
  [copydata.index[0]]
415
507
  + [
416
- date_offset_foll(
417
- dt.date(d.year, d.month, 1)
508
+ date_offset_foll( # type: ignore[misc]
509
+ raw_date=dt.date(d.year, d.month, 1)
418
510
  + relativedelta(months=1)
419
511
  - dt.timedelta(days=1),
420
- countries=countries,
421
512
  months_offset=0,
513
+ countries=countries,
514
+ markets=markets,
515
+ custom_holidays=custom_holidays,
422
516
  adjust=True,
423
517
  following=False,
424
518
  )
openseries/frame.py CHANGED
@@ -1,4 +1,11 @@
1
- """Defining the OpenFrame class."""
1
+ """Defining the OpenFrame class.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  # mypy: disable-error-code="index,assignment,arg-type,no-any-return"
4
11
  from __future__ import annotations
@@ -41,7 +48,6 @@ from pydantic import field_validator
41
48
  from ._common_model import _CommonModel
42
49
  from .datefixer import _do_resample_to_business_period_ends
43
50
  from .owntypes import (
44
- CountriesType,
45
51
  DaysInYearType,
46
52
  LabelsNotUniqueError,
47
53
  LiteralBizDayFreq,
@@ -429,7 +435,6 @@ class OpenFrame(_CommonModel):
429
435
  def resample_to_business_period_ends(
430
436
  self: Self,
431
437
  freq: LiteralBizDayFreq = "BME",
432
- countries: CountriesType = "SE",
433
438
  method: LiteralPandasReindexMethod = "nearest",
434
439
  ) -> Self:
435
440
  """Resamples timeseries frequency to the business calendar month end dates.
@@ -440,9 +445,6 @@ class OpenFrame(_CommonModel):
440
445
  ----------
441
446
  freq: LiteralBizDayFreq, default "BME"
442
447
  The date offset string that sets the resampled frequency
443
- countries: CountriesType, default: "SE"
444
- (List of) country code(s) according to ISO 3166-1 alpha-2
445
- to create a business day calendar used for date adjustments
446
448
  method: LiteralPandasReindexMethod, default: nearest
447
449
  Controls the method used to align values across columns
448
450
 
@@ -456,7 +458,8 @@ class OpenFrame(_CommonModel):
456
458
  dates = _do_resample_to_business_period_ends(
457
459
  data=xerie.tsdf,
458
460
  freq=freq,
459
- countries=countries,
461
+ countries=xerie.countries,
462
+ markets=xerie.markets,
460
463
  )
461
464
  xerie.tsdf = xerie.tsdf.reindex(
462
465
  [deyt.date() for deyt in dates],
openseries/load_plotly.py CHANGED
@@ -1,4 +1,11 @@
1
- """Function to load plotly layout and configuration from local json file."""
1
+ """Function to load plotly layout and configuration from local json file.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  from __future__ import annotations
4
11
 
openseries/owntypes.py CHANGED
@@ -1,4 +1,11 @@
1
- """Declaring types used throughout the project."""
1
+ """Declaring types used throughout the project.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  from __future__ import annotations
4
11
 
@@ -86,16 +93,6 @@ DaysInYearType = Annotated[int, Field(strict=True, ge=1, le=366)]
86
93
 
87
94
  DateType = str | dt.date | dt.datetime | datetime64 | Timestamp
88
95
 
89
- HolidayType = (
90
- dict[dt.date | dt.datetime | str | float | int, str]
91
- | list[dt.date | dt.datetime | str | float | int]
92
- | dt.date
93
- | dt.datetime
94
- | str
95
- | float
96
- | int
97
- )
98
-
99
96
  PlotlyLayoutType = dict[
100
97
  str,
101
98
  str
@@ -356,6 +353,10 @@ class CountriesNotStringNorListStrError(Exception):
356
353
  """Raised when countries argument is not provided in correct format."""
357
354
 
358
355
 
356
+ class MarketsNotStringNorListStrError(Exception):
357
+ """Raised when markets argument is not provided in correct format."""
358
+
359
+
359
360
  class TradingDaysNotAboveZeroError(Exception):
360
361
  """Raised when trading days argument is not above zero."""
361
362
 
@@ -1,4 +1,11 @@
1
- """Defining the portfolio tools for the OpenFrame class."""
1
+ """Defining the portfolio tools for the OpenFrame class.
2
+
3
+ Copyright (c) Captor Fund Management AB. This file is part of the openseries project.
4
+
5
+ Licensed under the BSD 3-Clause License. You may obtain a copy of the License at:
6
+ https://github.com/CaptorAB/openseries/blob/master/LICENSE.md
7
+ SPDX-License-Identifier: BSD-3-Clause
8
+ """
2
9
 
3
10
  # mypy: disable-error-code="index,assignment"
4
11
  from __future__ import annotations