openseries 2.0.0__py3-none-any.whl → 2.0.1__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 +2 -0
- openseries/_common_model.py +69 -1
- openseries/frame.py +313 -3
- openseries/owntypes.py +23 -1
- {openseries-2.0.0.dist-info → openseries-2.0.1.dist-info}/METADATA +4 -2
- {openseries-2.0.0.dist-info → openseries-2.0.1.dist-info}/RECORD +8 -8
- {openseries-2.0.0.dist-info → openseries-2.0.1.dist-info}/WHEEL +0 -0
- {openseries-2.0.0.dist-info → openseries-2.0.1.dist-info}/licenses/LICENSE.md +0 -0
openseries/__init__.py
CHANGED
openseries/_common_model.py
CHANGED
@@ -760,6 +760,7 @@ class _CommonModel(BaseModel, Generic[SeriesOrFloat_co]):
|
|
760
760
|
|
761
761
|
Parameters
|
762
762
|
----------
|
763
|
+
|
763
764
|
countries: CountriesType, optional
|
764
765
|
(List of) country code(s) according to ISO 3166-1 alpha-2
|
765
766
|
markets: list[str] | str, optional
|
@@ -767,7 +768,7 @@ class _CommonModel(BaseModel, Generic[SeriesOrFloat_co]):
|
|
767
768
|
custom_holidays: list[str] | str, optional
|
768
769
|
Argument where missing holidays can be added
|
769
770
|
method: LiteralPandasReindexMethod, default: "nearest"
|
770
|
-
|
771
|
+
Method for reindexing when aligning to business days
|
771
772
|
|
772
773
|
Returns:
|
773
774
|
--------
|
@@ -2694,3 +2695,70 @@ class _CommonModel(BaseModel, Generic[SeriesOrFloat_co]):
|
|
2694
2695
|
)
|
2695
2696
|
|
2696
2697
|
return DataFrame(voldf)
|
2698
|
+
|
2699
|
+
def outliers(
|
2700
|
+
self: Self,
|
2701
|
+
threshold: float = 3.0,
|
2702
|
+
months_from_last: int | None = None,
|
2703
|
+
from_date: dt.date | None = None,
|
2704
|
+
to_date: dt.date | None = None,
|
2705
|
+
) -> Series | DataFrame:
|
2706
|
+
"""Detect outliers using z-score analysis.
|
2707
|
+
|
2708
|
+
Identifies data points where the absolute z-score exceeds the threshold.
|
2709
|
+
For OpenTimeSeries, returns a pandas Series with dates and outlier values.
|
2710
|
+
For OpenFrame, returns a pandas DataFrame with dates and outlier values
|
2711
|
+
for each column.
|
2712
|
+
|
2713
|
+
Parameters
|
2714
|
+
----------
|
2715
|
+
threshold: float, default: 3.0
|
2716
|
+
Z-score threshold for outlier detection. Values with absolute
|
2717
|
+
z-score > threshold are considered outliers.
|
2718
|
+
months_from_last : int, optional
|
2719
|
+
Number of months offset as positive integer. Overrides use of from_date
|
2720
|
+
and to_date
|
2721
|
+
from_date : datetime.date, optional
|
2722
|
+
Specific from date
|
2723
|
+
to_date : datetime.date, optional
|
2724
|
+
Specific to date
|
2725
|
+
|
2726
|
+
Returns:
|
2727
|
+
--------
|
2728
|
+
pandas.Series | pandas.DataFrame
|
2729
|
+
For OpenTimeSeries: Series with dates as index and outlier values.
|
2730
|
+
For OpenFrame: DataFrame with dates as index and outlier values for
|
2731
|
+
each column.
|
2732
|
+
Returns empty Series/DataFrame if no outliers found.
|
2733
|
+
|
2734
|
+
"""
|
2735
|
+
earlier, later = self.calc_range(
|
2736
|
+
months_offset=months_from_last,
|
2737
|
+
from_dt=from_date,
|
2738
|
+
to_dt=to_date,
|
2739
|
+
)
|
2740
|
+
|
2741
|
+
# Get the data for the specified date range
|
2742
|
+
data = self.tsdf.loc[cast("Timestamp", earlier) : cast("Timestamp", later)]
|
2743
|
+
|
2744
|
+
# Calculate z-scores for each column
|
2745
|
+
z_scores = (data - data.mean()) / data.std()
|
2746
|
+
|
2747
|
+
# Find outliers where |z-score| > threshold
|
2748
|
+
outliers_mask = z_scores.abs() > threshold
|
2749
|
+
|
2750
|
+
if self.tsdf.shape[1] == 1:
|
2751
|
+
# OpenTimeSeries case - return Series
|
2752
|
+
outlier_values = data[outliers_mask].iloc[:, 0].dropna()
|
2753
|
+
return Series(
|
2754
|
+
data=outlier_values.values,
|
2755
|
+
index=outlier_values.index,
|
2756
|
+
name="Outliers",
|
2757
|
+
dtype="float64",
|
2758
|
+
)
|
2759
|
+
# OpenFrame case - return DataFrame
|
2760
|
+
outlier_df = data[outliers_mask].dropna(how="all")
|
2761
|
+
return DataFrame(
|
2762
|
+
data=outlier_df,
|
2763
|
+
dtype="float64",
|
2764
|
+
)
|
openseries/frame.py
CHANGED
@@ -60,14 +60,19 @@ from .owntypes import (
|
|
60
60
|
LiteralPandasReindexMethod,
|
61
61
|
LiteralPortfolioWeightings,
|
62
62
|
LiteralTrunc,
|
63
|
+
MaxDiversificationNaNError,
|
64
|
+
MaxDiversificationNegativeWeightsError,
|
63
65
|
MergingResultedInEmptyError,
|
64
66
|
MixedValuetypesError,
|
67
|
+
MultipleCurrenciesError,
|
65
68
|
NoWeightsError,
|
66
69
|
OpenFramePropertiesList,
|
70
|
+
PortfolioItemsNotWithinFrameError,
|
67
71
|
RatioInputError,
|
68
72
|
ResampleDataLossError,
|
69
73
|
Self,
|
70
74
|
ValueType,
|
75
|
+
WeightsNotProvidedError,
|
71
76
|
)
|
72
77
|
from .series import OpenTimeSeries
|
73
78
|
|
@@ -1434,12 +1439,49 @@ class OpenFrame(_CommonModel[SeriesFloat]):
|
|
1434
1439
|
corr_matrix = corrcoef(returns.T)
|
1435
1440
|
corr_matrix[isinf(corr_matrix)] = nan
|
1436
1441
|
corr_matrix[isnan(corr_matrix)] = nan
|
1442
|
+
|
1443
|
+
msga = (
|
1444
|
+
"max_div weight strategy failed: "
|
1445
|
+
"correlation matrix contains NaN values"
|
1446
|
+
)
|
1447
|
+
if isnan(corr_matrix).any():
|
1448
|
+
raise MaxDiversificationNaNError(msga)
|
1449
|
+
|
1437
1450
|
try:
|
1438
1451
|
inv_corr_sum = linalg.inv(corr_matrix).sum(axis=1)
|
1452
|
+
|
1453
|
+
msgb = (
|
1454
|
+
"max_div weight strategy failed: "
|
1455
|
+
"inverse correlation matrix sum contains NaN values"
|
1456
|
+
)
|
1457
|
+
if isnan(inv_corr_sum).any():
|
1458
|
+
raise MaxDiversificationNaNError(msgb)
|
1459
|
+
|
1439
1460
|
self.weights = list(divide(inv_corr_sum, inv_corr_sum.sum()))
|
1440
|
-
|
1441
|
-
|
1442
|
-
|
1461
|
+
|
1462
|
+
msgc = (
|
1463
|
+
"max_div weight strategy failed: "
|
1464
|
+
"final weights contain NaN values"
|
1465
|
+
)
|
1466
|
+
if any( # pragma: no cover
|
1467
|
+
isnan(weight) for weight in self.weights
|
1468
|
+
):
|
1469
|
+
raise MaxDiversificationNaNError(msgc)
|
1470
|
+
|
1471
|
+
msgd = (
|
1472
|
+
"max_div weight strategy failed: negative weights detected"
|
1473
|
+
f" - weights: {[round(w, 6) for w in self.weights]}"
|
1474
|
+
)
|
1475
|
+
if any(weight < 0 for weight in self.weights):
|
1476
|
+
raise MaxDiversificationNegativeWeightsError(msgd)
|
1477
|
+
|
1478
|
+
except linalg.LinAlgError as e:
|
1479
|
+
msge = (
|
1480
|
+
"max_div weight strategy failed: "
|
1481
|
+
f"correlation matrix is singular - {e!s}"
|
1482
|
+
)
|
1483
|
+
raise MaxDiversificationNaNError(msge) from e
|
1484
|
+
elif weight_strat == "min_vol_overweight":
|
1443
1485
|
vols = std(returns, axis=0, ddof=1)
|
1444
1486
|
min_vol_idx = vols.argmin()
|
1445
1487
|
min_vol_weight = 0.6
|
@@ -1693,3 +1735,271 @@ class OpenFrame(_CommonModel[SeriesFloat]):
|
|
1693
1735
|
result = DataFrame(data=output, index=indx, columns=[dependent_column[0]])
|
1694
1736
|
|
1695
1737
|
return result, predictions.to_cumret()
|
1738
|
+
|
1739
|
+
def rebalanced_portfolio(
|
1740
|
+
self: Self,
|
1741
|
+
name: str,
|
1742
|
+
items: list[str] | None = None,
|
1743
|
+
bal_weights: list[float] | None = None,
|
1744
|
+
frequency: int = 1,
|
1745
|
+
cash_index: OpenTimeSeries | None = None,
|
1746
|
+
*,
|
1747
|
+
equal_weights: bool = False,
|
1748
|
+
drop_extras: bool = True,
|
1749
|
+
) -> OpenFrame:
|
1750
|
+
"""Create a rebalanced portfolio from the OpenFrame constituents.
|
1751
|
+
|
1752
|
+
Parameters
|
1753
|
+
----------
|
1754
|
+
name: str
|
1755
|
+
Name of the portfolio
|
1756
|
+
items: list[str], optional
|
1757
|
+
List of items to include in the portfolio. If None, uses all items.
|
1758
|
+
bal_weights: list[float], optional
|
1759
|
+
List of weights for rebalancing. If None, uses frame weights.
|
1760
|
+
frequency: int, default: 1
|
1761
|
+
Rebalancing frequency
|
1762
|
+
cash_index: OpenTimeSeries, optional
|
1763
|
+
Cash index series for cash component
|
1764
|
+
equal_weights: bool, default: False
|
1765
|
+
If True, use equal weights for all items
|
1766
|
+
drop_extras: bool, default: True
|
1767
|
+
If True, only return TWR series; if False, return all details
|
1768
|
+
|
1769
|
+
Returns:
|
1770
|
+
--------
|
1771
|
+
OpenFrame
|
1772
|
+
OpenFrame containing the rebalanced portfolio
|
1773
|
+
|
1774
|
+
"""
|
1775
|
+
if bal_weights is None and not equal_weights:
|
1776
|
+
if self.weights is None:
|
1777
|
+
msg = "Weights must be provided."
|
1778
|
+
raise WeightsNotProvidedError(msg)
|
1779
|
+
bal_weights = list(self.weights)
|
1780
|
+
|
1781
|
+
if items is None:
|
1782
|
+
items = list(self.columns_lvl_zero)
|
1783
|
+
else:
|
1784
|
+
msg = "Items must be passed as list."
|
1785
|
+
if not isinstance(items, list):
|
1786
|
+
raise TypeError(msg)
|
1787
|
+
if not items:
|
1788
|
+
msg = "Items for portfolio must be within SeriesFrame items."
|
1789
|
+
raise PortfolioItemsNotWithinFrameError(msg)
|
1790
|
+
if not set(items) <= set(self.columns_lvl_zero):
|
1791
|
+
msg = "Items for portfolio must be within SeriesFrame items."
|
1792
|
+
raise PortfolioItemsNotWithinFrameError(msg)
|
1793
|
+
|
1794
|
+
if equal_weights:
|
1795
|
+
bal_weights = [1 / len(items)] * len(items)
|
1796
|
+
|
1797
|
+
if cash_index:
|
1798
|
+
cash_index.tsdf = cash_index.tsdf.reindex(self.tsdf.index)
|
1799
|
+
cash_values: list[float] = cast(
|
1800
|
+
"list[float]", cash_index.tsdf.iloc[:, 0].to_numpy().tolist()
|
1801
|
+
)
|
1802
|
+
else:
|
1803
|
+
cash_values = [1.0] * self.length
|
1804
|
+
|
1805
|
+
if self.tsdf.isna().to_numpy().any():
|
1806
|
+
self.value_nan_handle()
|
1807
|
+
|
1808
|
+
ccies = list({serie.currency for serie in self.constituents})
|
1809
|
+
|
1810
|
+
if len(ccies) != 1:
|
1811
|
+
msg = "Items for portfolio must be denominated in same currency."
|
1812
|
+
raise MultipleCurrenciesError(msg)
|
1813
|
+
|
1814
|
+
currency = ccies[0]
|
1815
|
+
|
1816
|
+
instruments = [*items, "cash", name]
|
1817
|
+
subheaders = [
|
1818
|
+
ValueType.PRICE,
|
1819
|
+
"buysell_qty",
|
1820
|
+
"position",
|
1821
|
+
"value",
|
1822
|
+
"twr",
|
1823
|
+
"settle",
|
1824
|
+
]
|
1825
|
+
|
1826
|
+
output = {
|
1827
|
+
item: {
|
1828
|
+
ValueType.PRICE: [],
|
1829
|
+
"buysell_qty": [0.0] * self.length,
|
1830
|
+
"position": [0.0] * self.length,
|
1831
|
+
"value": [0.0] * self.length,
|
1832
|
+
"twr": [0.0] * self.length,
|
1833
|
+
"settle": [0.0] * self.length,
|
1834
|
+
}
|
1835
|
+
for item in items
|
1836
|
+
}
|
1837
|
+
output.update(
|
1838
|
+
{
|
1839
|
+
"cash": {
|
1840
|
+
ValueType.PRICE: cash_values,
|
1841
|
+
"buysell_qty": [0.0] * self.length,
|
1842
|
+
"position": [0.0] * self.length,
|
1843
|
+
"value": [0.0] * self.length,
|
1844
|
+
"twr": [0.0] * self.length,
|
1845
|
+
"settle": [0.0] * self.length,
|
1846
|
+
},
|
1847
|
+
name: {
|
1848
|
+
ValueType.PRICE: [1.0] + [0.0] * (self.length - 1),
|
1849
|
+
"buysell_qty": [-1.0] + [0.0] * (self.length - 1),
|
1850
|
+
"position": [-1.0] + [0.0] * (self.length - 1),
|
1851
|
+
"value": [-1.0] + [0.0] * (self.length - 1),
|
1852
|
+
"twr": [1.0] + [0.0] * (self.length - 1),
|
1853
|
+
"settle": [1.0] + [0.0] * (self.length - 1),
|
1854
|
+
},
|
1855
|
+
},
|
1856
|
+
)
|
1857
|
+
|
1858
|
+
for item, weight in zip(items, cast("list[float]", bal_weights), strict=False):
|
1859
|
+
output[item][ValueType.PRICE] = cast(
|
1860
|
+
"list[float]", self.tsdf[(item, ValueType.PRICE)].to_numpy().tolist()
|
1861
|
+
)
|
1862
|
+
output[item]["buysell_qty"][0] = (
|
1863
|
+
weight / self.tsdf[(item, ValueType.PRICE)].iloc[0]
|
1864
|
+
)
|
1865
|
+
output[item]["position"][0] = output[item]["buysell_qty"][0]
|
1866
|
+
output[item]["value"][0] = (
|
1867
|
+
output[item]["position"][0] * output[item][ValueType.PRICE][0]
|
1868
|
+
)
|
1869
|
+
output[item]["settle"][0] = (
|
1870
|
+
-output[item]["buysell_qty"][0] * output[item][ValueType.PRICE][0]
|
1871
|
+
)
|
1872
|
+
output["cash"]["buysell_qty"][0] += output[item]["settle"][0]
|
1873
|
+
output[item]["twr"][0] = (
|
1874
|
+
output[item]["value"][0] / -output[item]["settle"][0]
|
1875
|
+
)
|
1876
|
+
|
1877
|
+
output["cash"]["position"][0] = (
|
1878
|
+
output["cash"]["buysell_qty"][0] + output[name]["settle"][0]
|
1879
|
+
)
|
1880
|
+
output["cash"]["settle"][0] = -output["cash"]["position"][0]
|
1881
|
+
|
1882
|
+
counter = 1
|
1883
|
+
for day in range(1, self.length):
|
1884
|
+
portfolio_value = 0.0
|
1885
|
+
settle_value = 0.0
|
1886
|
+
if day == frequency * counter:
|
1887
|
+
for item, weight in zip(
|
1888
|
+
items, cast("list[float]", bal_weights), strict=False
|
1889
|
+
):
|
1890
|
+
output[item]["buysell_qty"][day] = (
|
1891
|
+
weight
|
1892
|
+
- output[item]["value"][day - 1]
|
1893
|
+
/ -output[name]["value"][day - 1]
|
1894
|
+
) / output[item][ValueType.PRICE][day]
|
1895
|
+
output[item]["position"][day] = (
|
1896
|
+
output[item]["position"][day - 1]
|
1897
|
+
+ output[item]["buysell_qty"][day]
|
1898
|
+
)
|
1899
|
+
output[item]["value"][day] = (
|
1900
|
+
output[item]["position"][day]
|
1901
|
+
* output[item][ValueType.PRICE][day]
|
1902
|
+
)
|
1903
|
+
portfolio_value += output[item]["value"][day]
|
1904
|
+
output[item]["twr"][day] = (
|
1905
|
+
output[item]["value"][day]
|
1906
|
+
/ (
|
1907
|
+
output[item]["value"][day - 1]
|
1908
|
+
- output[item]["settle"][day]
|
1909
|
+
)
|
1910
|
+
* output[item]["twr"][day - 1]
|
1911
|
+
)
|
1912
|
+
output[item]["settle"][day] = (
|
1913
|
+
-output[item]["buysell_qty"][day]
|
1914
|
+
* output[item][ValueType.PRICE][day]
|
1915
|
+
)
|
1916
|
+
settle_value += output[item]["settle"][day]
|
1917
|
+
counter += 1
|
1918
|
+
else:
|
1919
|
+
for item in items:
|
1920
|
+
output[item]["position"][day] = output[item]["position"][day - 1]
|
1921
|
+
output[item]["value"][day] = (
|
1922
|
+
output[item]["position"][day]
|
1923
|
+
* output[item][ValueType.PRICE][day]
|
1924
|
+
)
|
1925
|
+
portfolio_value += output[item]["value"][day]
|
1926
|
+
output[item]["twr"][day] = (
|
1927
|
+
output[item]["value"][day]
|
1928
|
+
/ (
|
1929
|
+
output[item]["value"][day - 1]
|
1930
|
+
- output[item]["settle"][day]
|
1931
|
+
)
|
1932
|
+
* output[item]["twr"][day - 1]
|
1933
|
+
)
|
1934
|
+
output["cash"]["buysell_qty"][day] = settle_value
|
1935
|
+
output["cash"]["position"][day] = (
|
1936
|
+
output["cash"]["position"][day - 1]
|
1937
|
+
* output["cash"][ValueType.PRICE][day]
|
1938
|
+
/ output["cash"][ValueType.PRICE][day - 1]
|
1939
|
+
+ output["cash"]["buysell_qty"][day]
|
1940
|
+
)
|
1941
|
+
output["cash"]["value"][day] = output["cash"]["position"][day]
|
1942
|
+
portfolio_value += output["cash"]["value"][day]
|
1943
|
+
output[name]["position"][day] = output[name]["position"][day - 1]
|
1944
|
+
output[name]["value"][day] = -portfolio_value
|
1945
|
+
output[name]["twr"][day] = (
|
1946
|
+
output[name]["value"][day] / output[name]["position"][day]
|
1947
|
+
)
|
1948
|
+
output[name][ValueType.PRICE][day] = output[name]["twr"][day]
|
1949
|
+
|
1950
|
+
result = DataFrame()
|
1951
|
+
for outvalue in output.values():
|
1952
|
+
result = concat(
|
1953
|
+
[
|
1954
|
+
result,
|
1955
|
+
DataFrame(
|
1956
|
+
data=outvalue,
|
1957
|
+
index=self.tsdf.index,
|
1958
|
+
),
|
1959
|
+
],
|
1960
|
+
axis="columns",
|
1961
|
+
)
|
1962
|
+
lvlone, lvltwo = [], []
|
1963
|
+
for instr in instruments:
|
1964
|
+
lvlone.extend([instr] * 6)
|
1965
|
+
lvltwo.extend(subheaders)
|
1966
|
+
result.columns = MultiIndex.from_arrays([lvlone, lvltwo])
|
1967
|
+
|
1968
|
+
series = []
|
1969
|
+
if drop_extras:
|
1970
|
+
used_constituents = [
|
1971
|
+
item for item in self.constituents if item.label in items
|
1972
|
+
]
|
1973
|
+
series.extend(
|
1974
|
+
[
|
1975
|
+
OpenTimeSeries.from_df(
|
1976
|
+
dframe=result[(item.label, "twr")],
|
1977
|
+
valuetype=ValueType.PRICE,
|
1978
|
+
baseccy=item.currency,
|
1979
|
+
local_ccy=item.local_ccy,
|
1980
|
+
)
|
1981
|
+
for item in used_constituents
|
1982
|
+
]
|
1983
|
+
)
|
1984
|
+
series.append(
|
1985
|
+
OpenTimeSeries.from_df(
|
1986
|
+
dframe=result[(name, "twr")],
|
1987
|
+
valuetype=ValueType.PRICE,
|
1988
|
+
baseccy=currency,
|
1989
|
+
local_ccy=True,
|
1990
|
+
),
|
1991
|
+
)
|
1992
|
+
else:
|
1993
|
+
series.extend(
|
1994
|
+
[
|
1995
|
+
OpenTimeSeries.from_df(
|
1996
|
+
dframe=result.loc[:, col],
|
1997
|
+
valuetype=ValueType.PRICE,
|
1998
|
+
baseccy=currency,
|
1999
|
+
local_ccy=True,
|
2000
|
+
).set_new_label(f"{col[0]}, {col[1]!s}")
|
2001
|
+
for col in result.columns
|
2002
|
+
]
|
2003
|
+
)
|
2004
|
+
|
2005
|
+
return OpenFrame(series)
|
openseries/owntypes.py
CHANGED
@@ -135,7 +135,9 @@ LiteralPlotlyHistogramHistNorm = Literal[
|
|
135
135
|
"density",
|
136
136
|
"probability density",
|
137
137
|
]
|
138
|
-
LiteralPortfolioWeightings = Literal[
|
138
|
+
LiteralPortfolioWeightings = Literal[
|
139
|
+
"eq_weights", "inv_vol", "max_div", "min_vol_overweight"
|
140
|
+
]
|
139
141
|
LiteralMinimizeMethods = Literal[
|
140
142
|
"SLSQP",
|
141
143
|
"Nelder-Mead",
|
@@ -374,3 +376,23 @@ class PropertiesInputValidationError(Exception):
|
|
374
376
|
|
375
377
|
class ResampleDataLossError(Exception):
|
376
378
|
"""Raised when user attempts to run resample_to_business_period_ends on returns."""
|
379
|
+
|
380
|
+
|
381
|
+
class WeightsNotProvidedError(Exception):
|
382
|
+
"""Raised when weights are not provided."""
|
383
|
+
|
384
|
+
|
385
|
+
class MultipleCurrenciesError(Exception):
|
386
|
+
"""Raised when multiple currencies are provided."""
|
387
|
+
|
388
|
+
|
389
|
+
class PortfolioItemsNotWithinFrameError(Exception):
|
390
|
+
"""Raised when portfolio items are not within frame."""
|
391
|
+
|
392
|
+
|
393
|
+
class MaxDiversificationNaNError(Exception):
|
394
|
+
"""Raised when max_div weight strategy produces NaN values."""
|
395
|
+
|
396
|
+
|
397
|
+
class MaxDiversificationNegativeWeightsError(Exception):
|
398
|
+
"""Raised when max_div weight strategy produces negative weights."""
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: openseries
|
3
|
-
Version: 2.0.
|
3
|
+
Version: 2.0.1
|
4
4
|
Summary: Tools for analyzing financial timeseries.
|
5
5
|
License: # BSD 3-Clause License
|
6
6
|
|
@@ -35,11 +35,12 @@ Author: Martin Karrin
|
|
35
35
|
Author-email: martin.karrin@captor.se
|
36
36
|
Maintainer: Martin Karrin
|
37
37
|
Maintainer-email: martin.karrin@captor.se
|
38
|
-
Requires-Python: >=3.10,<3.
|
38
|
+
Requires-Python: >=3.10,<3.15
|
39
39
|
Classifier: Programming Language :: Python :: 3.10
|
40
40
|
Classifier: Programming Language :: Python :: 3.11
|
41
41
|
Classifier: Programming Language :: Python :: 3.12
|
42
42
|
Classifier: Programming Language :: Python :: 3.13
|
43
|
+
Classifier: Programming Language :: Python :: 3.14
|
43
44
|
Classifier: License :: OSI Approved :: BSD License
|
44
45
|
Classifier: Intended Audience :: Financial and Insurance Industry
|
45
46
|
Classifier: Topic :: Office/Business :: Financial :: Investment
|
@@ -77,6 +78,7 @@ Description-Content-Type: text/markdown
|
|
77
78
|
[](https://www.python.org/)
|
78
79
|
[](https://github.com/CaptorAB/openseries/actions/workflows/test.yml)
|
79
80
|
[](https://codecov.io/gh/CaptorAB/openseries/branch/master)
|
81
|
+
[](https://openseries.readthedocs.io/en/latest/?badge=latest)
|
80
82
|
[](https://python-poetry.org/)
|
81
83
|
[](https://beta.ruff.rs/docs/)
|
82
84
|
[](https://github.com/CaptorAB/openseries/blob/master/LICENSE.md)
|
@@ -1,10 +1,10 @@
|
|
1
|
-
openseries/__init__.py,sha256=
|
2
|
-
openseries/_common_model.py,sha256=
|
1
|
+
openseries/__init__.py,sha256=vkJZmQaReCry7g6f8plQU5RaP1LBwVKe3DoD9ekF3Sc,1127
|
2
|
+
openseries/_common_model.py,sha256=y-udTW6LI0Q5er-Egzd4IrFoGHfRGSnsjHJuCu_YTIU,91628
|
3
3
|
openseries/_risk.py,sha256=YisMnI8DQT0w9n9SQbrvq0ZZqmZHrz7-jhZtngObJRk,2094
|
4
4
|
openseries/datefixer.py,sha256=U1Kc6QdW3UEzAp61NIUALOllyLWb-mJemiL7KLfSAto,15512
|
5
|
-
openseries/frame.py,sha256=
|
5
|
+
openseries/frame.py,sha256=g3X3eVkUptawmj7fb-86i21odIFTX_QvwJEcMU_mtO0,69376
|
6
6
|
openseries/load_plotly.py,sha256=sVssTMzJ2tPPHceCa9OCavI4Mv5BFgSKR6wnTltnqQQ,1997
|
7
|
-
openseries/owntypes.py,sha256=
|
7
|
+
openseries/owntypes.py,sha256=wxyU4d08YrxfEGL_bSI9M6ZVLjipqMLEsq376gVV9lk,10001
|
8
8
|
openseries/plotly_captor_logo.json,sha256=F5nhMzEyxKywtjvQqMTKgKRCJQYMDIiBgDSxdte8Clo,178
|
9
9
|
openseries/plotly_layouts.json,sha256=MvDEQuiqIhMBXBelXb1sedTOlTPheizv6NZRLeE9YS4,1431
|
10
10
|
openseries/portfoliotools.py,sha256=v7s9-AgJFlvPIbPuPf6J7d0VjP-dDT-rsm086EoqSAE,19073
|
@@ -12,7 +12,7 @@ openseries/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
openseries/report.py,sha256=FYN79MroQfY8DrDSSdsSDBcy6S9PwIS0FyujnqPEQV0,14106
|
13
13
|
openseries/series.py,sha256=Wp5G3EmzsBqABtkIPvFQ0Y4T77RhXM9uzvtJBPFWMrQ,28354
|
14
14
|
openseries/simulation.py,sha256=J58uHuakeIbZ2Pabha-RtsaO-k-MVsRfXSdZexrMAkI,16071
|
15
|
-
openseries-2.0.
|
16
|
-
openseries-2.0.
|
17
|
-
openseries-2.0.
|
18
|
-
openseries-2.0.
|
15
|
+
openseries-2.0.1.dist-info/METADATA,sha256=_YFEzFjSM-xzkD9ayOMVf41bJ2NGk8dLw15E2k9yu9c,6281
|
16
|
+
openseries-2.0.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
17
|
+
openseries-2.0.1.dist-info/licenses/LICENSE.md,sha256=wNupG-KLsG0aTncb_SMNDh1ExtrKXlpxSJ6RC-g-SWs,1516
|
18
|
+
openseries-2.0.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|