From d0da610955e4b28c5cfd8f8766ff807b86ea2d00 Mon Sep 17 00:00:00 2001 From: Kwan Yuet Stephen Ho <3810067+stephenhky@users.noreply.github.com> Date: Thu, 21 Aug 2025 00:34:03 -0400 Subject: [PATCH 1/9] update publish-to-pypi.yaml --- .github/workflow/publish-to-pypi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflow/publish-to-pypi.yaml b/.github/workflow/publish-to-pypi.yaml index 1ca50dc..506e812 100644 --- a/.github/workflow/publish-to-pypi.yaml +++ b/.github/workflow/publish-to-pypi.yaml @@ -2,7 +2,7 @@ name: Publish Python Package to PyPI on: release: - types: [published] + types: [created] jobs: deploy: From 56f5deab79f419a27dd847731b1c1b6a1da3e679 Mon Sep 17 00:00:00 2001 From: Kwan Yuet Stephen Ho <3810067+stephenhky@users.noreply.github.com> Date: Thu, 21 Aug 2025 00:38:04 -0400 Subject: [PATCH 2/9] initiate development of 1.2.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1fe7af9..4b4208e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "finsim" -version = "1.2.0" +version = "1.2.1a1" authors = [ {name = "Kwan Yuet Stephen Ho", email = "stephenhky@yahoo.com.hk"} ] From 023a434286681bd6ebed0ab63368259f7a1a31d9 Mon Sep 17 00:00:00 2001 From: Kwan Yuet Stephen Ho <3810067+stephenhky@users.noreply.github.com> Date: Mon, 29 Sep 2025 18:13:36 -0400 Subject: [PATCH 3/9] remove dependene on `nptyping` --- .github/workflow/tests.yaml | 5 +- pyproject.toml | 3 +- src/finsim/estimate/fit.py | 28 +++++------ src/finsim/estimate/native/pyfit.py | 14 +++--- src/finsim/estimate/native/pyrisk.py | 12 +++-- src/finsim/estimate/risk.py | 18 +++---- src/finsim/portfolio/optimize/metrics.py | 23 ++++----- src/finsim/portfolio/optimize/numerics.py | 27 ++++++----- src/finsim/portfolio/optimize/policy.py | 58 +++++++++++------------ src/finsim/portfolio/portfolio.py | 13 ++--- src/finsim/simulation/stock.py | 16 +++++-- 11 files changed, 116 insertions(+), 101 deletions(-) diff --git a/.github/workflow/tests.yaml b/.github/workflow/tests.yaml index 21eb372..27ec8be 100644 --- a/.github/workflow/tests.yaml +++ b/.github/workflow/tests.yaml @@ -2,9 +2,10 @@ name: Python package CI on: push: - branches: [ master ] + branches: + - '**' pull_request: - branches: [ master ] + branches: [ master. develop ] jobs: build_and_test: diff --git a/pyproject.toml b/pyproject.toml index 4b4208e..012638a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "finsim" -version = "1.2.1a1" +version = "1.2.1a2" authors = [ {name = "Kwan Yuet Stephen Ho", email = "stephenhky@yahoo.com.hk"} ] @@ -37,7 +37,6 @@ dependencies = [ "openpyxl>=3.1.0", "numba", "typing-extensions", - "nptyping", "pydantic" ] diff --git a/src/finsim/estimate/fit.py b/src/finsim/estimate/fit.py index ea90da5..64f390a 100644 --- a/src/finsim/estimate/fit.py +++ b/src/finsim/estimate/fit.py @@ -1,9 +1,9 @@ -from typing import Literal +from typing import Literal, Annotated from itertools import product import numpy as np -from nptyping import NDArray, Shape, Float, Datetime64 +from numpy.typing import NDArray from .constants import dividing_factors_dict from .native.pyfit import python_fit_BlackScholesMerton_model, python_fit_multivariate_BlackScholesMerton_model @@ -12,8 +12,8 @@ # Note: always round-off to seconds first, but flexible about the unit to be used. def fit_BlackScholesMerton_model( - timestamps: NDArray[Shape["*"], Datetime64], - prices: NDArray[Shape["*"], Float], + timestamps: Annotated[NDArray[np.datetime64], Literal["1D array"]], + prices: Annotated[NDArray[np.float64], Literal["1D array"]], unit: Literal['second', 'minute', 'hour', 'day', 'year']='year' ) -> tuple[float, float]: """Fit a Black-Scholes-Merton model to price data to estimate rate of return and volatility. @@ -46,10 +46,10 @@ def fit_BlackScholesMerton_model( def fit_multivariate_BlackScholesMerton_model( - timestamps: NDArray[Shape["*"], Datetime64], - multiprices: NDArray[Shape["*, *"], Float], + timestamps: Annotated[NDArray[np.datetime64], Literal["1D array"]], + multiprices: Annotated[NDArray[np.float64], Literal["1D array"]], unit: Literal['second', 'minute', 'hour', 'day', 'year']='year', -) -> tuple[NDArray[Shape["*"], Float], NDArray[Shape["*, *"], Float]]: +) -> tuple[Annotated[NDArray[np.float64], Literal["1D array"]], Annotated[NDArray[np.float64], Literal["2D array"]]]: """Fit a multivariate Black-Scholes-Merton model to price data for multiple assets. This function estimates the parameters of the multivariate Black-Scholes-Merton model, @@ -82,9 +82,9 @@ def fit_multivariate_BlackScholesMerton_model( ######## routines below are for time-weighted portfolio building def fit_timeweighted_BlackScholesMerton_model( - timestamps: NDArray[Shape["*"], Datetime64], - prices: NDArray[Shape["*"], Float], - weights: NDArray[Shape["*"], Float], + timestamps: Annotated[NDArray[np.datetime64], Literal["1D array"]], + prices: Annotated[NDArray[np.float64], Literal["1D array"]], + weights: Annotated[NDArray[np.float64], Literal["1D array"]], unit: Literal['second', 'minute', 'hour', 'day', 'year']='year' ) -> tuple[float, float]: """Fit a time-weighted Black-Scholes-Merton model to price data. @@ -120,11 +120,11 @@ def fit_timeweighted_BlackScholesMerton_model( def fit_timeweighted_multivariate_BlackScholesMerton_model( - timestamps: NDArray[Shape["*"], Datetime64], - multiprices: NDArray[Shape["*, *"], Float], - weights: NDArray[Shape["*"], Float], + timestamps: Annotated[NDArray[np.datetime64], Literal["1D array"]], + multiprices: Annotated[NDArray[np.float64], Literal["2D array"]], + weights: Annotated[NDArray[np.float64], Literal["1D array"]], unit: Literal['second', 'minute', 'hour', 'day', 'year']='year' -) -> tuple[NDArray[Shape["*"], Float], NDArray[Shape["*, *"], Float]]: +) -> tuple[Annotated[NDArray[np.float64], Literal["1D array"]], Annotated[NDArray[np.float64], Literal["2D array"]]]: """Fit a time-weighted multivariate Black-Scholes-Merton model to price data for multiple assets. This function estimates the parameters of the multivariate Black-Scholes-Merton model diff --git a/src/finsim/estimate/native/pyfit.py b/src/finsim/estimate/native/pyfit.py index 83367ab..c72c53b 100644 --- a/src/finsim/estimate/native/pyfit.py +++ b/src/finsim/estimate/native/pyfit.py @@ -1,13 +1,15 @@ +from typing import Literal, Annotated + import numpy as np +from numpy.typing import NDArray import numba as nb -from nptyping import NDArray, Shape, Float @nb.njit def python_fit_BlackScholesMerton_model( - ts: NDArray[Shape["*"], Float], - prices: NDArray[Shape["*"], Float] + ts: Annotated[NDArray[np.float64], Literal["1D array"]], + prices: Annotated[NDArray[np.float64], Literal["1D array"]] ) -> tuple[float, float]: """Fit a Black-Scholes-Merton model to price data using Python implementation. @@ -35,9 +37,9 @@ def python_fit_BlackScholesMerton_model( @nb.njit def python_fit_multivariate_BlackScholesMerton_model( - ts: NDArray[Shape["*"], Float], - multiprices: NDArray[Shape["*, *"], Float] -) -> tuple[NDArray[Shape["*"], Float], NDArray[Shape["*, *"], Float]]: + ts: Annotated[NDArray[np.float64], Literal["1D array"]], + multiprices: Annotated[NDArray[np.float64], Literal["2D array"]] +) -> tuple[Annotated[NDArray[np.float64], Literal["1D array"]], Annotated[NDArray[np.float64], Literal["2D array"]]]: """Fit a multivariate Black-Scholes-Merton model to price data for multiple assets. This function estimates the parameters of the multivariate Black-Scholes-Merton model, diff --git a/src/finsim/estimate/native/pyrisk.py b/src/finsim/estimate/native/pyrisk.py index 72541ea..67c82da 100644 --- a/src/finsim/estimate/native/pyrisk.py +++ b/src/finsim/estimate/native/pyrisk.py @@ -1,13 +1,15 @@ +from typing import Literal, Annotated + import numpy as np +from numpy.typing import NDArray import numba as nb -from nptyping import NDArray, Shape, Float @nb.njit def python_estimate_downside_risk( - ts: NDArray[Shape["*"], Float], - prices: NDArray[Shape["*"], Float], + ts: Annotated[NDArray[np.float64], Literal["1D array"]], + prices: Annotated[NDArray[np.float64], Literal["1D array"]], target_return: float ) -> float: """Estimate the downside risk of an asset based on historical price data. @@ -35,8 +37,8 @@ def python_estimate_downside_risk( @nb.njit def python_estimate_upside_risk( - ts: NDArray[Shape["*"], Float], - prices: NDArray[Shape["*"], Float], + ts: Annotated[NDArray[np.float64], Literal["1D array"]], + prices: Annotated[NDArray[np.float64], Literal["1D array"]], target_return: float ) -> float: """Estimate the upside risk of an asset based on historical price data. diff --git a/src/finsim/estimate/risk.py b/src/finsim/estimate/risk.py index f0c469a..83e1681 100644 --- a/src/finsim/estimate/risk.py +++ b/src/finsim/estimate/risk.py @@ -1,17 +1,17 @@ -from typing import Literal +from typing import Literal, Annotated import numpy as np +from numpy.typing import NDArray from scipy import stats -from nptyping import NDArray, Shape, Float, Datetime64 from .native.pyrisk import python_estimate_downside_risk, python_estimate_upside_risk from .constants import dividing_factors_dict def estimate_downside_risk( - timestamps: NDArray[Shape["*"], Datetime64], - prices: NDArray[Shape["*"], Float], + timestamps: Annotated[NDArray[np.datetime64], Literal["1D array"]], + prices: Annotated[NDArray[np.float64], Literal["1D array"]], target_return: float, unit: Literal['second', 'minute', 'hour', 'day', 'year']='year' ) -> float: @@ -43,8 +43,8 @@ def estimate_downside_risk( def estimate_upside_risk( - timestamps: NDArray[Shape["*"], Datetime64], - prices: NDArray[Shape["*"], Float], + timestamps: Annotated[NDArray[np.datetime64], Literal["1D array"]], + prices: Annotated[NDArray[np.float64], Literal["1D array"]], target_return: float, unit: Literal['second', 'minute', 'hour', 'day', 'year'] = 'year' ) -> float: @@ -76,9 +76,9 @@ def estimate_upside_risk( def estimate_beta( - timestamps: NDArray[Shape["*"], Datetime64], - prices: NDArray[Shape["*"], Float], - market_prices: NDArray[Shape["*"], Float], + timestamps: Annotated[NDArray[np.datetime64], Literal["1D array"]], + prices: Annotated[NDArray[np.float64], Literal["1D array"]], + market_prices: Annotated[NDArray[np.float64], Literal["1D array"]], unit: Literal['second', 'minute', 'hour', 'day', 'year']='year' ) -> float: """Estimate the beta coefficient of an asset relative to market performance. diff --git a/src/finsim/portfolio/optimize/metrics.py b/src/finsim/portfolio/optimize/metrics.py index 657cd62..fca3385 100644 --- a/src/finsim/portfolio/optimize/metrics.py +++ b/src/finsim/portfolio/optimize/metrics.py @@ -1,15 +1,16 @@ -from typing import Literal +from typing import Literal, Annotated -from nptyping import NDArray, Shape, Float +import numpy as np +from numpy.typing import NDArray from .native.pymetrics import python_sharpe_ratio, python_mpt_costfunction, python_mpt_entropy_costfunction def sharpe_ratio( - weights: NDArray[Shape["*"], Float], - r: NDArray[Shape["*"], Float], - cov: NDArray[Shape["*, *"], Float], + weights: Annotated[NDArray[np.float64], Literal["1D array"]], + r: Annotated[NDArray[np.float64], Literal["1D array"]], + cov: Annotated[NDArray[np.float64], Literal["2D array"]], rf: float ) -> float: """Calculate the Sharpe ratio for a portfolio. @@ -27,9 +28,9 @@ def sharpe_ratio( def mpt_costfunction( - weights: NDArray[Shape["*"], Float], - r: NDArray[Shape["*"], Float], - cov: NDArray[Shape["*, *"], Float], + weights: Annotated[NDArray[np.float64], Literal["1D array"]], + r: Annotated[NDArray[np.float64], Literal["1D array"]], + cov: Annotated[NDArray[np.float64], Literal["2D array"]], rf: float, lamb: float, V0: float=10. @@ -52,9 +53,9 @@ def mpt_costfunction( def mpt_entropy_costfunction( - weights: NDArray[Shape["*"], Float], - r: NDArray[Shape["*"], Float], - cov: NDArray[Shape["*, *"], Float], + weights: Annotated[NDArray[np.float64], Literal["1D array"]], + r: Annotated[NDArray[np.float64], Literal["1D array"]], + cov: Annotated[NDArray[np.float64], Literal["2D array"]], rf: float, lamb0: float, lamb1: float, diff --git a/src/finsim/portfolio/optimize/numerics.py b/src/finsim/portfolio/optimize/numerics.py index 7d102e7..0b20f8b 100644 --- a/src/finsim/portfolio/optimize/numerics.py +++ b/src/finsim/portfolio/optimize/numerics.py @@ -3,15 +3,15 @@ from functools import partial from itertools import product from datetime import datetime -from typing import Optional +from typing import Optional, Literal, Annotated from os import PathLike import numpy as np +from numpy.typing import NDArray import pandas as pd from scipy.optimize import minimize, OptimizeResult from tqdm import tqdm import numba as nb -from nptyping import NDArray, Shape, Float from .metrics import sharpe_ratio, mpt_costfunction, mpt_entropy_costfunction from ..helper import align_timestamps_stock_dataframes @@ -23,7 +23,7 @@ @nb.njit(nb.float64(nb.float64[:], nb.float64, nb.int32)) def getarrayelementminusminvalue( - array: NDArray[Shape["*"], Float], + array: Annotated[NDArray[np.float64], Literal["1D array"]], minvalue: float, index: int ) -> float: @@ -41,7 +41,10 @@ def getarrayelementminusminvalue( @nb.njit(nb.float64(nb.float64[:], nb.float64)) -def checksumarray(array: NDArray[Shape["*"], Float], total: float) -> float: +def checksumarray( + array: Annotated[NDArray[np.float64], Literal["1D array"]], + total: float +) -> float: """Calculate the difference between a total and the sum of an array. Args: @@ -55,8 +58,8 @@ def checksumarray(array: NDArray[Shape["*"], Float], total: float) -> float: def optimized_portfolio_on_sharperatio( - r: NDArray[Shape["*"], Float], - cov: NDArray[Shape["*, *"], Float], + r: Annotated[NDArray[np.float64], Literal["1D array"]], + cov: Annotated[NDArray[np.float64], Literal["2D array"]], rf: float, minweight: float=0. ) -> OptimizeResult: @@ -91,8 +94,8 @@ def optimized_portfolio_on_sharperatio( def optimized_portfolio_mpt_costfunction( - r: NDArray[Shape["*"], Float], - cov: NDArray[Shape["*, *"], Float], + r: Annotated[NDArray[np.float64], Literal["1D array"]], + cov: Annotated[NDArray[np.float64], Literal["2D array"]], rf: float, lamb: float, V0: float=10. @@ -129,8 +132,8 @@ def optimized_portfolio_mpt_costfunction( def optimized_portfolio_mpt_entropy_costfunction( - r: NDArray[Shape["*"], Float], - cov: NDArray[Shape["*, *"], Float], + r: Annotated[NDArray[np.float64], Literal["1D array"]], + cov: Annotated[NDArray[np.float64], Literal["2D array"]], rf: float, lamb0: float, lamb1: float, @@ -208,7 +211,7 @@ def get_BlackScholesMerton_stocks_estimation( progressbar: bool=True, cacheddir: Optional[PathLike | str]=None, include_dividends: bool=False -) -> tuple[NDArray[Shape["*"], Float], NDArray[Shape["*, *"], Float]]: +) -> tuple[Annotated[NDArray[np.float64], Literal["1D array"]], Annotated[NDArray[np.float64], Literal["2D array"]]]: """Get Black-Scholes-Merton model estimations for a list of stocks. Args: @@ -289,7 +292,7 @@ def get_stocks_timeweighted_estimation( progressbar: bool=True, cacheddir: Optional[PathLike | str]=None, include_dividends: bool=False -) -> tuple[NDArray[Shape["*"], Float], NDArray[Shape["*, *"], Float]]: +) -> tuple[Annotated[NDArray[np.float64], Literal["1D array"]], Annotated[NDArray[np.float64], Literal["2D array"]]]: """Get time-weighted estimations for a list of stocks. Args: diff --git a/src/finsim/portfolio/optimize/policy.py b/src/finsim/portfolio/optimize/policy.py index 1954f22..37e21dc 100644 --- a/src/finsim/portfolio/optimize/policy.py +++ b/src/finsim/portfolio/optimize/policy.py @@ -1,18 +1,18 @@ from itertools import product from abc import ABC, abstractmethod -from typing import Any, Optional +from typing import Any, Optional, Literal, Annotated import numpy as np +from numpy.typing import NDArray import pandas as pd import numba as nb -from nptyping import NDArray, Shape, Float from .numerics import optimized_portfolio_on_sharperatio, optimized_portfolio_mpt_costfunction, optimized_portfolio_mpt_entropy_costfunction @nb.njit -def mat_to_list(mat: NDArray[Shape["*, *"], Float]) -> list[list[float]]: +def mat_to_list(mat: Annotated[NDArray[np.float64], Literal["2D array"]]) -> list[list[float]]: """Convert a 2D numpy array to a list of lists. Args: @@ -39,8 +39,8 @@ class OptimizedWeightingPolicy(ABC): def __init__( self, rf: float, - r: Optional[NDArray[Shape["*"], Float]]=None, - cov: Optional[NDArray[Shape["*, *"], Float]]=None, + r: Optional[Annotated[NDArray[np.float64], Literal["1D array"]]]=None, + cov: Optional[Annotated[NDArray[np.float64], Literal["2D array"]]]=None, symbols: Optional[list[str]]=None ): """Initialize the OptimizedWeightingPolicy. @@ -67,8 +67,8 @@ def __init__( @abstractmethod def optimize( self, - r: NDArray[Shape["*"], Float], - cov: NDArray[Shape["*, *"], Float], + r: Annotated[NDArray[np.float64], Literal["1D array"]], + cov: Annotated[NDArray[np.float64], Literal["2D array"]], symbols: Optional[list[str]]=None ) -> None: """Optimize the portfolio weights. @@ -91,11 +91,11 @@ def portfolio_symbols(self) -> list[str]: @property @abstractmethod - def weights(self) -> NDArray[Shape["*"], Float]: + def weights(self) -> Annotated[NDArray[np.float64], Literal["1D array"]]: """Get the optimized weights for each asset. Returns: - NDArray[Shape["*"], Float]: Array of optimized weights + Annotated[NDArray[np.float64], Literal["1D array"]]: Array of optimized weights """ raise NotImplemented() @@ -120,11 +120,11 @@ def volatility(self) -> float: raise NotImplemented() @property - def correlation_matrix(self) -> NDArray[Shape["*, *"], Float]: + def correlation_matrix(self) -> Annotated[NDArray[np.float64], Literal["2D array"]]: """Get the correlation matrix of the optimized portfolio. Returns: - NDArray[Shape["*, *"], Float]: Correlation matrix of the portfolio + Annotated[NDArray[np.float64], Literal["2D array"]]: Correlation matrix of the portfolio """ corr = np.zeros(self.cov.shape) for i, j in product(range(self.cov.shape[0]), range(self.cov.shape[1])): @@ -187,8 +187,8 @@ class OptimizedWeightingPolicyUsingMPTSharpeRatio(OptimizedWeightingPolicy): def __init__( self, rf: float, - r: Optional[NDArray[Shape["*"], Float]]=None, - cov: Optional[NDArray[Shape["*, *"], Float]]=None, + r: Optional[Annotated[NDArray[np.float64], Literal["1D array"]]]=None, + cov: Optional[Annotated[NDArray[np.float64], Literal["2D array"]]]=None, symbols: Optional[list[str]]=None, minweight: float=0. ): @@ -208,8 +208,8 @@ def __init__( def optimize( self, - r: NDArray[Shape["*"], Float], - cov: NDArray[Shape["*, *"], Float], + r: Annotated[NDArray[np.float64], Literal["1D array"]], + cov: Annotated[NDArray[np.float64], Literal["2D array"]], symbols: Optional[list[str]]=None ) -> None: """Optimize the portfolio weights using the Sharpe ratio. @@ -232,11 +232,11 @@ def optimize( self.optimized_volatility = np.sqrt(np.sum(sqweights * self.cov)) @property - def weights(self) -> NDArray[Shape["*"], Float]: + def weights(self) -> Annotated[NDArray[np.float64], Literal["1D array"]]: """Get the optimized weights for each asset. Returns: - NDArray[Shape["*"], Float]: Array of optimized weights + Annotated[NDArray[np.float64], Literal["1D array"]]: Array of optimized weights """ return self.optimized_weights @@ -298,8 +298,8 @@ class OptimizedWeightingPolicyUsingMPTCostFunction(OptimizedWeightingPolicy): def __init__( self, rf: float, - r: NDArray[Shape["*"], Float], - cov: NDArray[Shape["*, *"], Float], + r: Annotated[NDArray[np.float64], Literal["1D array"]], + cov: Annotated[NDArray[np.float64], Literal["2D array"]], symbols: Optional[list[str]]=None, lamb: float=0.0, V0: float=10.0 @@ -323,8 +323,8 @@ def __init__( def optimize( self, - r: NDArray[Shape["*"], Float], - cov: NDArray[Shape["*, *"], Float], + r: Annotated[NDArray[np.float64], Literal["1D array"]], + cov: Annotated[NDArray[np.float64], Literal["2D array"]], symbols: Optional[list[str]]=None ) -> None: """Optimize the portfolio weights using the MPT cost function. @@ -348,11 +348,11 @@ def optimize( self.optimized_volatility = np.sqrt(np.sum(sqweights * self.cov)) @property - def weights(self) -> NDArray[Shape["*"], Float]: + def weights(self) -> Annotated[NDArray[np.float64], Literal["1D array"]]: """Get the optimized weights for each asset. Returns: - NDArray[Shape["*"], Float]: Array of optimized weights + Annotated[NDArray[np.float64], Literal["1D array"]]: Array of optimized weights """ return self.optimized_weights @@ -416,8 +416,8 @@ class OptimizedWeightingPolicyUsingMPTEntropyCostFunction(OptimizedWeightingPoli def __init__( self, rf: float, - r: NDArray[Shape["*"], Float]=None, - cov: NDArray[Shape["*, *"], Float]=None, + r: Annotated[NDArray[np.float64], Literal["1D array"]]=None, + cov: Annotated[NDArray[np.float64], Literal["2D array"]]=None, symbols: Optional[list[str]]=None, lamb0: float=0.0, lamb1: float=0.0, @@ -444,8 +444,8 @@ def __init__( def optimize( self, - r: NDArray[Shape["*"], Float], - cov: NDArray[Shape["*, *"], Float], + r: Annotated[NDArray[np.float64], Literal["1D array"]], + cov: Annotated[NDArray[np.float64], Literal["2D array"]], symbols: Optional[list[str]]=None ) -> None: """Optimize the portfolio weights using the MPT entropy cost function. @@ -469,11 +469,11 @@ def optimize( self.optimized_volatility = np.sqrt(np.sum(sqweights * self.cov)) @property - def weights(self) -> NDArray[Shape["*"], Float]: + def weights(self) -> Annotated[NDArray[np.float64], Literal["1D array"]]: """Get the optimized weights for each asset. Returns: - NDArray[Shape["*"], Float]: Array of optimized weights + Annotated[NDArray[np.float64], Literal["1D array"]]: Array of optimized weights """ return self.optimized_weights diff --git a/src/finsim/portfolio/portfolio.py b/src/finsim/portfolio/portfolio.py index 54de67a..10c8482 100644 --- a/src/finsim/portfolio/portfolio.py +++ b/src/finsim/portfolio/portfolio.py @@ -3,7 +3,7 @@ import logging import sys from collections import defaultdict -from typing import Any, Optional +from typing import Any, Optional, Literal, Annotated from os import PathLike from io import TextIOWrapper if sys.version_info < (3, 11): @@ -11,9 +11,10 @@ else: from typing import Self +import numpy as np +from numpy.typing import NDArray from tqdm import tqdm import pandas as pd -from nptyping import NDArray, Shape, Float from ..data.preader import get_yahoofinance_data, get_symbol_closing_price from .optimize.policy import OptimizedWeightingPolicy @@ -364,11 +365,11 @@ def portfolio_symbols(self) -> list[str]: return self.policy.portfolio_symbols @property - def weights(self) -> NDArray[Shape["*"], Float]: + def weights(self) -> Annotated[NDArray[np.float64], Literal["1D array"]]: """Get the optimized weights for each asset. Returns: - NDArray[Shape["*"], Float]: Array of optimized weights + Annotated[NDArray[np.float64], Literal["1D array"]]: Array of optimized weights """ return self.policy.weights @@ -391,11 +392,11 @@ def volatility(self) -> float: return self.policy.volatility @property - def correlation_matrix(self) -> NDArray[Shape["*, *"], Float]: + def correlation_matrix(self) -> Annotated[NDArray[np.float64], Literal["2D array"]]: """Get the correlation matrix of the optimized portfolio. Returns: - NDArray[Shape["*, *"], Float]: Correlation matrix of the portfolio + Annotated[NDArray[np.float64], Literal["2D array"]]: Correlation matrix of the portfolio """ return self.policy.correlation_matrix diff --git a/src/finsim/simulation/stock.py b/src/finsim/simulation/stock.py index f590d5e..a5106f2 100644 --- a/src/finsim/simulation/stock.py +++ b/src/finsim/simulation/stock.py @@ -1,9 +1,10 @@ from abc import ABC, abstractmethod from math import log, exp +from typing import Literal, Annotated import numpy as np -from nptyping import NDArray, Shape, Float +from numpy.typing import NDArray class AbstractStochasticValue(ABC): @@ -46,7 +47,7 @@ def generate_time_series( T: float, dt: float, nbsimulations: int=1 - ) -> NDArray[Shape["*"], Float]: + ) -> Annotated[NDArray[np.float64], Literal["1D array"]]: """Generate a time series of stock prices using the Black-Scholes-Merton model. Args: @@ -88,7 +89,12 @@ def __init__(self, x0: float, theta: float, kappa: float, sigma: float): self.kappa = kappa self.sigma = sigma - def generate_time_series(self, T: float, dt: float, nbsimulations: int=1) -> NDArray[Shape["*"], Float]: + def generate_time_series( + self, + T: float, + dt: float, + nbsimulations: int=1 + ) -> Annotated[NDArray[np.float64], Literal["1D array"]]: """Generate a time series of values using the square root diffusion process. Args: @@ -139,7 +145,7 @@ def __init__(self, S0: float, r: float, v0: float, theta: float, kappa: float, s self.logS0 = log(self.S0) self.rho = np.array([[1., self.rho], [self.rho, 1.]]) - def generate_time_series(self, T: float, dt: float, nbsimulations: int=1) -> NDArray[Shape["*"], Float]: + def generate_time_series(self, T: float, dt: float, nbsimulations: int=1) -> Annotated[NDArray[np.float64], Literal["1D array"]]: """Generate a time series of stock prices using the Heston model. Args: @@ -202,7 +208,7 @@ def __init__(self, S0: float, r: float, sigma: float, mu: float, lamb: float, de self.logS0 = log(self.S0) - def generate_time_series(self, T: float, dt: float, nbsimulations: int=1) -> NDArray[Shape["*"], Float]: + def generate_time_series(self, T: float, dt: float, nbsimulations: int=1) -> Annotated[NDArray[np.float64], Literal["1D array"]]: """Generate a time series of stock prices using the Merton jump-diffusion model. Args: From 331f8f45916dda470762e230e8e0f44929c76df5 Mon Sep 17 00:00:00 2001 From: Kwan Yuet Stephen Ho <3810067+stephenhky@users.noreply.github.com> Date: Mon, 29 Sep 2025 18:20:13 -0400 Subject: [PATCH 4/9] remove dependene on `nptyping` further --- .../portfolio/optimize/native/pymetrics.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/finsim/portfolio/optimize/native/pymetrics.py b/src/finsim/portfolio/optimize/native/pymetrics.py index f52f75f..2c0cb97 100644 --- a/src/finsim/portfolio/optimize/native/pymetrics.py +++ b/src/finsim/portfolio/optimize/native/pymetrics.py @@ -1,14 +1,16 @@ +from typing import Literal, Annotated + import numpy as np +from numpy.typing import NDArray import numba as nb -from nptyping import NDArray, Shape, Float @nb.njit def python_sharpe_ratio( - weights: NDArray[Shape["*"], Float], - r: NDArray[Shape["*"], Float], - cov: NDArray[Shape["*, *"], Float], + weights: Annotated[NDArray[np.float64], Literal["1D array"]], + r: Annotated[NDArray[np.float64], Literal["1D array"]], + cov: Annotated[NDArray[np.float64], Literal["2D array"]], rf: float ) -> float: """Calculate the Sharpe ratio for a portfolio using Python/Numba. @@ -30,9 +32,9 @@ def python_sharpe_ratio( @nb.njit def python_mpt_costfunction( - weights: NDArray[Shape["*"], Float], - r: NDArray[Shape["*"], Float], - cov: NDArray[Shape["*, *"], Float], + weights: Annotated[NDArray[np.float64], Literal["1D array"]], + r: Annotated[NDArray[np.float64], Literal["1D array"]], + cov: Annotated[NDArray[np.float64], Literal["2D array"]], rf: float, lamb: float, V0: float=10. @@ -57,9 +59,9 @@ def python_mpt_costfunction( @nb.njit def python_mpt_entropy_costfunction( - weights: NDArray[Shape["*"], Float], - r: NDArray[Shape["*"], Float], - cov: NDArray[Shape["*, *"], Float], + weights: Annotated[NDArray[np.float64], Literal["1D array"]], + r: Annotated[NDArray[np.float64], Literal["1D array"]], + cov: Annotated[NDArray[np.float64], Literal["2D array"]], rf: float, lamb0: float, lamb1: float, From 382f5fc9363aa22774d5567751ffbda8553ce711 Mon Sep 17 00:00:00 2001 From: Kwan Yuet Stephen Ho <3810067+stephenhky@users.noreply.github.com> Date: Mon, 29 Sep 2025 19:10:59 -0400 Subject: [PATCH 5/9] updated Github workflow --- .github/workflow/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflow/tests.yaml b/.github/workflow/tests.yaml index 27ec8be..d96385d 100644 --- a/.github/workflow/tests.yaml +++ b/.github/workflow/tests.yaml @@ -5,7 +5,7 @@ on: branches: - '**' pull_request: - branches: [ master. develop ] + branches: [ master, develop ] jobs: build_and_test: From b1167025983f553e887ee19075af81b261a16e17 Mon Sep 17 00:00:00 2001 From: Kwan Yuet Stephen Ho <3810067+stephenhky@users.noreply.github.com> Date: Mon, 29 Sep 2025 19:17:16 -0400 Subject: [PATCH 6/9] refactoring --- .github/{workflow => workflows}/publish-to-pypi.yaml | 0 .github/{workflow => workflows}/tests.yaml | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename .github/{workflow => workflows}/publish-to-pypi.yaml (100%) rename .github/{workflow => workflows}/tests.yaml (100%) diff --git a/.github/workflow/publish-to-pypi.yaml b/.github/workflows/publish-to-pypi.yaml similarity index 100% rename from .github/workflow/publish-to-pypi.yaml rename to .github/workflows/publish-to-pypi.yaml diff --git a/.github/workflow/tests.yaml b/.github/workflows/tests.yaml similarity index 100% rename from .github/workflow/tests.yaml rename to .github/workflows/tests.yaml From bad035fdc84fba24b0b4ecaea6b8d9fbe3b9946e Mon Sep 17 00:00:00 2001 From: Kwan Yuet Stephen Ho <3810067+stephenhky@users.noreply.github.com> Date: Mon, 29 Sep 2025 19:21:43 -0400 Subject: [PATCH 7/9] updated Github testing workdlow --- .github/workflows/tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index d96385d..62c9e8b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -24,9 +24,9 @@ jobs: - name: Install dependencies run: | pip install --user --upgrade pip - pip install --user --upgrade setuptools wheel Cython + pip install --user --upgrade setuptools wheel pip cache purge pip install --user . pip install --user .[test] - name: Run tests - run: pytest --doctest-cython + run: pytest From 8288b883f5d7215d424e7b80bcfa889e45b67a4a Mon Sep 17 00:00:00 2001 From: Kwan Yuet Stephen Ho <3810067+stephenhky@users.noreply.github.com> Date: Tue, 30 Sep 2025 12:13:06 -0400 Subject: [PATCH 8/9] updated resource class for CirleCI --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 46e35f3..f7c4bf5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,6 +3,7 @@ version: 2 shared: &shared working_directory: ~/finsim + resource_class: medium+ steps: - checkout From 825745a21c90d09b384760365746b1c138818f9f Mon Sep 17 00:00:00 2001 From: Kwan Yuet Stephen Ho <3810067+stephenhky@users.noreply.github.com> Date: Tue, 30 Sep 2025 12:45:27 -0400 Subject: [PATCH 9/9] release 1.2.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 012638a..6441df7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "finsim" -version = "1.2.1a2" +version = "1.2.1" authors = [ {name = "Kwan Yuet Stephen Ho", email = "stephenhky@yahoo.com.hk"} ]