quantmllibrary 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. quantml/__init__.py +74 -0
  2. quantml/autograd.py +154 -0
  3. quantml/cli/__init__.py +10 -0
  4. quantml/cli/run_experiment.py +385 -0
  5. quantml/config/__init__.py +28 -0
  6. quantml/config/config.py +259 -0
  7. quantml/data/__init__.py +33 -0
  8. quantml/data/cache.py +149 -0
  9. quantml/data/feature_store.py +234 -0
  10. quantml/data/futures.py +254 -0
  11. quantml/data/loaders.py +236 -0
  12. quantml/data/memory_optimizer.py +234 -0
  13. quantml/data/validators.py +390 -0
  14. quantml/experiments/__init__.py +23 -0
  15. quantml/experiments/logger.py +208 -0
  16. quantml/experiments/results.py +158 -0
  17. quantml/experiments/tracker.py +223 -0
  18. quantml/features/__init__.py +25 -0
  19. quantml/features/base.py +104 -0
  20. quantml/features/gap_features.py +124 -0
  21. quantml/features/registry.py +138 -0
  22. quantml/features/volatility_features.py +140 -0
  23. quantml/features/volume_features.py +142 -0
  24. quantml/functional.py +37 -0
  25. quantml/models/__init__.py +27 -0
  26. quantml/models/attention.py +258 -0
  27. quantml/models/dropout.py +130 -0
  28. quantml/models/gru.py +319 -0
  29. quantml/models/linear.py +112 -0
  30. quantml/models/lstm.py +353 -0
  31. quantml/models/mlp.py +286 -0
  32. quantml/models/normalization.py +289 -0
  33. quantml/models/rnn.py +154 -0
  34. quantml/models/tcn.py +238 -0
  35. quantml/online.py +209 -0
  36. quantml/ops.py +1707 -0
  37. quantml/optim/__init__.py +42 -0
  38. quantml/optim/adafactor.py +206 -0
  39. quantml/optim/adagrad.py +157 -0
  40. quantml/optim/adam.py +267 -0
  41. quantml/optim/lookahead.py +97 -0
  42. quantml/optim/quant_optimizer.py +228 -0
  43. quantml/optim/radam.py +192 -0
  44. quantml/optim/rmsprop.py +203 -0
  45. quantml/optim/schedulers.py +286 -0
  46. quantml/optim/sgd.py +181 -0
  47. quantml/py.typed +0 -0
  48. quantml/streaming.py +175 -0
  49. quantml/tensor.py +462 -0
  50. quantml/time_series.py +447 -0
  51. quantml/training/__init__.py +135 -0
  52. quantml/training/alpha_eval.py +203 -0
  53. quantml/training/backtest.py +280 -0
  54. quantml/training/backtest_analysis.py +168 -0
  55. quantml/training/cv.py +106 -0
  56. quantml/training/data_loader.py +177 -0
  57. quantml/training/ensemble.py +84 -0
  58. quantml/training/feature_importance.py +135 -0
  59. quantml/training/features.py +364 -0
  60. quantml/training/futures_backtest.py +266 -0
  61. quantml/training/gradient_clipping.py +206 -0
  62. quantml/training/losses.py +248 -0
  63. quantml/training/lr_finder.py +127 -0
  64. quantml/training/metrics.py +376 -0
  65. quantml/training/regularization.py +89 -0
  66. quantml/training/trainer.py +239 -0
  67. quantml/training/walk_forward.py +190 -0
  68. quantml/utils/__init__.py +51 -0
  69. quantml/utils/gradient_check.py +274 -0
  70. quantml/utils/logging.py +181 -0
  71. quantml/utils/ops_cpu.py +231 -0
  72. quantml/utils/profiling.py +364 -0
  73. quantml/utils/reproducibility.py +220 -0
  74. quantml/utils/serialization.py +335 -0
  75. quantmllibrary-0.1.0.dist-info/METADATA +536 -0
  76. quantmllibrary-0.1.0.dist-info/RECORD +79 -0
  77. quantmllibrary-0.1.0.dist-info/WHEEL +5 -0
  78. quantmllibrary-0.1.0.dist-info/licenses/LICENSE +22 -0
  79. quantmllibrary-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,104 @@
