amquant 0.1.0__tar.gz

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.
amquant-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.4
2
+ Name: amquant
3
+ Version: 0.1.0
4
+ Summary: A lightweight library for loading market data and computing engineered features.
5
+ Author-email: Your Name <you@example.com>
6
+ License: MIT
7
+ Keywords: quant,finance,market-data,features,trading
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: requests>=2.31
11
+ Requires-Dist: pandas>=2.0
12
+
13
+ **activate the virtual env with** : source venv/bin/activate
14
+
15
+ **interactive program** :python3 -i main.py
16
+
17
+ **DATA ACCESS:**
18
+
19
+ After `main()` executes in the same Python session:
20
+
21
+ - `RAW_SERIES: dict[str, Series]` — populated with fully enriched `Series` objects (OHLCV + metadata).
22
+ - `FEATURE_SETS: dict[str, FeatureSet]` — contains computed technical features per symbol.
23
+
24
+ Both dicts are module-level globals, accessible immediately post-run via `RAW_SERIES['BIAT']` or `FEATURE_SETS['MC']`.
25
+
26
+ Data lives in memory for the duration of the Python process / REPL session. Not persisted to disk unless explicitly saved.
27
+
28
+ For library use: expose `RAW_SERIES` and `FEATURE_SETS` via a `DataManager` singleton or return them from `load_universe()` for clean dependency injection.
29
+
30
+ **demo**
31
+
32
+ list(RAW_SERIES.keys())
33
+ RAW_SERIES["BNP"].bars[0]
34
+ FEATURE_SETS["BNP"].ret_1d[:5]
35
+ FEATURE_SETS["BNP"].sma_20[:5]
36
+ FEATURE_SETS["BNP"].__dict__.keys()
37
+ len(RAW_SERIES["BNP"].bars)
38
+ len(RAW_SERIES)
39
+ len(FEATURE_SETS)
40
+
41
+ # AmQuant
42
+
@@ -0,0 +1,30 @@
1
+ **activate the virtual env with** : source venv/bin/activate
2
+
3
+ **interactive program** :python3 -i main.py
4
+
5
+ **DATA ACCESS:**
6
+
7
+ After `main()` executes in the same Python session:
8
+
9
+ - `RAW_SERIES: dict[str, Series]` — populated with fully enriched `Series` objects (OHLCV + metadata).
10
+ - `FEATURE_SETS: dict[str, FeatureSet]` — contains computed technical features per symbol.
11
+
12
+ Both dicts are module-level globals, accessible immediately post-run via `RAW_SERIES['BIAT']` or `FEATURE_SETS['MC']`.
13
+
14
+ Data lives in memory for the duration of the Python process / REPL session. Not persisted to disk unless explicitly saved.
15
+
16
+ For library use: expose `RAW_SERIES` and `FEATURE_SETS` via a `DataManager` singleton or return them from `load_universe()` for clean dependency injection.
17
+
18
+ **demo**
19
+
20
+ list(RAW_SERIES.keys())
21
+ RAW_SERIES["BNP"].bars[0]
22
+ FEATURE_SETS["BNP"].ret_1d[:5]
23
+ FEATURE_SETS["BNP"].sma_20[:5]
24
+ FEATURE_SETS["BNP"].__dict__.keys()
25
+ len(RAW_SERIES["BNP"].bars)
26
+ len(RAW_SERIES)
27
+ len(FEATURE_SETS)
28
+
29
+ # AmQuant
30
+
@@ -0,0 +1,4 @@
1
+ from amquant.loader import load_market_data, RAW_SERIES, FEATURE_SETS, LoadResult
2
+
3
+ __version__ = "0.1.0"
4
+ __all__ = ["load_market_data", "RAW_SERIES", "FEATURE_SETS", "LoadResult"]
@@ -0,0 +1,28 @@
1
+ """Shared data model. Mirrors include/qde/bar.hpp.
2
+
3
+ Everything downstream (csv_io, features) depends only on this module,
4
+ same as in the C++ version -- it's the one type both Yahoo-sourced and
5
+ manually-loaded (BVMT) data get normalized into.
6
+ """
7
+ from dataclasses import dataclass, field
8
+
9
+
10
+ @dataclass
11
+ class Bar:
12
+ """One trading day (or bar) of market data for a single instrument."""
13
+ timestamp_utc: int = 0 # unix seconds
14
+ open: float = 0.0
15
+ high: float = 0.0
16
+ low: float = 0.0
17
+ close: float = 0.0
18
+ adj_close: float = 0.0 # dividend/split adjusted; falls back to close if unknown
19
+ volume: float = 0.0
20
+
21
+
22
+ @dataclass
23
+ class Series:
24
+ """Raw series for one instrument."""
25
+ symbol: str = ""
26
+ exchange: str = "" # e.g. "BVMT", "EURONEXT_PARIS", "XETRA"
27
+ country: str = "" # e.g. "TN", "FR", "DE"
28
+ bars: list = field(default_factory=list) # list[Bar], ascending by timestamp
@@ -0,0 +1,173 @@
1
+ """Feature engineering. Mirrors include/qde/features.hpp + src/features.cpp.
2
+
3
+ Deliberately hand-rolled (not pandas/ta-lib) so the math is a line-for-line
4
+ match with the C++ version -- same Wilder smoothing for RSI/ATR, same
5
+ rolling-sum trick for the moving average, same annualization factor.
6
+ """
7
+ from __future__ import annotations
8
+ import math
9
+ from dataclasses import dataclass, field
10
+
11
+ from amquant.bar import Series
12
+
13
+ NaN = float("nan")
14
+
15
+
16
+ @dataclass
17
+ class FeatureSet:
18
+ """All lists are the same length as series.bars. Early entries in any
19
+ rolling-window feature are NaN until the window fills."""
20
+ timestamp: list = field(default_factory=list)
21
+ ret_1d: list = field(default_factory=list)
22
+ log_ret_1d: list = field(default_factory=list)
23
+ sma_5: list = field(default_factory=list)
24
+ sma_20: list = field(default_factory=list)
25
+ sma_50: list = field(default_factory=list)
26
+ ema_12: list = field(default_factory=list)
27
+ ema_26: list = field(default_factory=list)
28
+ macd: list = field(default_factory=list)
29
+ macd_signal: list = field(default_factory=list)
30
+ rsi_14: list = field(default_factory=list)
31
+ vol_20: list = field(default_factory=list)
32
+ bb_upper_20: list = field(default_factory=list)
33
+ bb_lower_20: list = field(default_factory=list)
34
+ atr_14: list = field(default_factory=list)
35
+ momentum_10: list = field(default_factory=list)
36
+ volume_zscore_20: list = field(default_factory=list)
37
+
38
+
39
+ def _rolling_mean(x: list, window: int) -> list:
40
+ out = [NaN] * len(x)
41
+ s = 0.0
42
+ for i, v in enumerate(x):
43
+ s += v
44
+ if i >= window:
45
+ s -= x[i - window]
46
+ if i >= window - 1:
47
+ out[i] = s / window
48
+ return out
49
+
50
+
51
+ def _rolling_std(x: list, window: int) -> list:
52
+ out = [NaN] * len(x)
53
+ for i in range(window - 1, len(x)):
54
+ window_vals = x[i - window + 1: i + 1]
55
+ mean = sum(window_vals) / window
56
+ var = sum((v - mean) ** 2 for v in window_vals) / max(window - 1, 1)
57
+ out[i] = math.sqrt(var)
58
+ return out
59
+
60
+
61
+ def _ema(x: list, window: int) -> list:
62
+ out = [NaN] * len(x)
63
+ if not x:
64
+ return out
65
+ alpha = 2.0 / (window + 1.0)
66
+ prev = NaN
67
+ for i, v in enumerate(x):
68
+ if math.isnan(v):
69
+ out[i] = prev
70
+ continue
71
+ prev = v if math.isnan(prev) else alpha * v + (1 - alpha) * prev
72
+ out[i] = prev
73
+ return out
74
+
75
+
76
+ def compute_features(series: Series) -> FeatureSet:
77
+ bars = series.bars
78
+ n = len(bars)
79
+ f = FeatureSet()
80
+ f.timestamp = [b.timestamp_utc for b in bars]
81
+
82
+ close = [b.adj_close if b.adj_close != 0.0 else b.close for b in bars]
83
+ volume = [b.volume for b in bars]
84
+ high = [b.high for b in bars]
85
+ low = [b.low for b in bars]
86
+
87
+ f.ret_1d = [NaN] * n
88
+ f.log_ret_1d = [NaN] * n
89
+ for i in range(1, n):
90
+ if close[i - 1] > 0:
91
+ f.ret_1d[i] = close[i] / close[i - 1] - 1.0
92
+ f.log_ret_1d[i] = math.log(close[i] / close[i - 1])
93
+
94
+ f.sma_5 = _rolling_mean(close, 5)
95
+ f.sma_20 = _rolling_mean(close, 20)
96
+ f.sma_50 = _rolling_mean(close, 50)
97
+ f.ema_12 = _ema(close, 12)
98
+ f.ema_26 = _ema(close, 26)
99
+
100
+ f.macd = [NaN] * n
101
+ for i in range(n):
102
+ if not math.isnan(f.ema_12[i]) and not math.isnan(f.ema_26[i]):
103
+ f.macd[i] = f.ema_12[i] - f.ema_26[i]
104
+ f.macd_signal = _ema(f.macd, 9)
105
+
106
+ # RSI-14, Wilder's smoothing (not a naive SMA of gains/losses)
107
+ f.rsi_14 = [NaN] * n
108
+ window = 14
109
+ avg_gain = 0.0
110
+ avg_loss = 0.0
111
+ for i in range(1, n):
112
+ change = close[i] - close[i - 1]
113
+ gain = change if change > 0 else 0.0
114
+ loss = -change if change < 0 else 0.0
115
+ if i <= window:
116
+ avg_gain += gain / window
117
+ avg_loss += loss / window
118
+ if i == window:
119
+ rs = math.inf if avg_loss == 0.0 else avg_gain / avg_loss
120
+ f.rsi_14[i] = 100.0 - (100.0 / (1.0 + rs))
121
+ else:
122
+ avg_gain = (avg_gain * (window - 1) + gain) / window
123
+ avg_loss = (avg_loss * (window - 1) + loss) / window
124
+ rs = math.inf if avg_loss == 0.0 else avg_gain / avg_loss
125
+ f.rsi_14[i] = 100.0 - (100.0 / (1.0 + rs))
126
+
127
+ # Rolling volatility of log returns, annualized (sqrt(252))
128
+ f.vol_20 = _rolling_std(f.log_ret_1d, 20)
129
+ f.vol_20 = [v * math.sqrt(252.0) if not math.isnan(v) else v for v in f.vol_20]
130
+
131
+ # Bollinger bands (20, 2 std) on raw price std
132
+ raw_std_20 = _rolling_std(close, 20)
133
+ f.bb_upper_20 = [NaN] * n
134
+ f.bb_lower_20 = [NaN] * n
135
+ for i in range(n):
136
+ if not math.isnan(f.sma_20[i]) and not math.isnan(raw_std_20[i]):
137
+ f.bb_upper_20[i] = f.sma_20[i] + 2 * raw_std_20[i]
138
+ f.bb_lower_20[i] = f.sma_20[i] - 2 * raw_std_20[i]
139
+
140
+ # ATR-14, Wilder smoothing on true range
141
+ f.atr_14 = [NaN] * n
142
+ tr = [NaN] * n
143
+ for i in range(1, n):
144
+ a = high[i] - low[i]
145
+ b = abs(high[i] - close[i - 1])
146
+ c = abs(low[i] - close[i - 1])
147
+ tr[i] = max(a, b, c)
148
+ atr = 0.0
149
+ window = 14
150
+ for i in range(1, n):
151
+ if i <= window:
152
+ atr += tr[i] / window
153
+ if i == window:
154
+ f.atr_14[i] = atr
155
+ else:
156
+ atr = (atr * (window - 1) + tr[i]) / window
157
+ f.atr_14[i] = atr
158
+
159
+ # Momentum-10
160
+ f.momentum_10 = [NaN] * n
161
+ for i in range(10, n):
162
+ if close[i - 10] > 0:
163
+ f.momentum_10[i] = close[i] / close[i - 10] - 1.0
164
+
165
+ # Volume z-score over 20d window
166
+ vol_mean_20 = _rolling_mean(volume, 20)
167
+ vol_std_20 = _rolling_std(volume, 20)
168
+ f.volume_zscore_20 = [NaN] * n
169
+ for i in range(n):
170
+ if not math.isnan(vol_mean_20[i]) and not math.isnan(vol_std_20[i]) and vol_std_20[i] > 0:
171
+ f.volume_zscore_20[i] = (volume[i] - vol_mean_20[i]) / vol_std_20[i]
172
+
173
+ return f
@@ -0,0 +1,37 @@
1
+ """Thin HTTP GET wrapper. Mirrors include/qde/http_client.hpp + src/http_client.cpp.
2
+
3
+ Nothing finance-specific here on purpose -- same reasoning as the C++ version:
4
+ if you swap Yahoo for another vendor later, this file doesn't change.
5
+ """
6
+ from dataclasses import dataclass
7
+ import requests
8
+
9
+
10
+ @dataclass
11
+ class HttpResponse:
12
+ status_code: int = 0
13
+ body: str = ""
14
+
15
+ def ok(self) -> bool:
16
+ return 200 <= self.status_code < 300
17
+
18
+
19
+ class HttpClient:
20
+ """Equivalent of qde::HttpClient. requests already handles connection
21
+ pooling / redirects / TLS verification, so this wrapper is thinner than
22
+ its C++ counterpart -- but kept as its own file for the same reason:
23
+ isolate networking from parsing logic.
24
+ """
25
+
26
+ def __init__(self, user_agent: str = "Mozilla/5.0 (X11; Linux x86_64) qde-data-engine/1.0"):
27
+ self.user_agent = user_agent
28
+
29
+ def get(self, url: str, headers: dict | None = None, timeout: int = 20) -> HttpResponse:
30
+ req_headers = {"User-Agent": self.user_agent}
31
+ if headers:
32
+ req_headers.update(headers)
33
+ try:
34
+ resp = requests.get(url, headers=req_headers, timeout=timeout)
35
+ except requests.RequestException as e:
36
+ raise RuntimeError(f"request failed: {e}") from e
37
+ return HttpResponse(status_code=resp.status_code, body=resp.text)
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+ import sys
3
+ import time
4
+ from dataclasses import dataclass, field
5
+
6
+ from amquant.universe_data import UNIVERSE
7
+ from amquant.manual_data import MANUAL_SERIES
8
+ from amquant.yahoo_finance import YahooFinanceClient
9
+ from amquant.features import compute_features, FeatureSet
10
+ from amquant.bar import Series
11
+
12
+ RAW_SERIES: dict[str, Series] = {}
13
+ FEATURE_SETS: dict[str, FeatureSet] = {}
14
+
15
+
16
+ @dataclass
17
+ class LoadResult:
18
+ raw_series: dict[str, Series] = field(default_factory=dict)
19
+ feature_sets: dict[str, FeatureSet] = field(default_factory=dict)
20
+ ok: int = 0
21
+ failed: int = 0
22
+ skipped: int = 0
23
+
24
+
25
+ def load_market_data(
26
+ universe=None,
27
+ *,
28
+ yahoo_client: YahooFinanceClient | None = None,
29
+ sleep_between_calls: float = 0.3,
30
+ verbose: bool = True,
31
+ update_globals: bool = True,
32
+ ) -> LoadResult:
33
+ """
34
+ Fetch/assemble bar series for every instrument in `universe`, compute features,
35
+ and return them as a LoadResult. By default also mirrors results into
36
+ amquant.loader.RAW_SERIES / FEATURE_SETS for quick terminal inspection.
37
+ """
38
+ universe = universe if universe is not None else UNIVERSE
39
+ yahoo = yahoo_client if yahoo_client is not None else YahooFinanceClient()
40
+
41
+ result = LoadResult()
42
+
43
+ if verbose:
44
+ print(f"Loaded {len(universe)} instruments")
45
+
46
+ for inst in universe:
47
+ series = None
48
+ if inst.source == "yahoo" and inst.yahoo_symbol:
49
+ series = yahoo.fetch_history(inst.yahoo_symbol, "1y", "1d")
50
+ if sleep_between_calls:
51
+ time.sleep(sleep_between_calls)
52
+ else:
53
+ series = MANUAL_SERIES.get(inst.symbol)
54
+ if series is None and verbose:
55
+ print(f"[manual] {inst.symbol}: no entry in MANUAL_SERIES", file=sys.stderr)
56
+
57
+ if not series or not series.bars:
58
+ if verbose:
59
+ print(f"SKIP {inst.symbol} ({inst.exchange})", file=sys.stderr)
60
+ result.skipped += 1
61
+ continue
62
+
63
+ series.symbol = inst.symbol
64
+ series.exchange = inst.exchange
65
+ series.country = inst.country
66
+
67
+ try:
68
+ features = compute_features(series)
69
+ result.raw_series[inst.symbol] = series
70
+ result.feature_sets[inst.symbol] = features
71
+ if verbose:
72
+ print(f"OK {inst.symbol} ({inst.exchange}) -- {len(series.bars)} bars")
73
+ result.ok += 1
74
+ except Exception as e:
75
+ if verbose:
76
+ print(f"FAIL {inst.symbol}: {e}", file=sys.stderr)
77
+ result.failed += 1
78
+
79
+ if verbose:
80
+ print(f"\nDone. ok={result.ok} failed={result.failed} skipped={result.skipped}")
81
+
82
+ if update_globals:
83
+ RAW_SERIES.clear()
84
+ RAW_SERIES.update(result.raw_series)
85
+ FEATURE_SETS.clear()
86
+ FEATURE_SETS.update(result.feature_sets)
87
+
88
+ return result
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+ from amquant.bar import Bar, Series
3
+
4
+ MANUAL_SERIES: dict[str, Series] = {
5
+ "SFBT": Series(symbol="SFBT", bars=[
6
+ # TODO: paste real SFBT bars here
7
+ ]),
8
+ "BIAT": Series(symbol="BIAT", bars=[
9
+ # TODO: paste real BIAT bars here
10
+ ]),
11
+ "ATB": Series(symbol="ATB", bars=[
12
+ # TODO: paste real ATB bars here
13
+ ]),
14
+ "BT": Series(symbol="BT", bars=[
15
+ # TODO: paste real BT bars here
16
+ ]),
17
+ "BH": Series(symbol="BH", bars=[
18
+ # TODO: paste real BH bars here
19
+ ]),
20
+ "DELICE": Series(symbol="DELICE", bars=[
21
+ # TODO: paste real DELICE bars here
22
+ ]),
23
+ "SOTUVER": Series(symbol="SOTUVER", bars=[
24
+ # TODO: paste real SOTUVER bars here
25
+ ]),
26
+ "TLS": Series(symbol="TLS", bars=[
27
+ # TODO: paste real TLS bars here
28
+ ]),
29
+ "ENNAKL": Series(symbol="ENNAKL", bars=[
30
+ # TODO: paste real ENNAKL bars here
31
+ ]),
32
+ "PGH": Series(symbol="PGH", bars=[
33
+ # TODO: paste real PGH bars here
34
+ ]),
35
+ }
@@ -0,0 +1,14 @@
1
+ """Universe loader. Mirrors include/qde/universe.hpp + src/universe.cpp."""
2
+ from __future__ import annotations
3
+ import csv
4
+ from dataclasses import dataclass
5
+
6
+
7
+ @dataclass
8
+ class Instrument:
9
+ symbol: str = ""
10
+ yahoo_symbol: str = ""
11
+ name: str = ""
12
+ country: str = ""
13
+ exchange: str = ""
14
+ source: str = "" # "yahoo" or "manual_csv"
@@ -0,0 +1,98 @@
1
+ from __future__ import annotations
2
+ from amquant.universe import Instrument
3
+ UNIVERSE: list[Instrument] = [
4
+ # ========================= TUNISIAN GROUP (manual) =========================
5
+ Instrument(symbol="SFBT", yahoo_symbol="", name="Societe Frigorifique et Brasserie de Tunis", country="TN", exchange="BVMT", source="manual_csv"),
6
+ Instrument(symbol="BIAT", yahoo_symbol="", name="Banque Internationale Arabe de Tunisie", country="TN", exchange="BVMT", source="manual_csv"),
7
+ Instrument(symbol="ATB", yahoo_symbol="", name="Arab Tunisian Bank", country="TN", exchange="BVMT", source="manual_csv"),
8
+ Instrument(symbol="BT", yahoo_symbol="", name="Banque de Tunisie", country="TN", exchange="BVMT", source="manual_csv"),
9
+ Instrument(symbol="BH", yahoo_symbol="", name="BH Bank", country="TN", exchange="BVMT", source="manual_csv"),
10
+ Instrument(symbol="DELICE", yahoo_symbol="", name="Delice Holding", country="TN", exchange="BVMT", source="manual_csv"),
11
+ Instrument(symbol="SOTUVER", yahoo_symbol="", name="Societe Tunisienne de Verreries", country="TN", exchange="BVMT", source="manual_csv"),
12
+ Instrument(symbol="TLS", yahoo_symbol="", name="Tunisie Leasing and Factoring", country="TN", exchange="BVMT", source="manual_csv"),
13
+ Instrument(symbol="ENNAKL", yahoo_symbol="", name="Ennakl Automobiles", country="TN", exchange="BVMT", source="manual_csv"),
14
+ Instrument(symbol="PGH", yahoo_symbol="", name="Poulina Group Holding", country="TN", exchange="BVMT", source="manual_csv"),
15
+ Instrument(symbol="TJARI", yahoo_symbol="", name="Banque Attijari de Tunisie", country="TN", exchange="BVMT", source="manual_csv"),
16
+ Instrument(symbol="ARTES", yahoo_symbol="", name="ARTES Renault", country="TN", exchange="BVMT", source="manual_csv"),
17
+ Instrument(symbol="SITEX", yahoo_symbol="", name="Societe Industrielle des Textiles", country="TN", exchange="BVMT", source="manual_csv"),
18
+ Instrument(symbol="ASSMA", yahoo_symbol="", name="Assurances Maghrebia", country="TN", exchange="BVMT", source="manual_csv"),
19
+ Instrument(symbol="AB", yahoo_symbol="", name="Amen Bank", country="TN", exchange="BVMT", source="manual_csv"),
20
+
21
+ # ========================= FRENCH GROUP =========================
22
+ Instrument(symbol="MC", yahoo_symbol="MC.PA", name="LVMH", country="FR", exchange="EURONEXT_PARIS", source="yahoo"),
23
+ Instrument(symbol="TTE", yahoo_symbol="TTE.PA", name="TotalEnergies", country="FR", exchange="EURONEXT_PARIS", source="yahoo"),
24
+ Instrument(symbol="SAN", yahoo_symbol="SAN.PA", name="Sanofi", country="FR", exchange="EURONEXT_PARIS", source="yahoo"),
25
+ Instrument(symbol="OR", yahoo_symbol="OR.PA", name="L'Oreal", country="FR", exchange="EURONEXT_PARIS", source="yahoo"),
26
+ Instrument(symbol="AIR", yahoo_symbol="AIR.PA", name="Airbus", country="FR", exchange="EURONEXT_PARIS", source="yahoo"),
27
+ Instrument(symbol="BNP", yahoo_symbol="BNP.PA", name="BNP Paribas", country="FR", exchange="EURONEXT_PARIS", source="yahoo"),
28
+ Instrument(symbol="AI", yahoo_symbol="AI.PA", name="Air Liquide", country="FR", exchange="EURONEXT_PARIS", source="yahoo"),
29
+ Instrument(symbol="SU", yahoo_symbol="SU.PA", name="Schneider Electric", country="FR", exchange="EURONEXT_PARIS", source="yahoo"),
30
+ Instrument(symbol="BN", yahoo_symbol="BN.PA", name="Danone", country="FR", exchange="EURONEXT_PARIS", source="yahoo"),
31
+ Instrument(symbol="ML", yahoo_symbol="ML.PA", name="Michelin", country="FR", exchange="EURONEXT_PARIS", source="yahoo"),
32
+ Instrument(symbol="RMS", yahoo_symbol="RMS.PA", name="Hermes International", country="FR", exchange="EURONEXT_PARIS", source="yahoo"),
33
+ Instrument(symbol="ACCP", yahoo_symbol="AC.PA", name="Accor", country="FR", exchange="EURONEXT_PARIS", source="yahoo"),
34
+ Instrument(symbol="CS", yahoo_symbol="CS.PA", name="AXA", country="FR", exchange="EURONEXT_PARIS", source="yahoo"),
35
+ # French Market
36
+ Instrument(symbol="CAC40", yahoo_symbol="^FCHI", name="CAC 40 Index", country="FR", exchange="EURONEXT_PARIS", source="yahoo"),
37
+
38
+ # ========================= GERMAN GROUP =========================
39
+ Instrument(symbol="SAP", yahoo_symbol="SAP.DE", name="SAP SE", country="DE", exchange="XETRA", source="yahoo"),
40
+ Instrument(symbol="SIE", yahoo_symbol="SIE.DE", name="Siemens", country="DE", exchange="XETRA", source="yahoo"),
41
+ Instrument(symbol="ALV", yahoo_symbol="ALV.DE", name="Allianz", country="DE", exchange="XETRA", source="yahoo"),
42
+ Instrument(symbol="VOW3", yahoo_symbol="VOW3.DE", name="Volkswagen", country="DE", exchange="XETRA", source="yahoo"),
43
+ Instrument(symbol="BAS", yahoo_symbol="BAS.DE", name="BASF", country="DE", exchange="XETRA", source="yahoo"),
44
+ Instrument(symbol="DBK", yahoo_symbol="DBK.DE", name="Deutsche Bank", country="DE", exchange="XETRA", source="yahoo"),
45
+ Instrument(symbol="BAYN", yahoo_symbol="BAYN.DE", name="Bayer", country="DE", exchange="XETRA", source="yahoo"),
46
+ Instrument(symbol="MBG", yahoo_symbol="MBG.DE", name="Mercedes-Benz Group", country="DE", exchange="XETRA", source="yahoo"),
47
+ Instrument(symbol="ADS", yahoo_symbol="ADS.DE", name="Adidas", country="DE", exchange="XETRA", source="yahoo"),
48
+ Instrument(symbol="BMW", yahoo_symbol="BMW.DE", name="BMW", country="DE", exchange="XETRA", source="yahoo"),
49
+ Instrument(symbol="IFX", yahoo_symbol="IFX.DE", name="Infineon Technologies", country="DE", exchange="XETRA", source="yahoo"),
50
+ Instrument(symbol="DTE", yahoo_symbol="DTE.DE", name="Deutsche Telekom", country="DE", exchange="XETRA", source="yahoo"),
51
+ # German Market
52
+ Instrument(symbol="DAX", yahoo_symbol="^GDAXI", name="DAX Index", country="DE", exchange="XETRA", source="yahoo"),
53
+
54
+ # ========================= ITALIAN GROUP =========================
55
+ Instrument(symbol="ENEL", yahoo_symbol="ENEL.MI", name="Enel", country="IT", exchange="BORSA_ITALIANA", source="yahoo"),
56
+ Instrument(symbol="ENI", yahoo_symbol="ENI.MI", name="Eni", country="IT", exchange="BORSA_ITALIANA", source="yahoo"),
57
+ Instrument(symbol="ISP", yahoo_symbol="ISP.MI", name="Intesa Sanpaolo", country="IT", exchange="BORSA_ITALIANA", source="yahoo"),
58
+ Instrument(symbol="UCG", yahoo_symbol="UCG.MI", name="UniCredit", country="IT", exchange="BORSA_ITALIANA", source="yahoo"),
59
+ Instrument(symbol="RACE", yahoo_symbol="RACE.MI", name="Ferrari", country="IT", exchange="BORSA_ITALIANA", source="yahoo"),
60
+ Instrument(symbol="STLAM", yahoo_symbol="STLAM.MI", name="Stellantis", country="IT", exchange="BORSA_ITALIANA", source="yahoo"),
61
+ Instrument(symbol="G", yahoo_symbol="G.MI", name="Generali", country="IT", exchange="BORSA_ITALIANA", source="yahoo"),
62
+ Instrument(symbol="STM", yahoo_symbol="STMMI.MI", name="STMicroelectronics", country="IT", exchange="BORSA_ITALIANA", source="yahoo"),
63
+ Instrument(symbol="PRY", yahoo_symbol="PRY.MI", name="Prysmian", country="IT", exchange="BORSA_ITALIANA", source="yahoo"),
64
+ # Italian Market (Corrected)
65
+ Instrument(symbol="FTSEMIB", yahoo_symbol="FTSEMIB.MI", name="FTSE MIB Index", country="IT", exchange="BORSA_ITALIANA", source="yahoo"),
66
+
67
+ # ========================= SPANISH GROUP =========================
68
+ Instrument(symbol="IBE", yahoo_symbol="IBE.MC", name="Iberdrola", country="ES", exchange="BME", source="yahoo"),
69
+ Instrument(symbol="SAN_ES", yahoo_symbol="SAN.MC", name="Banco Santander", country="ES", exchange="BME", source="yahoo"),
70
+ Instrument(symbol="ITX", yahoo_symbol="ITX.MC", name="Inditex", country="ES", exchange="BME", source="yahoo"),
71
+ Instrument(symbol="BBVA", yahoo_symbol="BBVA.MC", name="BBVA", country="ES", exchange="BME", source="yahoo"),
72
+ Instrument(symbol="REP", yahoo_symbol="REP.MC", name="Repsol", country="ES", exchange="BME", source="yahoo"),
73
+ Instrument(symbol="TEF", yahoo_symbol="TEF.MC", name="Telefonica", country="ES", exchange="BME", source="yahoo"),
74
+ Instrument(symbol="AMS", yahoo_symbol="AMS.MC", name="Amadeus IT Group", country="ES", exchange="BME", source="yahoo"),
75
+ Instrument(symbol="CABK", yahoo_symbol="CABK.MC", name="CaixaBank", country="ES", exchange="BME", source="yahoo"),
76
+ # Spanish Market
77
+ Instrument(symbol="IBEX35", yahoo_symbol="^IBEX", name="IBEX 35 Index", country="ES", exchange="BME", source="yahoo"),
78
+
79
+ # ========================= DUTCH GROUP =========================
80
+ Instrument(symbol="ASML", yahoo_symbol="ASML.AS", name="ASML Holding", country="NL", exchange="EURONEXT_AMSTERDAM", source="yahoo"),
81
+ Instrument(symbol="HEIA", yahoo_symbol="HEIA.AS", name="Heineken", country="NL", exchange="EURONEXT_AMSTERDAM", source="yahoo"),
82
+ Instrument(symbol="AD", yahoo_symbol="AD.AS", name="Ahold Delhaize", country="NL", exchange="EURONEXT_AMSTERDAM", source="yahoo"),
83
+ Instrument(symbol="PHIA", yahoo_symbol="PHIA.AS", name="Philips", country="NL", exchange="EURONEXT_AMSTERDAM", source="yahoo"),
84
+ Instrument(symbol="INGA", yahoo_symbol="INGA.AS", name="ING Groep", country="NL", exchange="EURONEXT_AMSTERDAM", source="yahoo"),
85
+ Instrument(symbol="PRX", yahoo_symbol="PRX.AS", name="Prosus", country="NL", exchange="EURONEXT_AMSTERDAM", source="yahoo"),
86
+ # Dutch Market
87
+ Instrument(symbol="AEX", yahoo_symbol="^AEX", name="AEX Index", country="NL", exchange="EURONEXT_AMSTERDAM", source="yahoo"),
88
+
89
+ # ========================= BELGIAN GROUP =========================
90
+ Instrument(symbol="ABI", yahoo_symbol="ABI.BR", name="Anheuser-Busch InBev", country="BE", exchange="EURONEXT_BRUSSELS", source="yahoo"),
91
+ Instrument(symbol="KBC", yahoo_symbol="KBC.BR", name="KBC Group", country="BE", exchange="EURONEXT_BRUSSELS", source="yahoo"),
92
+ Instrument(symbol="UCB", yahoo_symbol="UCB.BR", name="UCB SA", country="BE", exchange="EURONEXT_BRUSSELS", source="yahoo"),
93
+ Instrument(symbol="AGS", yahoo_symbol="AGS.BR", name="Ageas", country="BE", exchange="EURONEXT_BRUSSELS", source="yahoo"),
94
+ Instrument(symbol="ARGX", yahoo_symbol="ARGX.BR", name="argenx", country="BE", exchange="EURONEXT_BRUSSELS", source="yahoo"),
95
+ Instrument(symbol="ACKB", yahoo_symbol="ACKB.BR", name="Ackermans & van Haaren", country="BE", exchange="EURONEXT_BRUSSELS", source="yahoo"),
96
+ # Belgian Market
97
+ Instrument(symbol="BEL20", yahoo_symbol="^BFX", name="BEL 20 Index", country="BE", exchange="EURONEXT_BRUSSELS", source="yahoo"),
98
+ ]
@@ -0,0 +1,87 @@
1
+ """Yahoo Finance ingestion. Mirrors include/qde/yahoo_finance.hpp + src/yahoo_finance.cpp.
2
+
3
+ Uses the same unofficial chart endpoint:
4
+ https://query1.finance.yahoo.com/v8/finance/chart/<symbol>
5
+
6
+ NOTE: Tunis Stock Exchange (BVMT) tickers are NOT covered by Yahoo Finance --
7
+ same caveat as the C++ version. Use csv_io.read_series_csv for those instead.
8
+ """
9
+ from __future__ import annotations
10
+ import sys
11
+ from typing import Optional
12
+
13
+ from amquant.bar import Bar, Series
14
+ from amquant.http_client import HttpClient
15
+
16
+ _RANGE_MAP = {
17
+ "5d": "5d", "1mo": "1mo", "3mo": "3mo", "6mo": "6mo",
18
+ "1y": "1y", "2y": "2y", "5y": "5y", "10y": "10y", "max": "max",
19
+ }
20
+ _INTERVAL_MAP = {"1d": "1d", "1wk": "1wk", "1mo": "1mo"}
21
+
22
+
23
+ class YahooFinanceClient:
24
+ """Equivalent of qde::YahooFinanceClient."""
25
+
26
+ def __init__(self, http_client: HttpClient | None = None):
27
+ self.http = http_client or HttpClient()
28
+
29
+ def fetch_history(self, symbol: str, range_: str = "2y", interval: str = "1d") -> Optional[Series]:
30
+ """symbol must be the Yahoo ticker, e.g. 'AAPL', 'MC.PA', 'SAP.DE'.
31
+ Returns None on failure (network or bad HTTP status), same fail-soft
32
+ behaviour as the C++ version so one bad symbol doesn't kill a batch run.
33
+ """
34
+ url = (
35
+ f"https://query1.finance.yahoo.com/v8/finance/chart/{symbol}"
36
+ f"?range={_RANGE_MAP.get(range_, '2y')}"
37
+ f"&interval={_INTERVAL_MAP.get(interval, '1d')}"
38
+ f"&events=div,splits"
39
+ )
40
+ try:
41
+ resp = self.http.get(url, headers={"Accept": "application/json"})
42
+ except RuntimeError as e:
43
+ print(f"[yahoo] request failed for {symbol}: {e}", file=sys.stderr)
44
+ return None
45
+
46
+ if not resp.ok():
47
+ print(f"[yahoo] HTTP {resp.status_code} for {symbol}", file=sys.stderr)
48
+ return None
49
+
50
+ return self._parse_chart_json(symbol, resp.body)
51
+
52
+ def _parse_chart_json(self, symbol: str, json_text: str) -> Optional[Series]:
53
+ import json as jsonlib
54
+ try:
55
+ root = jsonlib.loads(json_text)
56
+ result = root["chart"]["result"]
57
+ if not result:
58
+ print(f"[yahoo] empty result for {symbol}", file=sys.stderr)
59
+ return None
60
+ r0 = result[0]
61
+ timestamps = r0["timestamp"]
62
+ quote = r0["indicators"]["quote"][0]
63
+ opens, highs, lows, closes, volumes = (
64
+ quote["open"], quote["high"], quote["low"], quote["close"], quote["volume"],
65
+ )
66
+ adjclose = None
67
+ if "adjclose" in r0["indicators"]:
68
+ adjclose = r0["indicators"]["adjclose"][0]["adjclose"]
69
+
70
+ series = Series(symbol=symbol)
71
+ for i, ts in enumerate(timestamps):
72
+ if ts is None or closes[i] is None:
73
+ continue # skip holes, same as the C++ version
74
+ bar = Bar(
75
+ timestamp_utc=int(ts),
76
+ open=opens[i] or 0.0,
77
+ high=highs[i] or 0.0,
78
+ low=lows[i] or 0.0,
79
+ close=closes[i],
80
+ volume=volumes[i] or 0.0,
81
+ adj_close=(adjclose[i] if adjclose and adjclose[i] is not None else closes[i]),
82
+ )
83
+ series.bars.append(bar)
84
+ return series
85
+ except (KeyError, IndexError, TypeError, ValueError, jsonlib.JSONDecodeError) as e:
86
+ print(f"[yahoo] JSON parse error for {symbol}: {e}", file=sys.stderr)
87
+ return None
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.4
2
+ Name: amquant
3
+ Version: 0.1.0
4
+ Summary: A lightweight library for loading market data and computing engineered features.
5
+ Author-email: Your Name <you@example.com>
6
+ License: MIT
7
+ Keywords: quant,finance,market-data,features,trading
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: requests>=2.31
11
+ Requires-Dist: pandas>=2.0
12
+
13
+ **activate the virtual env with** : source venv/bin/activate
14
+
15
+ **interactive program** :python3 -i main.py
16
+
17
+ **DATA ACCESS:**
18
+
19
+ After `main()` executes in the same Python session:
20
+
21
+ - `RAW_SERIES: dict[str, Series]` — populated with fully enriched `Series` objects (OHLCV + metadata).
22
+ - `FEATURE_SETS: dict[str, FeatureSet]` — contains computed technical features per symbol.
23
+
24
+ Both dicts are module-level globals, accessible immediately post-run via `RAW_SERIES['BIAT']` or `FEATURE_SETS['MC']`.
25
+
26
+ Data lives in memory for the duration of the Python process / REPL session. Not persisted to disk unless explicitly saved.
27
+
28
+ For library use: expose `RAW_SERIES` and `FEATURE_SETS` via a `DataManager` singleton or return them from `load_universe()` for clean dependency injection.
29
+
30
+ **demo**
31
+
32
+ list(RAW_SERIES.keys())
33
+ RAW_SERIES["BNP"].bars[0]
34
+ FEATURE_SETS["BNP"].ret_1d[:5]
35
+ FEATURE_SETS["BNP"].sma_20[:5]
36
+ FEATURE_SETS["BNP"].__dict__.keys()
37
+ len(RAW_SERIES["BNP"].bars)
38
+ len(RAW_SERIES)
39
+ len(FEATURE_SETS)
40
+
41
+ # AmQuant
42
+
@@ -0,0 +1,16 @@
1
+ README.md
2
+ pyproject.toml
3
+ amquant/__init__.py
4
+ amquant/bar.py
5
+ amquant/features.py
6
+ amquant/http_client.py
7
+ amquant/loader.py
8
+ amquant/manual_data.py
9
+ amquant/universe.py
10
+ amquant/universe_data.py
11
+ amquant/yahoo_finance.py
12
+ amquant.egg-info/PKG-INFO
13
+ amquant.egg-info/SOURCES.txt
14
+ amquant.egg-info/dependency_links.txt
15
+ amquant.egg-info/requires.txt
16
+ amquant.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ requests>=2.31
2
+ pandas>=2.0
@@ -0,0 +1 @@
1
+ amquant
@@ -0,0 +1,20 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "amquant"
7
+ version = "0.1.0"
8
+ description = "A lightweight library for loading market data and computing engineered features."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Your Name", email = "you@example.com" }]
13
+ keywords = ["quant", "finance", "market-data", "features", "trading"]
14
+ dependencies = [
15
+ "requests>=2.31",
16
+ "pandas>=2.0",
17
+ ]
18
+
19
+ [tool.setuptools]
20
+ packages = ["amquant"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+