Archives
Trending
Support
Login
clear text
XML
Django
JavaScript
MATLAB
C
C++
C#
Python
SQL
Shell
Bash
Markdown
YAML
JSON
HTML
CSS
PHP
Java
Ruby
Go
Rust
Swift
Kotlin
Arduino
TypeScript
Perl
Autohotkey
Lua
SQF
R
Scala
Haskell
Groovy
Dart
Clojure
VB.NET
Objective-C
PowerShell
Bash
CoffeeScript
Verilog
from typing import List from pandas import DataFrame import numpy as np from quant.strategy import Strategy, HistoricalData, StrategyOutput class MeanStrategy(Strategy): def __init__( self, stocks: List[str], lookback: int = 50, # ile punktów wstecz do liczenia z-score min_lookback: int = 5, # minimalna liczba obserwacji, żeby w ogóle robić MR use_equal_fallback: bool = False, # czy w razie problemów robić równy portfel ) -> None: self.stocks = stocks self.lookback = lookback self.min_lookback = min_lookback self.use_equal_fallback = use_equal_fallback def _equal_weights(self, available_stocks: List[str]) -> StrategyOutput: n = len(available_stocks) if n == 0: raise ValueError("No available stocks to assign equal weights to.") equal_w = np.ones(n) / n weights_df = DataFrame({ "stock_name": available_stocks, "weight": equal_w }) return StrategyOutput(weights=weights_df) def get_weights(self, historical_data: HistoricalData) -> StrategyOutput: prices = historical_data.stock_prices # Upewniamy się, że używamy tylko tych kolumn, które faktycznie są w DataFrame available_stocks = [s for s in self.stocks if s in prices.columns] if len(available_stocks) == 0: raise ValueError("None of the requested stocks are present in stock_prices.") prices = prices[available_stocks].dropna(how="all") if len(prices) == 0: if self.use_equal_fallback: return self._equal_weights(available_stocks) else: raise ValueError("No price data available for the selected stocks.") # --- Mean reversion na bazie z-score cen --- # realne okno = min(lookback, liczba dostępnych punktów) lookback = min(self.lookback, len(prices)) if lookback < self.min_lookback: if self.use_equal_fallback: return self._equal_weights(available_stocks) else: raise ValueError( f"Not enough data points for lookback={self.lookback}, " f"got only {len(prices)} rows." ) roll_mean = prices.rolling(window=lookback).mean() roll_std = prices.rolling(window=lookback).std() zscore = (prices - roll_mean) / roll_std # używamy ostatnie dostępne z-score (bez look-ahead) z_last = zscore.iloc[-1] # Mean reversion: jeśli coś jest wysoko (z>0), spodziewamy się spadku → short, # jeśli nisko (z<0), spodziewamy się odbicia → long signal = -z_last.values # minus = mean reversion # Jeżeli wszystko NaN lub 0 → równy portfel (albo błąd, zależnie od flagi) if np.all(np.isnan(signal)) or np.allclose(signal, 0): if self.use_equal_fallback: return self._equal_weights(available_stocks) else: raise ValueError("Signal is degenerate (all NaN or zero).") # Zastępujemy NaN zerami (brak sygnału na danym instrumencie) signal = np.nan_to_num(signal, nan=0.0) # Jeśli nadal same zera → fallback if np.allclose(signal, 0): if self.use_equal_fallback: return self._equal_weights(available_stocks) else: raise ValueError("Signal is zero after NaN handling.") # Normalizacja: sum(|w|) = 1 abs_sum = np.sum(np.abs(signal)) if abs_sum == 0: if self.use_equal_fallback: return self._equal_weights(available_stocks) else: raise ValueError("Absolute sum of signal is zero.") weights = signal / abs_sum weights_df = DataFrame({ "stock_name": available_stocks, "weight": weights }) return StrategyOutput(weights=weights_df)
Mark as private (unlisted)
for 30 minutes
for 6 hours
for 1 day
for 1 week
for 1 month
for 1 year
everlasting (like CCCP)