pyalloq-data-connector 0.1.4__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.
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.3
2
+ Name: pyalloq-data-connector
3
+ Version: 0.1.4
4
+ Summary: Vendor-agnostic data adapters for PyAlloq.
5
+ Author: Siddeshkanth
6
+ Author-email: Siddeshkanth <pyalloq-info@alloq-alpha.com>
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Intended Audience :: Financial and Insurance Industry
12
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
13
+ Classifier: Topic :: Office/Business :: Financial :: Investment
14
+ Requires-Dist: numpy>=1.24
15
+ Requires-Dist: pandas>=2.0
16
+ Requires-Dist: yfinance>=1.6.0
17
+ Requires-Dist: requests>=2.31
18
+ Requires-Dist: pyalloq-core
19
+ Requires-Python: >=3.11
20
+ Project-URL: Homepage, https://github.com/your-org/qpo
21
+ Project-URL: Repository, https://github.com/your-org/qpo
22
+ Project-URL: Issues, https://github.com/your-org/qpo/issues
23
+ Description-Content-Type: text/markdown
24
+
25
+ # pyalloq-data-connector
26
+
27
+ `pyalloq-data-connector` provides vendor-agnostic data adapters for **PyAlloq**. It handles fetching raw market data from various third-party APIs and automatically standardizes them into pure, time-aligned `MarketData` objects.
28
+
29
+ ## Supported Adapters
30
+
31
+ - **`YahooFinanceClient`**: Free adapter using Yahoo Finance (`yfinance`). No API key required.
32
+ - **`AlphaVantageClient`**: Adapter for Alpha Vantage Time Series Daily API.
33
+ - **`FinnhubClient`**: Adapter for Finnhub's stock candle endpoint.
34
+ - **`EODHistoricalDataClient`**: Adapter for EOD Historical Data API.
35
+
36
+ ## Features
37
+
38
+ - **Standardized Output**: Automatically converts heterogeneous JSON/DataFrame vendor payloads into `T x N` aligned price matrices (`MarketData`).
39
+ - **Missing Value Handling**: Implements forward filling (`ffill`) for prices and zero-filling for volume data.
40
+ - **Time Alignment**: Constructs unified datetime indices across all requested ticker symbols.
41
+
42
+ ## Quick Example
43
+
44
+ ```python
45
+ import datetime as dt
46
+ from pyalloq_data_connector.yahoo_finance import YahooFinanceClient
47
+
48
+ client = YahooFinanceClient()
49
+ start = dt.datetime(2023, 1, 1)
50
+ end = dt.datetime(2024, 1, 1)
51
+
52
+ # Fetch standardized MarketData object
53
+ market_data = client.get_market_data(
54
+ tickers=["AAPL", "MSFT", "GOOGL"],
55
+ start=start,
56
+ end=end
57
+ )
58
+
59
+ print(market_data.prices.head())
60
+ print(market_data.features["volume"].head())
61
+ ```
@@ -0,0 +1,37 @@
1
+ # pyalloq-data-connector
2
+
3
+ `pyalloq-data-connector` provides vendor-agnostic data adapters for **PyAlloq**. It handles fetching raw market data from various third-party APIs and automatically standardizes them into pure, time-aligned `MarketData` objects.
4
+
5
+ ## Supported Adapters
6
+
7
+ - **`YahooFinanceClient`**: Free adapter using Yahoo Finance (`yfinance`). No API key required.
8
+ - **`AlphaVantageClient`**: Adapter for Alpha Vantage Time Series Daily API.
9
+ - **`FinnhubClient`**: Adapter for Finnhub's stock candle endpoint.
10
+ - **`EODHistoricalDataClient`**: Adapter for EOD Historical Data API.
11
+
12
+ ## Features
13
+
14
+ - **Standardized Output**: Automatically converts heterogeneous JSON/DataFrame vendor payloads into `T x N` aligned price matrices (`MarketData`).
15
+ - **Missing Value Handling**: Implements forward filling (`ffill`) for prices and zero-filling for volume data.
16
+ - **Time Alignment**: Constructs unified datetime indices across all requested ticker symbols.
17
+
18
+ ## Quick Example
19
+
20
+ ```python
21
+ import datetime as dt
22
+ from pyalloq_data_connector.yahoo_finance import YahooFinanceClient
23
+
24
+ client = YahooFinanceClient()
25
+ start = dt.datetime(2023, 1, 1)
26
+ end = dt.datetime(2024, 1, 1)
27
+
28
+ # Fetch standardized MarketData object
29
+ market_data = client.get_market_data(
30
+ tickers=["AAPL", "MSFT", "GOOGL"],
31
+ start=start,
32
+ end=end
33
+ )
34
+
35
+ print(market_data.prices.head())
36
+ print(market_data.features["volume"].head())
37
+ ```
@@ -0,0 +1,60 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.10.9,<0.11.0"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "pyalloq-data-connector"
7
+ version = "0.1.4"
8
+ description = "Vendor-agnostic data adapters for PyAlloq."
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Siddeshkanth", email = "pyalloq-info@alloq-alpha.com" }
12
+ ]
13
+ requires-python = ">=3.11"
14
+
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Intended Audience :: Financial and Insurance Industry",
21
+ "Topic :: Scientific/Engineering :: Mathematics",
22
+ "Topic :: Office/Business :: Financial :: Investment",
23
+ ]
24
+
25
+ dependencies = [
26
+ "numpy>=1.24",
27
+ "pandas>=2.0",
28
+ "yfinance>=1.6.0",
29
+ "requests>=2.31",
30
+ "pyalloq-core"
31
+ ]
32
+
33
+ [dependency-groups]
34
+ dev = [
35
+ "pytest>=8",
36
+ "pytest-cov>=5",
37
+ "ruff>=0.6",
38
+ "mypy>=1.11",
39
+ "types-requests>=2.0",
40
+ "pandas-stubs>=3.0.5.260730",
41
+ ]
42
+
43
+ [project.urls]
44
+ Homepage = "https://github.com/your-org/qpo"
45
+ Repository = "https://github.com/your-org/qpo"
46
+ Issues = "https://github.com/your-org/qpo/issues"
47
+
48
+ [tool.setuptools.packages.find]
49
+ where = ["src/"]
50
+
51
+ [[tool.mypy.overrides]]
52
+ module = [
53
+ "numpy.*",
54
+ "pandas.*",
55
+ "yfinance.*"
56
+ ]
57
+ ignore_missing_imports = true
58
+
59
+ [tool.uv.sources]
60
+ pyalloq-core = { workspace = true }
@@ -0,0 +1,46 @@
1
+ import requests
2
+ import pandas as pd
3
+ import datetime as dt
4
+ from pyalloq_data_connector.base import BaseDataClient
5
+
6
+
7
+ class AlphaVantageClient(BaseDataClient):
8
+ """Adapter for Alpha Vantage Time Series Daily."""
9
+
10
+ BASE_URL = "https://www.alphavantage.co/query"
11
+
12
+ def fetch_raw_data(
13
+ self, tickers: list[str], start: dt.datetime, end: dt.datetime
14
+ ) -> dict[str, pd.DataFrame]:
15
+ if not self.api_key:
16
+ raise ValueError("Alpha Vantage requires an API key.")
17
+
18
+ raw_dict = {}
19
+ start_ts = pd.Timestamp(start)
20
+ end_ts = pd.Timestamp(end)
21
+
22
+ for ticker in tickers:
23
+ params = {
24
+ "function": "TIME_SERIES_DAILY",
25
+ "symbol": ticker,
26
+ "outputsize": "full",
27
+ "apikey": self.api_key,
28
+ }
29
+
30
+ response = requests.get(self.BASE_URL, params=params)
31
+ data = response.json()
32
+
33
+ if "Time Series (Daily)" not in data:
34
+ print(f"Warning: Alpha Vantage failed for {ticker}. Check API limits.")
35
+ continue
36
+
37
+ df = pd.DataFrame.from_dict(data["Time Series (Daily)"], orient="index")
38
+ df.index = pd.to_datetime(df.index)
39
+
40
+ df.columns = [col.split(" ")[1] for col in df.columns]
41
+ df = df.astype(float)
42
+
43
+ mask = (df.index >= start_ts) & (df.index <= end_ts)
44
+ raw_dict[ticker] = df.loc[mask].sort_index()
45
+
46
+ return raw_dict
@@ -0,0 +1,100 @@
1
+ from abc import ABC, abstractmethod
2
+ import pandas as pd
3
+ from pyalloq_core.data import MarketData
4
+ import datetime as dt
5
+
6
+
7
+ class BaseDataClient(ABC):
8
+ """
9
+ Abstract base class for all third-party data providers.
10
+ Ensures raw API data is always converted to MarketData format.
11
+ """
12
+
13
+ def __init__(self, api_key: str | None = None):
14
+ self.api_key = api_key
15
+
16
+ @abstractmethod
17
+ def fetch_raw_data(
18
+ self,
19
+ tickers: list[str],
20
+ start_date: dt.datetime,
21
+ end_date: dt.datetime,
22
+ ) -> dict[str, pd.DataFrame]:
23
+ """
24
+ Fetches OHLCV data from the provider and packages it into MarketData.
25
+
26
+ Args:
27
+ tickers: List of asset symbols (e.g., ['AAPL', 'MSFT'])
28
+ start_date: YYYY-MM-DD string
29
+ end_date: YYYY-MM-DD string
30
+
31
+ Returns:
32
+ MarketData: The standardized PyAlloq data container.
33
+ """
34
+ pass
35
+
36
+ def get_market_data(
37
+ self,
38
+ tickers: list[str],
39
+ start: dt.datetime,
40
+ end: dt.datetime,
41
+ ) -> MarketData:
42
+ """
43
+ The orchestrator method. Standardizes whatever chaotic data the vendor
44
+ returns into mathematically pure T x N matrices, perfectly aligned in time.
45
+ """
46
+ raw_dict = self.fetch_raw_data(tickers, start, end)
47
+
48
+ closes, volumes, opens, highs, lows = {}, {}, {}, {}, {}
49
+ all_dates = pd.DatetimeIndex([])
50
+
51
+ for ticker, df in raw_dict.items():
52
+ df.columns = [str(c).lower() for c in df.columns]
53
+
54
+ closes[ticker] = df.get("close", df.get("adj close"))
55
+ if "volume" in df.columns:
56
+ volumes[ticker] = df["volume"]
57
+ if "open" in df.columns:
58
+ opens[ticker] = df["open"]
59
+ if "high" in df.columns:
60
+ highs[ticker] = df["high"]
61
+ if "low" in df.columns:
62
+ lows[ticker] = df["low"]
63
+
64
+ dt_index = pd.to_datetime(df.index)
65
+ all_dates = all_dates.union(dt_index)
66
+
67
+ all_dates = all_dates.sort_values()
68
+
69
+ def build_and_align(series_dict: dict, fill_method: str) -> pd.DataFrame:
70
+ if not series_dict:
71
+ return pd.DataFrame()
72
+
73
+ df = pd.DataFrame(series_dict)
74
+ df = df.reindex(all_dates)
75
+
76
+ if fill_method == "ffill":
77
+ return df.ffill()
78
+ elif fill_method == "zero":
79
+ return df.fillna(0.0)
80
+ return df
81
+
82
+ prices_df = build_and_align(closes, fill_method="ffill")
83
+
84
+ features = {}
85
+ if volumes:
86
+ features["volume"] = build_and_align(volumes, fill_method="zero")
87
+ if opens:
88
+ features["open"] = build_and_align(opens, fill_method="ffill")
89
+ if highs:
90
+ features["high"] = build_and_align(highs, fill_method="ffill")
91
+ if lows:
92
+ features["low"] = build_and_align(lows, fill_method="ffill")
93
+
94
+ valid_indices = prices_df.dropna(how="all").index
95
+ prices_df = prices_df.loc[valid_indices]
96
+
97
+ for key in features:
98
+ features[key] = features[key].loc[valid_indices]
99
+
100
+ return MarketData(prices=prices_df, features=features)
@@ -0,0 +1,45 @@
1
+ import requests
2
+ import pandas as pd
3
+ import datetime as dt
4
+ from pyalloq_data_connector.base import BaseDataClient
5
+
6
+
7
+ class EODClient(BaseDataClient):
8
+ """Adapter for EOD Historical Data (eodhd.com)."""
9
+
10
+ BASE_URL = "https://eodhd.com/api/eod"
11
+
12
+ def fetch_raw_data(
13
+ self, tickers: list[str], start: dt.datetime, end: dt.datetime
14
+ ) -> dict[str, pd.DataFrame]:
15
+ if not self.api_key:
16
+ raise ValueError("EODHD requires an API key.")
17
+
18
+ start_str = start.strftime("%Y-%m-%d")
19
+ end_str = end.strftime("%Y-%m-%d")
20
+
21
+ raw_dict = {}
22
+ for ticker in tickers:
23
+ url = f"{self.BASE_URL}/{ticker}"
24
+ params = {
25
+ "api_token": self.api_key,
26
+ "fmt": "json",
27
+ "from": start_str,
28
+ "to": end_str,
29
+ }
30
+
31
+ response = requests.get(url, params=params)
32
+ if response.status_code != 200:
33
+ print(
34
+ f"Warning: EOD returned status {response.status_code} for {ticker}"
35
+ )
36
+ continue
37
+
38
+ df = pd.DataFrame(response.json())
39
+ if not df.empty:
40
+ df["date"] = pd.to_datetime(df["date"])
41
+ df.set_index("date", inplace=True)
42
+ df["close"] = df["adjusted_close"]
43
+ raw_dict[ticker] = df
44
+
45
+ return raw_dict
@@ -0,0 +1,56 @@
1
+ import requests
2
+ import pandas as pd
3
+ import datetime as dt
4
+ from pyalloq_data_connector.base import BaseDataClient
5
+ import time
6
+
7
+
8
+ class FinnhubClient(BaseDataClient):
9
+ """Adapter for Finnhub's stock candle endpoint."""
10
+
11
+ BASE_URL = "https://finnhub.io/api/v1/stock/candle"
12
+
13
+ def fetch_raw_data(
14
+ self, tickers: list[str], start: dt.datetime, end: dt.datetime
15
+ ) -> dict[str, pd.DataFrame]:
16
+ if not self.api_key:
17
+ raise ValueError("Finnhub requires an API key.")
18
+
19
+ raw_dict = {}
20
+
21
+ start_unix = int(start.timestamp())
22
+ end_unix = int(end.timestamp())
23
+
24
+ for ticker in tickers:
25
+ params: dict[str, str | int] = {
26
+ "symbol": ticker,
27
+ "resolution": "D", # Daily bars
28
+ "from": start_unix,
29
+ "to": end_unix,
30
+ "token": self.api_key,
31
+ }
32
+
33
+ response = requests.get(self.BASE_URL, params=params)
34
+ data = response.json()
35
+
36
+ if data.get("s") != "ok":
37
+ print(f"Warning: Finnhub returned no data or error for {ticker}")
38
+ continue
39
+
40
+ df = pd.DataFrame(
41
+ {
42
+ "open": data["o"],
43
+ "high": data["h"],
44
+ "low": data["l"],
45
+ "close": data["c"],
46
+ "volume": data["v"],
47
+ },
48
+ index=pd.to_datetime(data["t"], unit="s"),
49
+ )
50
+
51
+ dt_idx = pd.DatetimeIndex(df.index)
52
+ df.index = dt_idx.tz_localize(None).normalize()
53
+ raw_dict[ticker] = df
54
+ time.sleep(0.5)
55
+
56
+ return raw_dict
@@ -0,0 +1 @@
1
+ # Marker file for PEP 561
@@ -0,0 +1,36 @@
1
+ import yfinance as yf
2
+ import pandas as pd
3
+ import datetime as dt
4
+ from pyalloq_data_connector.base import BaseDataClient
5
+ from pyalloq_core.data import MarketData
6
+
7
+
8
+ class YahooFinanceClient(BaseDataClient):
9
+ """
10
+ Adapter for Yahoo Finance.
11
+ Does not require an API key.
12
+ """
13
+
14
+ def __init__(self):
15
+ super().__init__(api_key=None)
16
+
17
+ def fetch_raw_data(
18
+ self, tickers: list[str], start: dt.datetime, end: dt.datetime
19
+ ) -> dict[str, pd.DataFrame]:
20
+ raw_dict = {}
21
+ for t in tickers:
22
+ yt = yf.Ticker(t)
23
+ df = yt.history(start=start, end=end, auto_adjust=True)
24
+
25
+ if not df.empty:
26
+ df.index = pd.to_datetime(df.index).tz_localize(None)
27
+ raw_dict[t] = df
28
+ else:
29
+ print(f"Warning: No data found for {t} on Yahoo Finance.")
30
+
31
+ return raw_dict
32
+
33
+ def fetch_historical_prices(
34
+ self, tickers: list[str], start_date: dt.datetime, end_date: dt.datetime
35
+ ) -> MarketData:
36
+ return self.get_market_data(tickers, start_date, end_date)