quantred 2.4.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 (117) hide show
  1. quantred/__init__.py +24 -0
  2. quantred/alphas/__init__.py +53 -0
  3. quantred/alphas/base.py +221 -0
  4. quantred/alphas/combine.py +260 -0
  5. quantred/alphas/horizon.py +153 -0
  6. quantred/alphas/refine.py +300 -0
  7. quantred/alphas/scorers.py +87 -0
  8. quantred/analytics/attribution.py +354 -0
  9. quantred/analytics/bootstrap.py +353 -0
  10. quantred/analytics/charts.py +393 -0
  11. quantred/analytics/excursion.py +185 -0
  12. quantred/analytics/execution_quality.py +180 -0
  13. quantred/analytics/exposure.py +167 -0
  14. quantred/analytics/htmlreport.py +1208 -0
  15. quantred/analytics/information.py +197 -0
  16. quantred/analytics/metrics.py +502 -0
  17. quantred/analytics/performance.py +639 -0
  18. quantred/analytics/reporting.py +1672 -0
  19. quantred/analytics/screening.py +190 -0
  20. quantred/analytics/series_comparison.py +303 -0
  21. quantred/analytics/trade_analytics.py +353 -0
  22. quantred/brokers/alpaca/broker.py +415 -0
  23. quantred/brokers/alpaca/factory.py +33 -0
  24. quantred/brokers/alpaca/market_data.py +166 -0
  25. quantred/brokers/base.py +220 -0
  26. quantred/brokers/dryrun.py +164 -0
  27. quantred/brokers/errors.py +70 -0
  28. quantred/cli.py +8619 -0
  29. quantred/costs/__init__.py +11 -0
  30. quantred/costs/base.py +126 -0
  31. quantred/costs/parametric.py +131 -0
  32. quantred/data/__init__.py +29 -0
  33. quantred/data/compute.py +332 -0
  34. quantred/data/edges.py +81 -0
  35. quantred/data/features.py +120 -0
  36. quantred/data/panel.py +61 -0
  37. quantred/data/scan.py +85 -0
  38. quantred/data/store.py +269 -0
  39. quantred/demo/__init__.py +22 -0
  40. quantred/demo/scanners.py +104 -0
  41. quantred/demo/strategies.py +125 -0
  42. quantred/engine/backtest.py +1206 -0
  43. quantred/engine/barcheck.py +228 -0
  44. quantred/engine/live.py +539 -0
  45. quantred/execution/decision.py +192 -0
  46. quantred/execution/flatten.py +302 -0
  47. quantred/execution/halt.py +165 -0
  48. quantred/execution/ledger.py +758 -0
  49. quantred/execution/live_trader.py +587 -0
  50. quantred/execution/order_id.py +83 -0
  51. quantred/execution/sizing.py +114 -0
  52. quantred/indicators/indicators.py +158 -0
  53. quantred/marketdata/base.py +62 -0
  54. quantred/marketdata/client.py +59 -0
  55. quantred/marketdata/session.py +112 -0
  56. quantred/marketdata/synthetic.py +74 -0
  57. quantred/marketdata/timeframe.py +125 -0
  58. quantred/mcp/__init__.py +6 -0
  59. quantred/mcp/selftest.py +139 -0
  60. quantred/mcp/server.py +2419 -0
  61. quantred/optimization/causality.py +607 -0
  62. quantred/optimization/config_store.py +300 -0
  63. quantred/optimization/optimizer.py +431 -0
  64. quantred/optimization/parallel.py +345 -0
  65. quantred/optimization/param_space.py +309 -0
  66. quantred/optimization/walk_forward.py +1254 -0
  67. quantred/portfolio/allocator.py +135 -0
  68. quantred/portfolio/benchmark.py +109 -0
  69. quantred/portfolio/optimizer.py +786 -0
  70. quantred/portfolio/policy.py +335 -0
  71. quantred/portfolio/posterior.py +208 -0
  72. quantred/research/__init__.py +30 -0
  73. quantred/research/agent.py +868 -0
  74. quantred/research/demo_proposals.py +222 -0
  75. quantred/research/llm.py +311 -0
  76. quantred/research/proposer.py +234 -0
  77. quantred/research/provider_errors.py +242 -0
  78. quantred/research/sandbox.py +340 -0
  79. quantred/risk/__init__.py +60 -0
  80. quantred/risk/base.py +218 -0
  81. quantred/risk/conditional.py +474 -0
  82. quantred/risk/exposures.py +88 -0
  83. quantred/risk/factor.py +119 -0
  84. quantred/risk/sample.py +89 -0
  85. quantred/risk/streaming.py +214 -0
  86. quantred/scanners/base.py +141 -0
  87. quantred/scanners/symbol_scanner.py +148 -0
  88. quantred/services/__init__.py +51 -0
  89. quantred/services/analysis.py +6082 -0
  90. quantred/services/audit.py +574 -0
  91. quantred/services/campaign.py +381 -0
  92. quantred/services/configs.py +282 -0
  93. quantred/services/data.py +172 -0
  94. quantred/services/dryrun.py +206 -0
  95. quantred/services/glossary.py +130 -0
  96. quantred/services/maintenance.py +314 -0
  97. quantred/services/registry.py +351 -0
  98. quantred/services/setup.py +1023 -0
  99. quantred/services/sizing.py +150 -0
  100. quantred/services/smallreal.py +450 -0
  101. quantred/settings.py +571 -0
  102. quantred/store/__init__.py +0 -0
  103. quantred/store/bars.py +660 -0
  104. quantred/store/trials.py +1804 -0
  105. quantred/strategies/base.py +643 -0
  106. quantred/strategies/signals.py +28 -0
  107. quantred/utils/errors.py +48 -0
  108. quantred/utils/extras.py +330 -0
  109. quantred/utils/logging_config.py +33 -0
  110. quantred/utils/numeric.py +46 -0
  111. quantred/utils/streaming.py +474 -0
  112. quantred/utils/timeutils.py +148 -0
  113. quantred-2.4.0.dist-info/METADATA +632 -0
  114. quantred-2.4.0.dist-info/RECORD +117 -0
  115. quantred-2.4.0.dist-info/WHEEL +4 -0
  116. quantred-2.4.0.dist-info/entry_points.txt +8 -0
  117. quantred-2.4.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,104 @@
