From 5fa696715b57e3ffad47f47bdb97b23afce6701b Mon Sep 17 00:00:00 2001 From: edtechre Date: Sat, 22 Jun 2024 19:08:45 -0700 Subject: [PATCH 1/5] Update docs copyright year. --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index bddd6b6..ebb23db 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -7,7 +7,7 @@ nitpick_ignore = [("py:class", "type")] project = "PyBroker" -copyright = "2023, Edward West" +copyright = "2024, Edward West" author = "Edward West" release = "1.0" From e187aa680f7800ba1968c9f91f72ba6c5cbc2910 Mon Sep 17 00:00:00 2001 From: edtechre Date: Sat, 22 Jun 2024 19:10:52 -0700 Subject: [PATCH 2/5] Fix dep order in requirements.txt --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 168df4f..402aebd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,10 +17,10 @@ pytest-cov>=4.0.0 pytest-instafail>=0.4.2 pytest-randomly>=3.12.0 pytest-xdist>=3.0.2 +ruff>=0.3.4 scikit-learn>=1.2.1 Sphinx>=5.3.0 sphinx_rtd_theme>=2.0.0 sphinx-intl>=2.1.0 -yfinance>=0.1.84 yahooquery>=2.3.7 -ruff>=0.3.4 \ No newline at end of file +yfinance>=0.1.84 \ No newline at end of file From 267e355a33bf57ac2caf1692638927cce091c9b4 Mon Sep 17 00:00:00 2001 From: edtechre Date: Sat, 22 Jun 2024 19:29:24 -0700 Subject: [PATCH 3/5] Fix YFinance docstring. --- src/pybroker/data.py | 4 ++-- src/pybroker/ext/data.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pybroker/data.py b/src/pybroker/data.py index 4c5a536..a8285ed 100644 --- a/src/pybroker/data.py +++ b/src/pybroker/data.py @@ -460,7 +460,7 @@ def _fetch_data( class YFinance(DataSource): - r"""Retrieves data from `Yahoo Finance `_. + r"""Retrieves data from `Yahoo Finance `_\ . Attributes: ADJ_CLOSE: Column name of adjusted close prices. @@ -481,7 +481,7 @@ def query( _timeframe: Optional[str] = "", _adjust: Optional[Any] = None, ) -> pd.DataFrame: - r"""Queries data from `Yahoo Finance `_. + r"""Queries data from `Yahoo Finance `_\ . The timeframe of the data is limited to per day only. Args: diff --git a/src/pybroker/ext/data.py b/src/pybroker/ext/data.py index 9f22f9d..805c160 100644 --- a/src/pybroker/ext/data.py +++ b/src/pybroker/ext/data.py @@ -95,8 +95,8 @@ def _fetch_data( class YQuery(DataSource): - r"""Retrieves data from `Yahoo Finance `_ - using `Yahooquery `_. + r"""Retrieves data from `Yahoo Finance `_ + using `Yahooquery `_\ . """ _tf_to_period = { From e58084d5534e8e8dac842c76f3266822eb009844 Mon Sep 17 00:00:00 2001 From: edtechre Date: Sat, 22 Jun 2024 19:33:49 -0700 Subject: [PATCH 4/5] Fix YQuery docstring. --- src/pybroker/ext/data.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pybroker/ext/data.py b/src/pybroker/ext/data.py index 805c160..fee56de 100644 --- a/src/pybroker/ext/data.py +++ b/src/pybroker/ext/data.py @@ -95,9 +95,8 @@ def _fetch_data( class YQuery(DataSource): - r"""Retrieves data from `Yahoo Finance `_ - using `Yahooquery `_\ . - """ + r"""Retrieves data from Yahoo Finance using + `Yahooquery `_\ .""" _tf_to_period = { "": "1d", From 169ed684954220736a0b7cf2595adce8a6efe5fc Mon Sep 17 00:00:00 2001 From: edtechre Date: Sat, 22 Jun 2024 22:58:20 -0700 Subject: [PATCH 5/5] Replaces np.float_ references with np.float64 np.float32 references also replaced with np.float64. --- src/pybroker/common.py | 12 +++--- src/pybroker/context.py | 16 ++++---- src/pybroker/eval.py | 80 +++++++++++++++++++-------------------- src/pybroker/indicator.py | 16 ++++---- src/pybroker/scope.py | 4 +- src/pybroker/vect.py | 12 +++--- 6 files changed, 70 insertions(+), 70 deletions(-) diff --git a/src/pybroker/common.py b/src/pybroker/common.py index a19f4d0..dee3a14 100644 --- a/src/pybroker/common.py +++ b/src/pybroker/common.py @@ -192,12 +192,12 @@ class BarData: def __init__( self, date: NDArray[np.datetime64], - open: NDArray[np.float_], - high: NDArray[np.float_], - low: NDArray[np.float_], - close: NDArray[np.float_], - volume: Optional[NDArray[np.float_]], - vwap: Optional[NDArray[np.float_]], + open: NDArray[np.float64], + high: NDArray[np.float64], + low: NDArray[np.float64], + close: NDArray[np.float64], + volume: Optional[NDArray[np.float64]], + vwap: Optional[NDArray[np.float64]], **kwargs, ): self.date = date diff --git a/src/pybroker/context.py b/src/pybroker/context.py index c716b24..89cae84 100644 --- a/src/pybroker/context.py +++ b/src/pybroker/context.py @@ -280,7 +280,7 @@ def model(self, name: str, symbol: str) -> Any: raise ValueError(f"Model {name!r} not found for {symbol}.") return self._models[model_sym].instance - def indicator(self, name: str, symbol: str) -> NDArray[np.float_]: + def indicator(self, name: str, symbol: str) -> NDArray[np.float64]: r"""Returns indicator data. Args: @@ -704,7 +704,7 @@ def date(self) -> NDArray[np.datetime64]: ) @property - def open(self) -> NDArray[np.float_]: + def open(self) -> NDArray[np.float64]: """Current bar's open price.""" self._verify_symbol() return self._col_scope.fetch( # type: ignore[return-value] @@ -714,7 +714,7 @@ def open(self) -> NDArray[np.float_]: ) @property - def high(self) -> NDArray[np.float_]: + def high(self) -> NDArray[np.float64]: """Current bar's high price.""" self._verify_symbol() return self._col_scope.fetch( # type: ignore[return-value] @@ -724,7 +724,7 @@ def high(self) -> NDArray[np.float_]: ) @property - def low(self) -> NDArray[np.float_]: + def low(self) -> NDArray[np.float64]: """Current bar's low price.""" self._verify_symbol() return self._col_scope.fetch( # type: ignore[return-value] @@ -734,7 +734,7 @@ def low(self) -> NDArray[np.float_]: ) @property - def close(self) -> NDArray[np.float_]: + def close(self) -> NDArray[np.float64]: """Current bar's close price.""" self._verify_symbol() return self._col_scope.fetch( # type: ignore[return-value] @@ -744,7 +744,7 @@ def close(self) -> NDArray[np.float_]: ) @property - def volume(self) -> Optional[NDArray[np.float_]]: + def volume(self) -> Optional[NDArray[np.float64]]: """Current bar's volume.""" self._verify_symbol() return self._col_scope.fetch( # type: ignore[return-value] @@ -754,7 +754,7 @@ def volume(self) -> Optional[NDArray[np.float_]]: ) @property - def vwap(self) -> Optional[NDArray[np.float_]]: + def vwap(self) -> Optional[NDArray[np.float64]]: """Current bar's volume-weighted average price (VWAP).""" self._verify_symbol() return self._col_scope.fetch( # type: ignore[return-value] @@ -887,7 +887,7 @@ def model(self, name: str, symbol: Optional[str] = None) -> Any: def indicator( self, name: str, symbol: Optional[str] = None - ) -> NDArray[np.float_]: + ) -> NDArray[np.float64]: r"""Returns indicator data. Args: diff --git a/src/pybroker/eval.py b/src/pybroker/eval.py index 6707499..6a268ac 100644 --- a/src/pybroker/eval.py +++ b/src/pybroker/eval.py @@ -66,10 +66,10 @@ class BootConfIntervals(NamedTuple): @njit def bca_boot_conf( - x: NDArray[np.float_], + x: NDArray[np.float64], n: int, n_boot: int, - fn: Callable[[NDArray[np.float_]], float], + fn: Callable[[NDArray[np.float64]], float], ) -> BootConfIntervals: """Computes confidence intervals for a user-defined parameter using the `bias corrected and accelerated (BCa) bootstrap method. @@ -169,7 +169,7 @@ def clamp(k: int): @njit def profit_factor( - changes: NDArray[np.float_], use_log: bool = False + changes: NDArray[np.float64], use_log: bool = False ) -> np.floating: """Computes the profit factor, which is the ratio of gross profit to gross loss. @@ -181,7 +181,7 @@ def profit_factor( wins = changes[changes > 0] losses = changes[changes < 0] if not len(wins) and not len(losses): - return np.float32(0) + return np.float64(0) numer = denom = 1.0e-10 numer += np.sum(wins) denom -= np.sum(losses) @@ -192,7 +192,7 @@ def profit_factor( @njit -def log_profit_factor(changes: NDArray[np.float_]) -> np.floating: +def log_profit_factor(changes: NDArray[np.float64]) -> np.floating: """Computes the log transformed profit factor, which is the ratio of gross profit to gross loss. @@ -204,7 +204,7 @@ def log_profit_factor(changes: NDArray[np.float_]) -> np.floating: @njit def sharpe_ratio( - changes: NDArray[np.float_], + changes: NDArray[np.float64], obs: Optional[int] = None, downside_only: bool = False, ) -> np.floating: @@ -219,10 +219,10 @@ def sharpe_ratio( """ std_changes = changes[changes < 0] if downside_only else changes if not len(std_changes): - return np.float32(0) + return np.float64(0) std = np.std(std_changes) if std == 0: - return np.float32(0) + return np.float64(0) sr = np.mean(changes) / std if obs is not None: sr *= np.sqrt(obs) @@ -230,7 +230,7 @@ def sharpe_ratio( def sortino_ratio( - changes: NDArray[np.float_], obs: Optional[int] = None + changes: NDArray[np.float64], obs: Optional[int] = None ) -> float: """Computes the `Sortino Ratio `_. @@ -245,7 +245,7 @@ def sortino_ratio( def conf_profit_factor( - x: NDArray[np.float_], n: int, n_boot: int + x: NDArray[np.float64], n: int, n_boot: int ) -> BootConfIntervals: """Computes confidence intervals for :func:`.profit_factor`.""" intervals = bca_boot_conf(x, n, n_boot, log_profit_factor) @@ -260,7 +260,7 @@ def conf_profit_factor( def conf_sharpe_ratio( - x: NDArray[np.float_], n: int, n_boot: int, obs: Optional[int] = None + x: NDArray[np.float64], n: int, n_boot: int, obs: Optional[int] = None ) -> BootConfIntervals: """Computes confidence intervals for :func:`.sharpe_ratio`.""" intervals = bca_boot_conf(x, n, n_boot, sharpe_ratio) @@ -278,7 +278,7 @@ def conf_sharpe_ratio( @njit -def max_drawdown(changes: NDArray[np.float_]) -> float: +def max_drawdown(changes: NDArray[np.float64]) -> float: """Computes maximum drawdown, measured in cash. Args: @@ -301,7 +301,7 @@ def max_drawdown(changes: NDArray[np.float_]) -> float: return -dd -def calmar_ratio(changes: NDArray[np.float_], bars_per_year: int) -> float: +def calmar_ratio(changes: NDArray[np.float64], bars_per_year: int) -> float: """Computes the Calmar Ratio. Args: @@ -317,7 +317,7 @@ def calmar_ratio(changes: NDArray[np.float_], bars_per_year: int) -> float: @njit -def max_drawdown_percent(returns: NDArray[np.float_]) -> float: +def max_drawdown_percent(returns: NDArray[np.float64]) -> float: """Computes maximum drawdown, measured in percentage loss. Args: @@ -342,7 +342,7 @@ def max_drawdown_percent(returns: NDArray[np.float_]) -> float: @njit -def _dd_conf(q: float, boot: NDArray[np.float_]) -> float: +def _dd_conf(q: float, boot: NDArray[np.float64]) -> float: k = int((q * (len(boot) + 1)) - 1) k = max(k, 0) return boot[k] @@ -379,7 +379,7 @@ class DrawdownMetrics(NamedTuple): @njit -def _dd_confs(boot: NDArray[np.float_]) -> DrawdownConfs: +def _dd_confs(boot: NDArray[np.float64]) -> DrawdownConfs: boot.sort() boot = boot[::-1] return DrawdownConfs( @@ -392,8 +392,8 @@ def _dd_confs(boot: NDArray[np.float_]) -> DrawdownConfs: @njit def drawdown_conf( - changes: NDArray[np.float_], - returns: NDArray[np.float_], + changes: NDArray[np.float64], + returns: NDArray[np.float64], n: int, n_boot: int, ) -> DrawdownMetrics: @@ -434,7 +434,7 @@ def drawdown_conf( @njit -def relative_entropy(values: NDArray[np.float_]) -> float: +def relative_entropy(values: NDArray[np.float64]) -> float: """Computes the relative `entropy `_. """ @@ -465,7 +465,7 @@ def relative_entropy(values: NDArray[np.float_]) -> float: return -sum_ / np.log(n_bins) -def iqr(values: NDArray[np.float_]) -> float: +def iqr(values: NDArray[np.float64]) -> float: """Computes the `interquartile range (IQR) `_ of ``values``.""" x = values[~np.isnan(values)] @@ -476,7 +476,7 @@ def iqr(values: NDArray[np.float_]) -> float: @njit -def ulcer_index(values: NDArray[np.float_], period: int = 14) -> float: +def ulcer_index(values: NDArray[np.float64], period: int = 14) -> float: """Computes the `Ulcer Index `_ of ``values``. """ @@ -496,7 +496,7 @@ def ulcer_index(values: NDArray[np.float_], period: int = 14) -> float: @njit def upi( - values: NDArray[np.float_], period: int = 14, ui: Optional[float] = None + values: NDArray[np.float64], period: int = 14, ui: Optional[float] = None ) -> float: """Computes the `Ulcer Performance Index `_ of ``values``. @@ -513,7 +513,7 @@ def upi( return float(np.mean(r) / ui) -def win_loss_rate(pnls: NDArray[np.float_]) -> tuple[float, float]: +def win_loss_rate(pnls: NDArray[np.float64]) -> tuple[float, float]: """Computes the win rate and loss rate as percentages. Args: @@ -531,7 +531,7 @@ def win_loss_rate(pnls: NDArray[np.float_]) -> tuple[float, float]: return win_rate, loss_rate -def winning_losing_trades(pnls: NDArray[np.float_]) -> tuple[int, int]: +def winning_losing_trades(pnls: NDArray[np.float64]) -> tuple[int, int]: """Returns the number of winning and losing trades. Args: @@ -546,7 +546,7 @@ def winning_losing_trades(pnls: NDArray[np.float_]) -> tuple[int, int]: return len(pnls[pnls > 0]), len(pnls[pnls < 0]) -def total_profit_loss(pnls: NDArray[np.float_]) -> tuple[float, float]: +def total_profit_loss(pnls: NDArray[np.float64]) -> tuple[float, float]: """Computes total profit and loss. Args: @@ -563,7 +563,7 @@ def total_profit_loss(pnls: NDArray[np.float_]) -> tuple[float, float]: ) -def avg_profit_loss(pnls: NDArray[np.float_]) -> tuple[float, float]: +def avg_profit_loss(pnls: NDArray[np.float64]) -> tuple[float, float]: """Computes the average profit and average loss per trade. Args: @@ -581,7 +581,7 @@ def avg_profit_loss(pnls: NDArray[np.float_]) -> tuple[float, float]: ) -def largest_win_loss(pnls: NDArray[np.float_]) -> tuple[float, float]: +def largest_win_loss(pnls: NDArray[np.float64]) -> tuple[float, float]: """Computes the largest profit and largest loss of all trades. Args: @@ -599,7 +599,7 @@ def largest_win_loss(pnls: NDArray[np.float_]) -> tuple[float, float]: @njit -def max_wins_losses(pnls: NDArray[np.float_]) -> tuple[int, int]: +def max_wins_losses(pnls: NDArray[np.float64]) -> tuple[int, int]: """Computes the max consecutive wins and max consecutive losses. Args: @@ -656,7 +656,7 @@ def annual_total_return_percent( ) * 100 -def r_squared(values: NDArray[np.float_]) -> float: +def r_squared(values: NDArray[np.float64]) -> float: """Computes R-squared of ``values``.""" n = len(values) if not n: @@ -947,22 +947,22 @@ def evaluate( logger.calc_bootstrap_metrics_completed() return EvalResult(metrics, bootstrap) - def _calc_bar_returns(self, df: pd.DataFrame) -> NDArray[np.float32]: + def _calc_bar_returns(self, df: pd.DataFrame) -> NDArray[np.float64]: prev_market_value = df["market_value"].shift(1) returns = (df["market_value"] - prev_market_value) / prev_market_value return returns.dropna().to_numpy() - def _calc_bar_changes(self, df: pd.DataFrame) -> NDArray[np.float32]: + def _calc_bar_changes(self, df: pd.DataFrame) -> NDArray[np.float64]: changes = df["market_value"] - df["market_value"].shift(1) return changes.dropna().to_numpy() def _calc_eval_metrics( self, - market_values: NDArray[np.float_], - bar_changes: NDArray[np.float_], - bar_returns: NDArray[np.float_], - pnls: NDArray[np.float_], - return_pcts: NDArray[np.float_], + market_values: NDArray[np.float64], + bar_changes: NDArray[np.float64], + bar_returns: NDArray[np.float64], + pnls: NDArray[np.float64], + return_pcts: NDArray[np.float64], bars: NDArray[np.int_], winning_bars: NDArray[np.int_], losing_bars: NDArray[np.int_], @@ -970,7 +970,7 @@ def _calc_eval_metrics( largest_win_pct: float, largest_loss_num_bars: int, largest_loss_pct: float, - fees: NDArray[np.float_], + fees: NDArray[np.float64], bars_per_year: Optional[int], ) -> EvalMetrics: total_fees = fees[-1] if len(fees) else 0 @@ -1092,7 +1092,7 @@ def _calc_eval_metrics( def _calc_conf_intervals( self, - changes: NDArray[np.float_], + changes: NDArray[np.float64], sample_size: int, samples: int, bars_per_year: Optional[int], @@ -1124,8 +1124,8 @@ def _to_conf_intervals( def _calc_drawdown_conf( self, - changes: NDArray[np.float_], - returns: NDArray[np.float_], + changes: NDArray[np.float64], + returns: NDArray[np.float64], sample_size: int, samples: int, ) -> _DrawdownResult: diff --git a/src/pybroker/indicator.py b/src/pybroker/indicator.py index b7714a3..3b287bb 100644 --- a/src/pybroker/indicator.py +++ b/src/pybroker/indicator.py @@ -72,7 +72,7 @@ class Indicator: def __init__( self, name: str, - fn: Callable[..., NDArray[np.float_]], + fn: Callable[..., NDArray[np.float64]], kwargs: dict[str, Any], ): self.name = name @@ -114,7 +114,7 @@ def __str__(self): def indicator( - name: str, fn: Callable[..., NDArray[np.float_]], **kwargs + name: str, fn: Callable[..., NDArray[np.float64]], **kwargs ) -> Indicator: r"""Creates an :class:`.Indicator` instance and registers it globally with ``name``. @@ -141,12 +141,12 @@ def decorated_indicator_fn( symbol: str, ind_name: str, date: NDArray[np.datetime64], - open: NDArray[np.float_], - high: NDArray[np.float_], - low: NDArray[np.float_], - close: NDArray[np.float_], - volume: Optional[NDArray[np.float_]], - vwap: Optional[NDArray[np.float_]], + open: NDArray[np.float64], + high: NDArray[np.float64], + low: NDArray[np.float64], + close: NDArray[np.float64], + volume: Optional[NDArray[np.float64]], + vwap: Optional[NDArray[np.float64]], custom_col_data: Mapping[str, Optional[NDArray]], ) -> tuple[IndicatorSymbol, pd.Series]: bar_data = BarData( diff --git a/src/pybroker/scope.py b/src/pybroker/scope.py index 2ea90ca..dad40d3 100644 --- a/src/pybroker/scope.py +++ b/src/pybroker/scope.py @@ -338,11 +338,11 @@ def __init__( ): self._indicator_data = indicator_data self._filter_dates = filter_dates - self._sym_inds: dict[IndicatorSymbol, NDArray[np.float_]] = {} + self._sym_inds: dict[IndicatorSymbol, NDArray[np.float64]] = {} def fetch( self, symbol: str, name: str, end_index: Optional[int] = None - ) -> NDArray[np.float_]: + ) -> NDArray[np.float64]: """Fetches :class:`pybroker.indicator.Indicator` data. Args: diff --git a/src/pybroker/vect.py b/src/pybroker/vect.py index b10b85b..be37167 100644 --- a/src/pybroker/vect.py +++ b/src/pybroker/vect.py @@ -12,7 +12,7 @@ @njit -def _verify_input(array: NDArray[np.float_], n: int): +def _verify_input(array: NDArray[np.float64], n: int): if n <= 0: raise ValueError("n needs to be >= 1.") if n > len(array): @@ -20,7 +20,7 @@ def _verify_input(array: NDArray[np.float_], n: int): @njit -def lowv(array: NDArray[np.float_], n: int) -> NDArray[np.float_]: +def lowv(array: NDArray[np.float64], n: int) -> NDArray[np.float64]: """Calculates the lowest values for every ``n`` period in ``array``. Args: @@ -42,7 +42,7 @@ def lowv(array: NDArray[np.float_], n: int) -> NDArray[np.float_]: @njit -def highv(array: NDArray[np.float_], n: int) -> NDArray[np.float_]: +def highv(array: NDArray[np.float64], n: int) -> NDArray[np.float64]: """Calculates the highest values for every ``n`` period in ``array``. Args: @@ -64,7 +64,7 @@ def highv(array: NDArray[np.float_], n: int) -> NDArray[np.float_]: @njit -def sumv(array: NDArray[np.float_], n: int) -> NDArray[np.float_]: +def sumv(array: NDArray[np.float64], n: int) -> NDArray[np.float64]: """Calculates the sums for every ``n`` period in ``array``. Args: @@ -85,7 +85,7 @@ def sumv(array: NDArray[np.float_], n: int) -> NDArray[np.float_]: @njit -def returnv(array: NDArray[np.float_], n: int = 1) -> NDArray[np.float_]: +def returnv(array: NDArray[np.float64], n: int = 1) -> NDArray[np.float64]: """Calculates returns. Args: @@ -105,7 +105,7 @@ def returnv(array: NDArray[np.float_], n: int = 1) -> NDArray[np.float_]: @njit -def cross(a: NDArray[np.float_], b: NDArray[np.float_]) -> NDArray[np.bool_]: +def cross(a: NDArray[np.float64], b: NDArray[np.float64]) -> NDArray[np.bool_]: """Checks for crossover of ``a`` above ``b``. Args: