Coverage for src / monte_neo / indicators / evaluator.py: 82%
56 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-28 16:27 +0200
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-28 16:27 +0200
1"""Evaluator for dynamic indicators.
2"""
4from __future__ import annotations
6from collections.abc import Callable
7from typing import Any
9import numpy as np
10import pandas as pd
12from monte_neo.utils.logger import get_logger
14logger = get_logger(__name__)
16def rsi(data, period=14):
17 if isinstance(data, dict):
18 close = pd.Series(data['close'])
19 else:
20 close = data['close'] if hasattr(data, 'close') else data
21 delta = close.diff()
22 gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
23 loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
24 rs = gain / loss
25 return 100 - (100 / (1 + rs))
27def sma(data, period=20):
28 if isinstance(data, dict):
29 close = pd.Series(data['close'])
30 else:
31 close = data['close'] if hasattr(data, 'close') else data
32 return close.rolling(window=period).mean()
34def compile_source(source_code: str) -> Callable[..., Any]:
35 """Compile source code into a function."""
36 try:
37 exec_globals = {
38 "np": np,
39 "pd": pd,
40 "_np": np,
41 "_pd": pd,
42 "rsi": rsi,
43 "sma": sma,
44 "__builtins__": __builtins__,
45 }
47 func_code = (
48 f"def _dynamic_calc(data, np, pd):\n return {source_code}"
49 )
51 local_scope: dict[str, Any] = {}
52 exec(func_code, exec_globals, local_scope)
53 return local_scope["_dynamic_calc"]
54 except Exception as e:
55 logger.debug(f"Failed to compile dynamic indicator: {e}")
56 # Fallback to safe source
57 safe_source = "data['close']"
58 func_code = (
59 f"def _dynamic_calc(data, np, pd):\n return {safe_source}"
60 )
61 safe_scope: dict[str, Any] = {}
62 exec(func_code, exec_globals, safe_scope)
63 return safe_scope["_dynamic_calc"]
65def evaluate_fast_signals(compiled_code: Callable, data: pd.DataFrame | np.ndarray) -> np.ndarray:
66 """Fast version of signal generation."""
67 if isinstance(data, pd.DataFrame):
68 fast_data = {
69 "open": data["open"],
70 "high": data["high"],
71 "low": data["low"],
72 "close": data["close"],
73 "volume": data["volume"],
74 }
75 n_rows = len(data)
76 else:
77 # If it's a numpy array, check dimensions
78 if data.ndim == 1:
79 # Only close prices provided (typical for MC scenarios)
80 fast_data = {
81 "open": pd.Series(data),
82 "high": pd.Series(data),
83 "low": pd.Series(data),
84 "close": pd.Series(data),
85 "volume": pd.Series(np.ones_like(data)),
86 }
87 else:
88 # OHLCV provided
89 fast_data = {
90 "open": pd.Series(data[:, 0]),
91 "high": pd.Series(data[:, 1]),
92 "low": pd.Series(data[:, 2]),
93 "close": pd.Series(data[:, 3]),
94 "volume": pd.Series(data[:, 4]),
95 }
96 n_rows = len(data)
98 try:
99 vals = compiled_code(fast_data, np, pd)
101 if isinstance(vals, pd.Series):
102 vals = vals.values
104 if not isinstance(vals, np.ndarray):
105 vals = np.full(n_rows, vals)
107 sig_vals = np.zeros(n_rows, dtype=np.float32)
109 # Standardize signals: >0 is 1, <0 is -1, 0 is 0
110 sig_vals[np.greater(vals, 0)] = 1.0
111 sig_vals[np.less(vals, 0)] = -1.0
112 return sig_vals
113 except Exception as e:
114 logger.debug(f"Error in fast evaluation: {e}")
115 return np.zeros(n_rows, dtype=np.float32)