1
+ """The example scanner: volume and price-move confirmation.
2
+
3
+ Flags symbols whose latest bar shows unusually high volume *and* a meaningful price
4
+ move - a simple liquidity/attention filter for building a trading universe. Pure
5
+ pandas/numpy; no TA-Lib.
6
+
7
+ **A demonstration, not a screen to trade on.** It reaches the engine through the
8
+ ``quantred.scanners`` entry-point group in this project's ``pyproject.toml``, which is
9
+ the same mechanism a private pack uses. Yours belongs in your own package -
10
+ ``quantred init --example-pack ./my-signals`` copies one you own.
11
+ """
12
+
13
+ from typing import Any, ClassVar, Dict
14
+
15
+ import pandas as pd
16
+
17
+ from quantred.scanners.base import SCANNER_BUY, SCANNER_HOLD, SCANNER_SELL, ScannerStrategy
18
+
19
+
20
+ class DemoVolumeScanner(ScannerStrategy):
21
+ """Select symbols on a volume spike confirmed by price movement."""
22
+
23
+ #: A shipped demonstration, not a screen anyone should trade on. Carried on
24
+ #: the class so it survives being registered by entry point.
25
+ DEMO = True
26
+
27
+ TIMEFRAME = "1Day"
28
+
29
+ PARAM_RANGES: ClassVar[Dict[str, Dict[str, Any]]] = {
30
+ "min_volume": {
31
+ "type": "float",
32
+ "min": 50_000,
33
+ "max": 1_000_000,
34
+ "step": 50_000,
35
+ "default": 500_000,
36
+ "description": "Minimum bar volume to consider a symbol liquid",
37
+ },
38
+ "volume_ma_period": {
39
+ "type": "int",
40
+ "min": 5,
41
+ "max": 50,
42
+ "step": 5,
43
+ "default": 10,
44
+ "description": "Lookback for the volume moving-average baseline",
45
+ },
46
+ "volume_threshold": {
47
+ "type": "float",
48
+ "min": 1.0,
49
+ "max": 5.0,
50
+ "step": 0.25,
51
+ "default": 1.75,
52
+ "description": "Volume / volume-MA ratio that flags unusual activity",
53
+ },
54
+ "price_change_threshold": {
55
+ "type": "float",
56
+ "min": 0.5,
57
+ "max": 5.0,
58
+ "step": 0.25,
59
+ "default": 0.5,
60
+ "description": "Minimum |intrabar price change| (%) to confirm",
61
+ },
62
+ }
63
+
64
+ def initialize(self) -> None: # nothing to set up
65
+ pass
66
+
67
+ def required_data_points(self) -> int:
68
+ return self.config["volume_ma_period"] + 1
69
+
70
+ def process_data(self, data: pd.DataFrame) -> pd.DataFrame:
71
+ if data.empty:
72
+ return pd.DataFrame()
73
+
74
+ enriched = data.copy()
75
+ volume_ma = data["volume"].rolling(window=self.config["volume_ma_period"]).mean()
76
+ enriched["volume_ratio"] = data["volume"] / volume_ma
77
+ enriched["price_change"] = ((data["close"] - data["open"]) / data["open"] * 100).abs()
78
+ return enriched
79
+
80
+ def generate_signals_df(self, data: pd.DataFrame) -> pd.DataFrame:
81
+ if data.empty:
82
+ return pd.DataFrame()
83
+
84
+ out = pd.DataFrame(index=data.index)
85
+ out["signal"] = SCANNER_HOLD
86
+ out["signal_strength"] = 0.0
87
+
88
+ qualifies = (
89
+ (data["volume_ratio"] > self.config["volume_threshold"])
90
+ & (data["price_change"] > self.config["price_change_threshold"])
91
+ & (data["volume"] > self.config["min_volume"])
92
+ )
93
+ bullish = qualifies & (data["close"] > data["open"])
94
+ bearish = qualifies & (data["close"] <= data["open"])
95
+
96
+ out.loc[bullish, "signal"] = SCANNER_BUY
97
+ out.loc[bearish, "signal"] = SCANNER_SELL
98
+ out.loc[qualifies, "signal_strength"] = (
99
+ data.loc[qualifies, "volume_ratio"] * data.loc[qualifies, "price_change"]
100
+ )
101
+ return out
102
+
103
+ def is_hit(self, returns: float, price_change: float) -> bool:
104
+ return price_change > self.config["price_change_threshold"]
@@ -0,0 +1,125 @@
1
+ """The example strategy: a moving-average crossover, the "hello world" of trend following.
2
+
3
+ **This ships so the demo has something to run and so there is a smallest complete
4
+ strategy to read. It is not an edge, and it is not where your work goes** — that belongs
5
+ in your own package. ``quantred init --example-pack ./my-signals`` copies a complete one
6
+ you own, with two strategies, a scanner and configs already wired up.
7
+
8
+ It reaches the engine through the ``quantred.strategies`` entry-point group declared in
9
+ this project's ``pyproject.toml`` — the same mechanism your pack will use, so the path
10
+ is exercised by every install rather than only in tests.
11
+
12
+ Long-only. Its conviction score is the **normalized EMA gap**
13
+ ``(fast - slow) / slow``: positive (and rising) when the fast line leads, negative
14
+ when it lags. The base class derives the trade signal from the score's sign, so a
15
+ golden cross (score crossing above 0) becomes a ``BUY`` and a death cross (crossing
16
+ below 0) a ``CLOSE_BUY`` - the discrete behavior is a consequence of the score, not
17
+ a second code path. Deliberately simple and only five parameters, so it's an honest
18
+ baseline - and a clean example of how little it takes to add a strategy (one file,
19
+ a score, the indicators you already have, register the name).
20
+ """
21
+
22
+ from typing import Any, ClassVar, Dict, Tuple
23
+
24
+ import pandas as pd
25
+
26
+ from quantred.indicators import indicators
27
+ from quantred.strategies.base import Strategy
28
+
29
+
30
+ class DemoTrendStrategy(Strategy):
31
+ """Long-only EMA trend follower: buy the golden cross, exit the death cross."""
32
+
33
+ #: Marks this as a shipped demonstration rather than something to trade. Carried on
34
+ #: the class rather than inferred from where it was registered, because how a
35
+ #: strategy was discovered says nothing about what it is for - and after this moved
36
+ #: to an entry point, registry membership stopped being able to tell.
37
+ DEMO = True
38
+
39
+ #: Bars this strategy is designed to trade.
40
+ TIMEFRAME = "1Day"
41
+
42
+ PARAM_RANGES: ClassVar[Dict[str, Dict[str, Any]]] = {
43
+ "fast_ema_period": {
44
+ "type": "int",
45
+ "min": 5,
46
+ "max": 20,
47
+ "step": 1,
48
+ "default": 10,
49
+ "description": "Fast EMA period (the responsive line)",
50
+ },
51
+ "slow_ema_period": {
52
+ "type": "int",
53
+ "min": 21,
54
+ "max": 60,
55
+ "step": 1,
56
+ "default": 30,
57
+ "description": "Slow EMA period (the trend baseline)",
58
+ },
59
+ "risk_per_trade": {
60
+ "type": "float",
61
+ "min": 0.01,
62
+ "max": 0.05,
63
+ "step": 0.01,
64
+ "default": 0.02,
65
+ "description": "Capital fraction risked per trade",
66
+ },
67
+ "stop_loss": {
68
+ "type": "float",
69
+ "min": 0.01,
70
+ "max": 0.08,
71
+ "step": 0.01,
72
+ "default": 0.03,
73
+ "description": "Stop-loss distance from entry (fraction)",
74
+ },
75
+ "take_profit": {
76
+ "type": "float",
77
+ "min": 0.02,
78
+ "max": 0.15,
79
+ "step": 0.01,
80
+ "default": 0.06,
81
+ "description": "Take-profit distance from entry (fraction)",
82
+ },
83
+ }
84
+
85
+ #: The fast line has to be faster than the slow one. Declared rather than checked
86
+ #: in ``initialize`` so a sampler can read it: today's ranges (5-20 against 21-60)
87
+ #: cannot produce a violation, but narrowed ranges can, and a combination that
88
+ #: gets evaluated is a journaled trial that raises the deflation bar for every
89
+ #: future candidate in this family.
90
+ PARAM_CONSTRAINTS: ClassVar[Tuple[Tuple[str, str, Any], ...]] = (
91
+ ("fast_ema_period", "<", "slow_ema_period"),
92
+ )
93
+
94
+ def __init__(self, config: Dict[str, Any]):
95
+ config["timeframe"] = self.TIMEFRAME
96
+ config.setdefault(
97
+ "position_limits",
98
+ {"max_positions": 1, "max_position_size": 100_000.0, "max_total_risk": 0.05},
99
+ )
100
+ super().__init__(config)
101
+
102
+ def calculate_required_lookback(self) -> int:
103
+ return self.config["slow_ema_period"] + 1
104
+
105
+ def initialize(self) -> None:
106
+ # The fast/slow ordering is declared in PARAM_CONSTRAINTS and enforced at
107
+ # construction, so there is nothing left to restate here: a second copy of the
108
+ # comparison would be the half a sampler cannot read.
109
+ pass
110
+
111
+ def process_data(self, data: pd.DataFrame) -> pd.DataFrame:
112
+ if data.empty:
113
+ return pd.DataFrame()
114
+
115
+ enriched = data.copy()
116
+ enriched["fast_ema"] = indicators.calculate_ema(data["close"], self.config["fast_ema_period"])
117
+ enriched["slow_ema"] = indicators.calculate_ema(data["close"], self.config["slow_ema_period"])
118
+ return enriched
119
+
120
+ def calculate_scores(self, data: pd.DataFrame) -> pd.Series:
121
+ if data.empty:
122
+ return pd.Series(dtype=float)
123
+ # Normalized EMA gap: signed trend strength. Sign crossings are the golden /
124
+ # death crosses; magnitude is how decisively the fast line leads.
125
+ return (data["fast_ema"] - data["slow_ema"]) / data["slow_ema"]