1
+ """
2
+ Base feature class for plugin-based feature system.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import List, Dict, Any, Optional
7
+ from dataclasses import dataclass
8
+
9
+
10
+ @dataclass
11
+ class FeatureMetadata:
12
+ """Metadata for a feature."""
13
+ name: str
14
+ description: str
15
+ formula: Optional[str] = None
16
+ expected_range: Optional[tuple] = None
17
+ unit: Optional[str] = None
18
+
19
+
20
+ class BaseFeature(ABC):
21
+ """
22
+ Base class for all features.
23
+
24
+ All features should inherit from this class and implement the compute method.
25
+ """
26
+
27
+ def __init__(self, name: str, description: str = "", **kwargs):
28
+ """
29
+ Initialize feature.
30
+
31
+ Args:
32
+ name: Feature name
33
+ description: Feature description
34
+ **kwargs: Feature-specific parameters
35
+ """
36
+ self.name = name
37
+ self.description = description
38
+ self.params = kwargs
39
+ self.metadata = FeatureMetadata(
40
+ name=name,
41
+ description=description
42
+ )
43
+
44
+ @abstractmethod
45
+ def compute(self, data: Dict[str, List[float]]) -> List[float]:
46
+ """
47
+ Compute feature values.
48
+
49
+ Args:
50
+ data: Dictionary with required data (e.g., {'price': [...], 'volume': [...]})
51
+
52
+ Returns:
53
+ List of feature values
54
+ """
55
+ pass
56
+
57
+ def get_metadata(self) -> FeatureMetadata:
58
+ """Get feature metadata."""
59
+ return self.metadata
60
+
61
+ def validate_data(self, data: Dict[str, List[float]], required_keys: List[str]) -> bool:
62
+ """
63
+ Validate that required data keys are present.
64
+
65
+ Args:
66
+ data: Data dictionary
67
+ required_keys: List of required keys
68
+
69
+ Returns:
70
+ True if all required keys present
71
+ """
72
+ return all(key in data for key in required_keys)
73
+
74
+
75
+ class Feature(BaseFeature):
76
+ """
77
+ Simple feature implementation.
78
+
79
+ Use this for simple features that don't need complex logic.
80
+ """
81
+
82
+ def __init__(
83
+ self,
84
+ name: str,
85
+ compute_fn: callable,
86
+ description: str = "",
87
+ **kwargs
88
+ ):
89
+ """
90
+ Initialize feature with compute function.
91
+
92
+ Args:
93
+ name: Feature name
94
+ compute_fn: Function to compute feature: compute_fn(data) -> List[float]
95
+ description: Feature description
96
+ **kwargs: Additional parameters
97
+ """
98
+ super().__init__(name, description, **kwargs)
99
+ self.compute_fn = compute_fn
100
+
101
+ def compute(self, data: Dict[str, List[float]]) -> List[float]:
102
+ """Compute feature using provided function."""
103
+ return self.compute_fn(data, **self.params)
104
+
@@ -0,0 +1,124 @@
1
+ """
2
+ Overnight gap features for futures trading.
3
+ """
4
+
5
+ from typing import List, Dict
6
+ from quantml.features.base import BaseFeature, FeatureMetadata
7
+
8
+
9
+ class OvernightGapFeature(BaseFeature):
10
+ """
11
+ Overnight gap feature.
12
+
13
+ Computes the gap between previous day's close and current day's open.
14
+ """
15
+
16
+ def __init__(self, normalize: bool = True):
17
+ """
18
+ Initialize overnight gap feature.
19
+
20
+ Args:
21
+ normalize: Whether to normalize gap by previous close
22
+ """
23
+ super().__init__(
24
+ name="overnight_gap",
25
+ description="Gap between previous close and current open",
26
+ normalize=normalize
27
+ )
28
+ self.metadata.formula = "gap = (open(t) - close(t-1)) / close(t-1) if normalize else open(t) - close(t-1)"
29
+ self.metadata.expected_range = (-0.1, 0.1) if normalize else None
30
+ self.metadata.unit = "fraction" if normalize else "price_units"
31
+
32
+ def compute(self, data: Dict[str, List[float]]) -> List[float]:
33
+ """Compute overnight gaps."""
34
+ if not self.validate_data(data, ['open', 'close']):
35
+ raise ValueError("OvernightGapFeature requires 'open' and 'close' in data")
36
+
37
+ opens = data['open']
38
+ closes = data['close']
39
+
40
+ gaps = [0.0] # First gap is 0 (no previous close)
41
+
42
+ for i in range(1, len(opens)):
43
+ if closes[i-1] > 0:
44
+ if self.params.get('normalize', True):
45
+ gap = (opens[i] - closes[i-1]) / closes[i-1]
46
+ else:
47
+ gap = opens[i] - closes[i-1]
48
+ else:
49
+ gap = 0.0
50
+ gaps.append(gap)
51
+
52
+ return gaps
53
+
54
+
55
+ class GapSizeFeature(BaseFeature):
56
+ """
57
+ Gap size feature (absolute value of gap).
58
+ """
59
+
60
+ def __init__(self, normalize: bool = True):
61
+ """
62
+ Initialize gap size feature.
63
+
64
+ Args:
65
+ normalize: Whether to normalize by previous close
66
+ """
67
+ super().__init__(
68
+ name="gap_size",
69
+ description="Absolute size of overnight gap",
70
+ normalize=normalize
71
+ )
72
+ self.metadata.formula = "gap_size = |gap|"
73
+ self.metadata.expected_range = (0.0, 0.1) if normalize else None
74
+
75
+ def compute(self, data: Dict[str, List[float]]) -> List[float]):
76
+ """Compute gap sizes."""
77
+ gap_feature = OvernightGapFeature(normalize=self.params.get('normalize', True))
78
+ gaps = gap_feature.compute(data)
79
+ return [abs(g) for g in gaps]
80
+
81
+
82
+ class GapClosureFeature(BaseFeature):
83
+ """
84
+ Gap closure feature (whether gap closed during the day).
85
+ """
86
+
87
+ def __init__(self):
88
+ """Initialize gap closure feature."""
89
+ super().__init__(
90
+ name="gap_closure",
91
+ description="Binary indicator if overnight gap closed during day"
92
+ )
93
+ self.metadata.formula = "gap_closed = 1 if gap closed, 0 otherwise"
94
+ self.metadata.expected_range = (0, 1)
95
+
96
+ def compute(self, data: Dict[str, List[float]]) -> List[float]:
97
+ """Compute gap closure indicators."""
98
+ if not self.validate_data(data, ['open', 'close', 'high', 'low']):
99
+ raise ValueError("GapClosureFeature requires 'open', 'close', 'high', 'low'")
100
+
101
+ opens = data['open']
102
+ closes = data['close']
103
+ highs = data['high']
104
+ lows = data['low']
105
+
106
+ closures = [0.0] # First day has no gap
107
+
108
+ for i in range(1, len(opens)):
109
+ prev_close = closes[i-1]
110
+ gap = opens[i] - prev_close
111
+
112
+ if abs(gap) < 0.0001: # No gap
113
+ closures.append(0.0)
114
+ elif gap > 0: # Gap up
115
+ # Gap closed if low <= prev_close
116
+ closed = 1.0 if lows[i] <= prev_close else 0.0
117
+ closures.append(closed)
118
+ else: # Gap down
119
+ # Gap closed if high >= prev_close
120
+ closed = 1.0 if highs[i] >= prev_close else 0.0
121
+ closures.append(closed)
122
+
123
+ return closures
124
+
@@ -0,0 +1,138 @@
1
+ """
2
+ Feature registry for plugin-based feature system.
3
+ """
4
+
5
+ from typing import Dict, List, Optional, Callable
6
+ from quantml.features.base import BaseFeature, FeatureMetadata
7
+
8
+
9
+ class FeatureRegistry:
10
+ """Registry for managing features."""
11
+
12
+ _instance = None
13
+ _features: Dict[str, BaseFeature] = {}
14
+
15
+ def __new__(cls):
16
+ if cls._instance is None:
17
+ cls._instance = super().__new__(cls)
18
+ return cls._instance
19
+
20
+ def register(self, feature: BaseFeature, name: Optional[str] = None):
21
+ """
22
+ Register a feature.
23
+
24
+ Args:
25
+ feature: Feature instance
26
+ name: Optional name override (default: feature.name)
27
+ """
28
+ feature_name = name or feature.name
29
+ self._features[feature_name] = feature
30
+
31
+ def get(self, name: str) -> Optional[BaseFeature]:
32
+ """
33
+ Get feature by name.
34
+
35
+ Args:
36
+ name: Feature name
37
+
38
+ Returns:
39
+ Feature instance or None
40
+ """
41
+ return self._features.get(name)
42
+
43
+ def list_features(self) -> List[str]:
44
+ """List all registered feature names."""
45
+ return list(self._features.keys())
46
+
47
+ def get_all(self) -> Dict[str, BaseFeature]:
48
+ """Get all registered features."""
49
+ return self._features.copy()
50
+
51
+ def compute_features(
52
+ self,
53
+ feature_names: List[str],
54
+ data: Dict[str, List[float]]
55
+ ) -> Dict[str, List[float]]:
56
+ """
57
+ Compute multiple features.
58
+
59
+ Args:
60
+ feature_names: List of feature names to compute
61
+ data: Input data dictionary
62
+
63
+ Returns:
64
+ Dictionary of feature name -> feature values
65
+ """
66
+ results = {}
67
+
68
+ for name in feature_names:
69
+ feature = self.get(name)
70
+ if feature is None:
71
+ raise ValueError(f"Feature not found: {name}")
72
+
73
+ try:
74
+ values = feature.compute(data)
75
+ results[name] = values
76
+ except Exception as e:
77
+ raise RuntimeError(f"Error computing feature {name}: {e}")
78
+
79
+ return results
80
+
81
+ def get_metadata(self, name: str) -> Optional[FeatureMetadata]:
82
+ """
83
+ Get feature metadata.
84
+
85
+ Args:
86
+ name: Feature name
87
+
88
+ Returns:
89
+ FeatureMetadata or None
90
+ """
91
+ feature = self.get(name)
92
+ return feature.get_metadata() if feature else None
93
+
94
+
95
+ # Global registry instance
96
+ _registry = FeatureRegistry()
97
+
98
+
99
+ def register_feature(feature: BaseFeature, name: Optional[str] = None):
100
+ """
101
+ Register a feature in the global registry.
102
+
103
+ Args:
104
+ feature: Feature instance
105
+ name: Optional name override
106
+ """
107
+ _registry.register(feature, name)
108
+
109
+
110
+ def get_feature(name: str) -> Optional[BaseFeature]:
111
+ """
112
+ Get feature from global registry.
113
+
114
+ Args:
115
+ name: Feature name
116
+
117
+ Returns:
118
+ Feature instance or None
119
+ """
120
+ return _registry.get(name)
121
+
122
+
123
+ def compute_features(
124
+ feature_names: List[str],
125
+ data: Dict[str, List[float]]
126
+ ) -> Dict[str, List[float]]:
127
+ """
128
+ Compute multiple features from registry.
129
+
130
+ Args:
131
+ feature_names: List of feature names
132
+ data: Input data
133
+
134
+ Returns:
135
+ Dictionary of computed features
136
+ """
137
+ return _registry.compute_features(feature_names, data)
138
+
@@ -0,0 +1,140 @@
1
+ """
2
+ Volatility-based features.
3
+ """
4
+
5
+ from typing import List, Dict
6
+ from quantml.features.base import BaseFeature, FeatureMetadata
7
+
8
+
9
+ class VolatilityRegimeFeature(BaseFeature):
10
+ """
11
+ Volatility regime feature (low/normal/high volatility).
12
+ """
13
+
14
+ def __init__(self, window: int = 20, low_threshold: float = 0.25, high_threshold: float = 0.75):
15
+ """
16
+ Initialize volatility regime feature.
17
+
18
+ Args:
19
+ window: Window for volatility calculation
20
+ low_threshold: Percentile for low volatility
21
+ high_threshold: Percentile for high volatility
22
+ """
23
+ super().__init__(
24
+ name="volatility_regime",
25
+ description="Volatility regime (0=low, 1=normal, 2=high)",
26
+ window=window,
27
+ low_threshold=low_threshold,
28
+ high_threshold=high_threshold
29
+ )
30
+ self.metadata.formula = "regime based on realized volatility percentiles"
31
+ self.metadata.expected_range = (0, 2)
32
+
33
+ def compute(self, data: Dict[str, List[float]]) -> List[float]:
34
+ """Compute volatility regimes."""
35
+ if not self.validate_data(data, ['price']):
36
+ raise ValueError("VolatilityRegimeFeature requires 'price' in data")
37
+
38
+ prices = data['price']
39
+ window = self.params.get('window', 20)
40
+
41
+ # Calculate returns
42
+ returns = []
43
+ for i in range(1, len(prices)):
44
+ if prices[i-1] > 0:
45
+ ret = (prices[i] - prices[i-1]) / prices[i-1]
46
+ else:
47
+ ret = 0.0
48
+ returns.append(ret)
49
+
50
+ # Calculate rolling volatility
51
+ volatilities = []
52
+ for i in range(window, len(returns)):
53
+ window_rets = returns[i-window:i]
54
+ mean_ret = sum(window_rets) / len(window_rets)
55
+ variance = sum((r - mean_ret) ** 2 for r in window_rets) / len(window_rets)
56
+ vol = variance ** 0.5
57
+ volatilities.append(vol)
58
+
59
+ if len(volatilities) < 2:
60
+ return [1.0] * len(prices) # Default to normal
61
+
62
+ # Calculate percentiles
63
+ sorted_vols = sorted(volatilities)
64
+ low_idx = int(len(sorted_vols) * self.params['low_threshold'])
65
+ high_idx = int(len(sorted_vols) * self.params['high_threshold'])
66
+
67
+ low_threshold = sorted_vols[low_idx]
68
+ high_threshold = sorted_vols[high_idx]
69
+
70
+ # Map to regimes
71
+ regimes = [1.0] * window # Not enough data
72
+
73
+ for vol in volatilities:
74
+ if vol < low_threshold:
75
+ regimes.append(0.0)
76
+ elif vol >= high_threshold:
77
+ regimes.append(2.0)
78
+ else:
79
+ regimes.append(1.0)
80
+
81
+ return regimes
82
+
83
+
84
+ class RealizedVolatilityFeature(BaseFeature):
85
+ """
86
+ Realized volatility feature.
87
+ """
88
+
89
+ def __init__(self, window: int = 20, annualize: bool = True):
90
+ """
91
+ Initialize realized volatility feature.
92
+
93
+ Args:
94
+ window: Rolling window
95
+ annualize: Whether to annualize (multiply by sqrt(252))
96
+ """
97
+ super().__init__(
98
+ name="realized_volatility",
99
+ description="Realized volatility (std of returns)",
100
+ window=window,
101
+ annualize=annualize
102
+ )
103
+ self.metadata.formula = "vol = std(returns) * sqrt(252) if annualize"
104
+ self.metadata.expected_range = (0.0, 1.0)
105
+
106
+ def compute(self, data: Dict[str, List[float]]) -> List[float]:
107
+ """Compute realized volatility."""
108
+ if not self.validate_data(data, ['price']):
109
+ raise ValueError("RealizedVolatilityFeature requires 'price' in data")
110
+
111
+ prices = data['price']
112
+ window = self.params.get('window', 20)
113
+ annualize = self.params.get('annualize', True)
114
+
115
+ # Calculate returns
116
+ returns = []
117
+ for i in range(1, len(prices)):
118
+ if prices[i-1] > 0:
119
+ ret = (prices[i] - prices[i-1]) / prices[i-1]
120
+ else:
121
+ ret = 0.0
122
+ returns.append(ret)
123
+
124
+ # Calculate rolling volatility
125
+ volatilities = [0.0] * min(window, len(prices))
126
+
127
+ for i in range(window, len(returns)):
128
+ window_rets = returns[i-window:i]
129
+ mean_ret = sum(window_rets) / len(window_rets)
130
+ variance = sum((r - mean_ret) ** 2 for r in window_rets) / len(window_rets)
131
+ vol = variance ** 0.5
132
+
133
+ if annualize:
134
+ import math
135
+ vol = vol * math.sqrt(252)
136
+
137
+ volatilities.append(vol)
138
+
139
+ return volatilities
140
+
@@ -0,0 +1,142 @@
1
+ """
2
+ Volume-based features.
3
+ """
4
+
5
+ from typing import List, Dict
6
+ from quantml.features.base import BaseFeature, FeatureMetadata
7
+
8
+
9
+ class VolumeRegimeFeature(BaseFeature):
10
+ """
11
+ Volume regime feature (low/normal/high volume).
12
+ """
13
+
14
+ def __init__(self, low_threshold: float = 0.25, high_threshold: float = 0.75):
15
+ """
16
+ Initialize volume regime feature.
17
+
18
+ Args:
19
+ low_threshold: Percentile for low volume (default: 25th)
20
+ high_threshold: Percentile for high volume (default: 75th)
21
+ """
22
+ super().__init__(
23
+ name="volume_regime",
24
+ description="Volume regime classification (0=low, 1=normal, 2=high)",
25
+ low_threshold=low_threshold,
26
+ high_threshold=high_threshold
27
+ )
28
+ self.metadata.formula = "regime = 0 if volume < p25, 1 if p25 <= volume < p75, 2 if volume >= p75"
29
+ self.metadata.expected_range = (0, 2)
30
+
31
+ def compute(self, data: Dict[str, List[float]]) -> List[float]:
32
+ """Compute volume regimes."""
33
+ if not self.validate_data(data, ['volume']):
34
+ raise ValueError("VolumeRegimeFeature requires 'volume' in data")
35
+
36
+ volumes = data['volume']
37
+
38
+ if len(volumes) < 20:
39
+ return [1.0] * len(volumes) # Default to normal
40
+
41
+ # Calculate percentiles
42
+ sorted_vols = sorted(volumes)
43
+ low_idx = int(len(sorted_vols) * self.params['low_threshold'])
44
+ high_idx = int(len(sorted_vols) * self.params['high_threshold'])
45
+
46
+ low_threshold = sorted_vols[low_idx]
47
+ high_threshold = sorted_vols[high_idx]
48
+
49
+ regimes = []
50
+ for vol in volumes:
51
+ if vol < low_threshold:
52
+ regimes.append(0.0)
53
+ elif vol >= high_threshold:
54
+ regimes.append(2.0)
55
+ else:
56
+ regimes.append(1.0)
57
+
58
+ return regimes
59
+
60
+
61
+ class VolumeShockFeature(BaseFeature):
62
+ """
63
+ Volume shock feature (unusual volume).
64
+ """
65
+
66
+ def __init__(self, window: int = 20, threshold: float = 2.0):
67
+ """
68
+ Initialize volume shock feature.
69
+
70
+ Args:
71
+ window: Rolling window for volume average
72
+ threshold: Multiplier threshold for shock (default: 2x average)
73
+ """
74
+ super().__init__(
75
+ name="volume_shock",
76
+ description="Binary indicator for volume shock (1 if volume > threshold * avg)",
77
+ window=window,
78
+ threshold=threshold
79
+ )
80
+ self.metadata.formula = "shock = 1 if volume > threshold * rolling_mean(volume, window)"
81
+ self.metadata.expected_range = (0, 1)
82
+
83
+ def compute(self, data: Dict[str, List[float]]) -> List[float]:
84
+ """Compute volume shocks."""
85
+ if not self.validate_data(data, ['volume']):
86
+ raise ValueError("VolumeShockFeature requires 'volume' in data")
87
+
88
+ volumes = data['volume']
89
+ window = self.params.get('window', 20)
90
+ threshold = self.params.get('threshold', 2.0)
91
+
92
+ shocks = [0.0] * min(window, len(volumes)) # Not enough data
93
+
94
+ for i in range(window, len(volumes)):
95
+ window_vols = volumes[i-window:i]
96
+ avg_volume = sum(window_vols) / len(window_vols)
97
+
98
+ shock = 1.0 if volumes[i] > threshold * avg_volume else 0.0
99
+ shocks.append(shock)
100
+
101
+ return shocks
102
+
103
+
104
+ class VolumeRatioFeature(BaseFeature):
105
+ """
106
+ Volume ratio feature (current volume / average volume).
107
+ """
108
+
109
+ def __init__(self, window: int = 20):
110
+ """
111
+ Initialize volume ratio feature.
112
+
113
+ Args:
114
+ window: Rolling window for average volume
115
+ """
116
+ super().__init__(
117
+ name="volume_ratio",
118
+ description="Ratio of current volume to rolling average",
119
+ window=window
120
+ )
121
+ self.metadata.formula = "volume_ratio = volume(t) / mean(volume(t-window:t))"
122
+ self.metadata.expected_range = (0.0, 10.0)
123
+
124
+ def compute(self, data: Dict[str, List[float]]) -> List[float]:
125
+ """Compute volume ratios."""
126
+ if not self.validate_data(data, ['volume']):
127
+ raise ValueError("VolumeRatioFeature requires 'volume' in data")
128
+
129
+ volumes = data['volume']
130
+ window = self.params.get('window', 20)
131
+
132
+ ratios = [1.0] * min(window, len(volumes))
133
+
134
+ for i in range(window, len(volumes)):
135
+ window_vols = volumes[i-window:i]
136
+ avg_volume = sum(window_vols) / len(window_vols)
137
+
138
+ ratio = volumes[i] / avg_volume if avg_volume > 0 else 1.0
139
+ ratios.append(ratio)
140
+
141
+ return ratios
142
+
quantml/functional.py ADDED
@@ -0,0 +1,37 @@
1
+ """
2
+ Functional API for QuantML operations.
3
+
4
+ This module provides a convenient functional interface to all operations,
5
+ similar to PyTorch's functional API. Use this for a cleaner API when
6
+ composing operations.
7
+ """
8
+
9
+ from typing import Union, Optional
10
+ from quantml.tensor import Tensor
11
+ from quantml import ops
12
+
13
+ # Re-export all operations with F. prefix
14
+ add = ops.add
15
+ sub = ops.sub
16
+ mul = ops.mul
17
+ div = ops.div
18
+ pow = ops.pow
19
+ matmul = ops.matmul
20
+ dot = ops.dot
21
+ sum = ops.sum
22
+ mean = ops.mean
23
+ std = ops.std
24
+ relu = ops.relu
25
+ sigmoid = ops.sigmoid
26
+ tanh = ops.tanh
27
+ abs = ops.abs
28
+ maximum = ops.maximum
29
+
30
+ __all__ = [
31
+ 'add', 'sub', 'mul', 'div', 'pow',
32
+ 'matmul', 'dot',
33
+ 'sum', 'mean', 'std',
34
+ 'relu', 'sigmoid', 'tanh',
35
+ 'abs', 'maximum'
36
+ ]
37
+