diff options
Diffstat (limited to 'services/strategy-engine/strategies')
8 files changed, 78 insertions, 0 deletions
diff --git a/services/strategy-engine/strategies/base.py b/services/strategy-engine/strategies/base.py index fdf49ed..cf5e6e4 100644 --- a/services/strategy-engine/strategies/base.py +++ b/services/strategy-engine/strategies/base.py @@ -20,3 +20,7 @@ class BaseStrategy(ABC): def reset(self) -> None: pass + + def validate_params(self, params: dict) -> list[str]: + """Validate parameters and return list of error messages. Empty = valid.""" + return [] diff --git a/services/strategy-engine/strategies/bollinger_strategy.py b/services/strategy-engine/strategies/bollinger_strategy.py index bee7ee4..4aceee4 100644 --- a/services/strategy-engine/strategies/bollinger_strategy.py +++ b/services/strategy-engine/strategies/bollinger_strategy.py @@ -29,6 +29,13 @@ class BollingerStrategy(BaseStrategy): self._min_bandwidth = float(params.get("min_bandwidth", 0.02)) self._quantity = Decimal(str(params.get("quantity", "0.01"))) + if self._period < 2: + raise ValueError(f"Bollinger period must be >= 2, got {self._period}") + if self._num_std <= 0: + raise ValueError(f"Bollinger num_std must be > 0, got {self._num_std}") + if self._quantity <= 0: + raise ValueError(f"Quantity must be positive, got {self._quantity}") + def reset(self) -> None: self._closes.clear() self._was_below_lower = False diff --git a/services/strategy-engine/strategies/ema_crossover_strategy.py b/services/strategy-engine/strategies/ema_crossover_strategy.py index 17234a3..b0ccbbf 100644 --- a/services/strategy-engine/strategies/ema_crossover_strategy.py +++ b/services/strategy-engine/strategies/ema_crossover_strategy.py @@ -26,6 +26,18 @@ class EmaCrossoverStrategy(BaseStrategy): self._long_period = int(params.get("long_period", 21)) self._quantity = Decimal(str(params.get("quantity", "0.01"))) + if self._short_period >= self._long_period: + raise ValueError( + f"EMA short_period must be < long_period, " + f"got short={self._short_period}, long={self._long_period}" + ) + if self._short_period < 2: + raise ValueError(f"EMA short_period must be >= 2, got {self._short_period}") + if self._long_period < 2: + raise ValueError(f"EMA long_period must be >= 2, got {self._long_period}") + if self._quantity <= 0: + raise ValueError(f"Quantity must be positive, got {self._quantity}") + def reset(self) -> None: self._closes.clear() self._prev_short_above = None diff --git a/services/strategy-engine/strategies/grid_strategy.py b/services/strategy-engine/strategies/grid_strategy.py index 78e2703..b65264c 100644 --- a/services/strategy-engine/strategies/grid_strategy.py +++ b/services/strategy-engine/strategies/grid_strategy.py @@ -27,6 +27,17 @@ class GridStrategy(BaseStrategy): self._upper_price = float(params["upper_price"]) self._grid_count = int(params.get("grid_count", 5)) self._quantity = Decimal(str(params.get("quantity", "0.01"))) + + if self._lower_price >= self._upper_price: + raise ValueError( + f"Grid lower_price must be < upper_price, " + f"got lower={self._lower_price}, upper={self._upper_price}" + ) + if self._grid_count < 2: + raise ValueError(f"Grid grid_count must be >= 2, got {self._grid_count}") + if self._quantity <= 0: + raise ValueError(f"Quantity must be positive, got {self._quantity}") + self._grid_levels = list( np.linspace(self._lower_price, self._upper_price, self._grid_count + 1) ) diff --git a/services/strategy-engine/strategies/macd_strategy.py b/services/strategy-engine/strategies/macd_strategy.py index 049574e..e3bb35c 100644 --- a/services/strategy-engine/strategies/macd_strategy.py +++ b/services/strategy-engine/strategies/macd_strategy.py @@ -28,6 +28,20 @@ class MacdStrategy(BaseStrategy): self._signal_period = int(params.get("signal_period", 9)) self._quantity = Decimal(str(params.get("quantity", "0.01"))) + if self._fast_period >= self._slow_period: + raise ValueError( + f"MACD fast_period must be < slow_period, " + f"got fast={self._fast_period}, slow={self._slow_period}" + ) + if self._fast_period < 2: + raise ValueError(f"MACD fast_period must be >= 2, got {self._fast_period}") + if self._slow_period < 2: + raise ValueError(f"MACD slow_period must be >= 2, got {self._slow_period}") + if self._signal_period < 2: + raise ValueError(f"MACD signal_period must be >= 2, got {self._signal_period}") + if self._quantity <= 0: + raise ValueError(f"Quantity must be positive, got {self._quantity}") + def reset(self) -> None: self._closes.clear() self._prev_histogram = None diff --git a/services/strategy-engine/strategies/rsi_strategy.py b/services/strategy-engine/strategies/rsi_strategy.py index c37957d..59946f4 100644 --- a/services/strategy-engine/strategies/rsi_strategy.py +++ b/services/strategy-engine/strategies/rsi_strategy.py @@ -44,6 +44,16 @@ class RsiStrategy(BaseStrategy): self._overbought = float(params.get("overbought", 70)) self._quantity = Decimal(str(params.get("quantity", "0.01"))) + if self._period < 2: + raise ValueError(f"RSI period must be >= 2, got {self._period}") + if not (0 < self._oversold < self._overbought < 100): + raise ValueError( + f"RSI thresholds must be 0 < oversold < overbought < 100, " + f"got oversold={self._oversold}, overbought={self._overbought}" + ) + if self._quantity <= 0: + raise ValueError(f"Quantity must be positive, got {self._quantity}") + def reset(self) -> None: self._closes.clear() diff --git a/services/strategy-engine/strategies/volume_profile_strategy.py b/services/strategy-engine/strategies/volume_profile_strategy.py index e9463bf..b91e107 100644 --- a/services/strategy-engine/strategies/volume_profile_strategy.py +++ b/services/strategy-engine/strategies/volume_profile_strategy.py @@ -29,6 +29,19 @@ class VolumeProfileStrategy(BaseStrategy): self._value_area_pct = float(params.get("value_area_pct", 0.7)) self._quantity = Decimal(str(params.get("quantity", "0.01"))) + if self._lookback_period < 2: + raise ValueError( + f"Volume profile lookback_period must be >= 2, got {self._lookback_period}" + ) + if self._num_bins < 2: + raise ValueError(f"Volume profile num_bins must be >= 2, got {self._num_bins}") + if not (0 < self._value_area_pct <= 1): + raise ValueError( + f"Volume profile value_area_pct must be 0 < pct <= 1, got {self._value_area_pct}" + ) + if self._quantity <= 0: + raise ValueError(f"Quantity must be positive, got {self._quantity}") + def reset(self) -> None: self._candles.clear() self._was_below_va = False diff --git a/services/strategy-engine/strategies/vwap_strategy.py b/services/strategy-engine/strategies/vwap_strategy.py index d1b86b5..78919f1 100644 --- a/services/strategy-engine/strategies/vwap_strategy.py +++ b/services/strategy-engine/strategies/vwap_strategy.py @@ -24,6 +24,13 @@ class VwapStrategy(BaseStrategy): self._deviation_threshold = float(params.get("deviation_threshold", 0.002)) self._quantity = Decimal(str(params.get("quantity", "0.01"))) + if self._deviation_threshold <= 0: + raise ValueError( + f"VWAP deviation_threshold must be > 0, got {self._deviation_threshold}" + ) + if self._quantity <= 0: + raise ValueError(f"Quantity must be positive, got {self._quantity}") + def reset(self) -> None: self._cumulative_tp_vol = 0.0 self._cumulative_vol = 0.0 |
