arithmaxchest 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arithmax Research
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: arithmaxchest
3
+ Version: 0.1.0
4
+ Summary: Centralized market data API and Python client
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: fastapi>=0.110
9
+ Requires-Dist: httpx>=0.27
10
+ Requires-Dist: pandas>=2.0
11
+ Requires-Dist: pyarrow>=14.0
12
+ Requires-Dist: requests>=2.31
13
+ Provides-Extra: yahoo
14
+ Requires-Dist: yfinance>=0.2.40; extra == "yahoo"
15
+ Provides-Extra: futures
16
+ Requires-Dist: databento>=1.0; extra == "futures"
17
+ Provides-Extra: server
18
+ Requires-Dist: uvicorn[standard]>=0.29; extra == "server"
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=8.0; extra == "dev"
21
+ Provides-Extra: all
22
+ Requires-Dist: yfinance>=0.2.40; extra == "all"
23
+ Requires-Dist: databento>=1.0; extra == "all"
24
+ Requires-Dist: uvicorn[standard]>=0.29; extra == "all"
25
+ Dynamic: license-file
26
+
27
+ # Arithmax Chest
28
+
29
+ A standalone FastAPI service and Python client for querying centralized market data. Provider API keys stay on the server; research projects use only the client URL and token.
30
+
31
+ ## Server
32
+
33
+ ```bash
34
+ python -m pip install -e '.[all]'
35
+ export DATA_API_TOKEN='private-client-token'
36
+ export BINANCE_API_KEY='server-only-if-required'
37
+ export POLYGON_API_KEY='server-only'
38
+ export DATABENTO_API_KEY='server-only'
39
+ uvicorn achest.server:app --host 0.0.0.0 --port 8000
40
+ ```
41
+
42
+ ## Client
43
+
44
+ ```python
45
+ from achest import MarketDataClient
46
+
47
+ with MarketDataClient('https://data.example.com', token='private-client-token') as client:
48
+ frame = client.get(['BTCUSDT', 'ES.FUT'], '2024-01-01', '2024-01-31')
49
+ client.download(['BTCUSDT'], '2024-01-01', '2024-01-31', 'data/btc.parquet')
50
+ ```
51
+
52
+ `auto` routes crypto to Binance, futures to Databento first, and equities to Yahoo. Supported response formats are JSON, CSV, and Parquet. Large downloads should be moved to a background job endpoint before production use.
53
+
54
+ ## PyPI publishing
55
+
56
+ ```bash
57
+ python -m pip install build twine
58
+ python -m build
59
+ python -m twine upload dist/*
60
+ ```
@@ -0,0 +1,34 @@
1
+ # Arithmax Chest
2
+
3
+ A standalone FastAPI service and Python client for querying centralized market data. Provider API keys stay on the server; research projects use only the client URL and token.
4
+
5
+ ## Server
6
+
7
+ ```bash
8
+ python -m pip install -e '.[all]'
9
+ export DATA_API_TOKEN='private-client-token'
10
+ export BINANCE_API_KEY='server-only-if-required'
11
+ export POLYGON_API_KEY='server-only'
12
+ export DATABENTO_API_KEY='server-only'
13
+ uvicorn achest.server:app --host 0.0.0.0 --port 8000
14
+ ```
15
+
16
+ ## Client
17
+
18
+ ```python
19
+ from achest import MarketDataClient
20
+
21
+ with MarketDataClient('https://data.example.com', token='private-client-token') as client:
22
+ frame = client.get(['BTCUSDT', 'ES.FUT'], '2024-01-01', '2024-01-31')
23
+ client.download(['BTCUSDT'], '2024-01-01', '2024-01-31', 'data/btc.parquet')
24
+ ```
25
+
26
+ `auto` routes crypto to Binance, futures to Databento first, and equities to Yahoo. Supported response formats are JSON, CSV, and Parquet. Large downloads should be moved to a background job endpoint before production use.
27
+
28
+ ## PyPI publishing
29
+
30
+ ```bash
31
+ python -m pip install build twine
32
+ python -m build
33
+ python -m twine upload dist/*
34
+ ```
@@ -0,0 +1,5 @@
1
+ """Arithmax Chest market-data client and provider service."""
2
+
3
+ from .client import MarketDataClient
4
+
5
+ __all__ = ["MarketDataClient"]
@@ -0,0 +1,41 @@
1
+ """Python client for the centralized market-data API."""
2
+
3
+ from datetime import date
4
+ from pathlib import Path
5
+ from typing import Iterable
6
+
7
+ import httpx
8
+ import pandas as pd
9
+
10
+
11
+ class MarketDataClient:
12
+ def __init__(self, base_url: str, token: str | None = None, timeout: float = 300.0):
13
+ headers = {"Authorization": f"Bearer {token}"} if token else {}
14
+ self.client = httpx.Client(base_url=base_url.rstrip("/"), headers=headers, timeout=timeout)
15
+
16
+ def route(self, symbol: str, resolution: str = "daily", provider: str = "auto") -> dict:
17
+ response = self.client.get("/v1/route", params={"symbol": symbol, "resolution": resolution, "provider": provider})
18
+ response.raise_for_status()
19
+ return response.json()
20
+
21
+ def get(self, symbols: Iterable[str], start: date | str, end: date | str, resolution: str = "daily", provider: str = "auto") -> pd.DataFrame:
22
+ response = self.client.post("/v1/data", json={"symbols": list(symbols), "start": str(start), "end": str(end), "resolution": resolution, "provider": provider, "format": "json"})
23
+ response.raise_for_status()
24
+ return pd.DataFrame(response.json())
25
+
26
+ def download(self, symbols: Iterable[str], start: date | str, end: date | str, output: str | Path, resolution: str = "daily", provider: str = "auto", format: str = "parquet") -> Path:
27
+ response = self.client.post("/v1/data", json={"symbols": list(symbols), "start": str(start), "end": str(end), "resolution": resolution, "provider": provider, "format": format})
28
+ response.raise_for_status()
29
+ destination = Path(output)
30
+ destination.parent.mkdir(parents=True, exist_ok=True)
31
+ destination.write_bytes(response.content)
32
+ return destination
33
+
34
+ def close(self) -> None:
35
+ self.client.close()
36
+
37
+ def __enter__(self):
38
+ return self
39
+
40
+ def __exit__(self, exc_type, exc_value, traceback) -> None:
41
+ self.close()
@@ -0,0 +1,79 @@
1
+ """FastAPI application for the centralized market-data service."""
2
+
3
+ from datetime import date
4
+ from io import BytesIO
5
+ import os
6
+
7
+ from fastapi import Depends, FastAPI, Header, HTTPException, Query
8
+ from fastapi.responses import Response
9
+ from pydantic import BaseModel, Field, field_validator
10
+
11
+ from .service import PROVIDER_CAPABILITIES, DataRequest, UnsupportedRequest, fetch, select_provider
12
+
13
+ app = FastAPI(title="Central Market Data API", version="0.1.0")
14
+
15
+
16
+ class DownloadRequest(BaseModel):
17
+ symbols: list[str] = Field(min_length=1)
18
+ start: date
19
+ end: date
20
+ resolution: str = "daily"
21
+ provider: str = "auto"
22
+ format: str = "csv"
23
+
24
+ @field_validator("resolution")
25
+ @classmethod
26
+ def valid_resolution(cls, value: str) -> str:
27
+ if value not in {"tick", "second", "minute", "hour", "daily"}:
28
+ raise ValueError("unsupported resolution")
29
+ return value
30
+
31
+ @field_validator("format")
32
+ @classmethod
33
+ def valid_format(cls, value: str) -> str:
34
+ if value not in {"json", "csv", "parquet"}:
35
+ raise ValueError("format must be json, csv, or parquet")
36
+ return value
37
+
38
+
39
+ def require_client_token(authorization: str | None = Header(default=None)) -> None:
40
+ expected = os.getenv("DATA_API_TOKEN")
41
+ if expected and authorization != f"Bearer {expected}":
42
+ raise HTTPException(status_code=401, detail="invalid client token")
43
+
44
+
45
+ @app.get("/health")
46
+ def health() -> dict[str, str]:
47
+ return {"status": "ok"}
48
+
49
+
50
+ @app.get("/v1/providers", dependencies=[Depends(require_client_token)])
51
+ def providers() -> dict:
52
+ return {"providers": PROVIDER_CAPABILITIES}
53
+
54
+
55
+ @app.get("/v1/route", dependencies=[Depends(require_client_token)])
56
+ def route(symbol: str, resolution: str = Query(default="daily"), provider: str = Query(default="auto")) -> dict[str, str]:
57
+ try:
58
+ selected = select_provider(symbol, provider, resolution)
59
+ except UnsupportedRequest as error:
60
+ raise HTTPException(status_code=422, detail=str(error)) from error
61
+ return {"symbol": symbol, "resolution": resolution, "provider": selected}
62
+
63
+
64
+ @app.post("/v1/data", dependencies=[Depends(require_client_token)])
65
+ def data(request: DownloadRequest) -> Response:
66
+ try:
67
+ frame = fetch(DataRequest(request.symbols, request.start, request.end, request.resolution, request.provider))
68
+ except (UnsupportedRequest, ValueError) as error:
69
+ raise HTTPException(status_code=422, detail=str(error)) from error
70
+ except Exception as error:
71
+ raise HTTPException(status_code=502, detail=f"provider request failed: {error}") from error
72
+ table = frame.reset_index(names="timestamp")
73
+ if request.format == "json":
74
+ return Response(table.to_json(orient="records", date_format="iso"), media_type="application/json")
75
+ if request.format == "csv":
76
+ return Response(table.to_csv(index=False), media_type="text/csv")
77
+ output = BytesIO()
78
+ table.to_parquet(output, index=False)
79
+ return Response(output.getvalue(), media_type="application/octet-stream", headers={"Content-Disposition": "attachment; filename=market-data.parquet"})
@@ -0,0 +1,161 @@
1
+ """Provider routing and normalized market-data retrieval."""
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import date, datetime, time
5
+ import os
6
+ from typing import Iterable
7
+
8
+ import pandas as pd
9
+ import requests
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class DataRequest:
14
+ symbols: list[str]
15
+ start: date
16
+ end: date
17
+ resolution: str = "daily"
18
+ provider: str = "auto"
19
+
20
+
21
+ PROVIDER_CAPABILITIES = {
22
+ "yahoo": {"assets": {"equity", "etf", "index", "crypto"}, "resolutions": {"minute", "hour", "daily"}},
23
+ "binance": {"assets": {"crypto"}, "resolutions": {"minute", "hour", "daily"}},
24
+ "polygon": {"assets": {"futures"}, "resolutions": {"minute", "hour", "daily"}},
25
+ "databento": {"assets": {"futures"}, "resolutions": {"tick", "second", "minute", "hour", "daily"}},
26
+ }
27
+ FUTURES_ROOTS = {"ES", "NQ", "YM", "RTY", "CL", "GC", "SI", "ZB", "ZN", "NG", "ZS", "ZC", "ZW"}
28
+
29
+
30
+ class UnsupportedRequest(ValueError):
31
+ pass
32
+
33
+
34
+ def classify_symbol(symbol: str) -> str:
35
+ clean = symbol.upper().strip()
36
+ if clean.endswith(("USDT", "USDC", "-USD")):
37
+ return "crypto"
38
+ if clean.endswith(".FUT") or ".c." in clean or clean in FUTURES_ROOTS:
39
+ return "futures"
40
+ if clean.startswith("^"):
41
+ return "index"
42
+ return "equity"
43
+
44
+
45
+ def select_provider(symbol: str, requested: str, resolution: str) -> str:
46
+ asset = classify_symbol(symbol)
47
+ if requested != "auto":
48
+ capabilities = PROVIDER_CAPABILITIES.get(requested)
49
+ if not capabilities or asset not in capabilities["assets"] or resolution not in capabilities["resolutions"]:
50
+ raise UnsupportedRequest(f"Provider {requested!r} does not support {asset} at {resolution} resolution")
51
+ return requested
52
+ preferences = {
53
+ "futures": ["databento", "polygon"],
54
+ "crypto": ["binance", "yahoo"],
55
+ "equity": ["yahoo"],
56
+ "etf": ["yahoo"],
57
+ "index": ["yahoo"],
58
+ }[asset]
59
+ for provider in preferences:
60
+ if resolution in PROVIDER_CAPABILITIES[provider]["resolutions"]:
61
+ return provider
62
+ raise UnsupportedRequest(f"No provider supports {asset} at {resolution} resolution")
63
+
64
+
65
+ def _as_datetime(value: date, end_of_day: bool = False) -> datetime:
66
+ return datetime.combine(value, time.max if end_of_day else time.min)
67
+
68
+
69
+ def _frame_from_bars(bars: Iterable[dict]) -> pd.DataFrame:
70
+ frame = pd.DataFrame(list(bars))
71
+ if frame.empty:
72
+ return frame
73
+ frame["timestamp"] = pd.to_datetime(frame["timestamp"], utc=True)
74
+ return frame.set_index("timestamp").sort_index()[["open", "high", "low", "close", "volume"]]
75
+
76
+
77
+ def _yahoo(symbol: str, request: DataRequest) -> pd.DataFrame:
78
+ import yfinance as yf
79
+
80
+ interval = {"minute": "1m", "hour": "1h", "daily": "1d"}[request.resolution]
81
+ history = yf.Ticker(symbol).history(
82
+ start=request.start.isoformat(), end=request.end.isoformat(), interval=interval, auto_adjust=False
83
+ )
84
+ if history.empty:
85
+ return pd.DataFrame()
86
+ history.index.name = "timestamp"
87
+ return history.rename(columns={"Open": "open", "High": "high", "Low": "low", "Close": "close", "Volume": "volume"})[
88
+ ["open", "high", "low", "close", "volume"]
89
+ ]
90
+
91
+
92
+ def _binance(symbol: str, request: DataRequest) -> pd.DataFrame:
93
+ interval = {"minute": "1m", "hour": "1h", "daily": "1d"}[request.resolution]
94
+ response = requests.get("https://api.binance.com/api/v3/klines", params={
95
+ "symbol": symbol.upper(), "interval": interval,
96
+ "startTime": int(_as_datetime(request.start).timestamp() * 1000),
97
+ "endTime": int(_as_datetime(request.end, True).timestamp() * 1000), "limit": 1000,
98
+ }, timeout=60)
99
+ response.raise_for_status()
100
+ return _frame_from_bars({
101
+ "timestamp": datetime.fromtimestamp(row[0] / 1000), "open": row[1], "high": row[2],
102
+ "low": row[3], "close": row[4], "volume": row[5],
103
+ } for row in response.json())
104
+
105
+
106
+ def _polygon(symbol: str, request: DataRequest) -> pd.DataFrame:
107
+ timespan = {"minute": "minute", "hour": "hour", "daily": "day"}[request.resolution]
108
+ key = os.getenv("POLYGON_API_KEY")
109
+ if not key:
110
+ raise RuntimeError("POLYGON_API_KEY is not configured on the server")
111
+ response = requests.get(
112
+ f"https://api.polygon.io/v2/aggs/ticker/{symbol}/range/1/{timespan}/{request.start}/{request.end}",
113
+ params={"apiKey": key, "limit": 50000}, timeout=120,
114
+ )
115
+ response.raise_for_status()
116
+ return _frame_from_bars({
117
+ "timestamp": datetime.fromtimestamp(row["t"] / 1000), "open": row["o"], "high": row["h"],
118
+ "low": row["l"], "close": row["c"], "volume": row.get("v", 0),
119
+ } for row in response.json().get("results", []))
120
+
121
+
122
+ def _databento(symbol: str, request: DataRequest) -> pd.DataFrame:
123
+ import databento as db
124
+ from databento import Schema, SType
125
+
126
+ key = os.getenv("DATABENTO_API_KEY")
127
+ if not key:
128
+ raise RuntimeError("DATABENTO_API_KEY is not configured on the server")
129
+ continuous = symbol.replace(".FUT", ".c.0")
130
+ schema = {"tick": Schema.MBP_1, "second": Schema.OHLCV_1S, "minute": Schema.OHLCV_1M, "hour": Schema.OHLCV_1H, "daily": Schema.OHLCV_1D}[request.resolution]
131
+ data = db.Historical(key=key).timeseries.get_range(
132
+ dataset="GLBX.MDP3", symbols=continuous, schema=schema,
133
+ start=request.start.isoformat(), end=request.end.isoformat(),
134
+ stype_in=SType.CONTINUOUS if ".c." in continuous else SType.RAW_SYMBOL,
135
+ ).to_df()
136
+ if data.empty:
137
+ return data
138
+ data = data.rename(columns={"ts_event": "timestamp"}).set_index("timestamp")
139
+ return data[["open", "high", "low", "close", "volume"]]
140
+
141
+
142
+ def fetch_symbol(request: DataRequest, symbol: str) -> tuple[str, pd.DataFrame]:
143
+ provider = select_provider(symbol, request.provider, request.resolution)
144
+ fetchers = {"yahoo": _yahoo, "binance": _binance, "polygon": _polygon, "databento": _databento}
145
+ frame = fetchers[provider](symbol, request)
146
+ if not frame.empty:
147
+ frame.index = pd.to_datetime(frame.index, utc=True)
148
+ frame.insert(0, "symbol", symbol)
149
+ frame.insert(1, "provider", provider)
150
+ return provider, frame
151
+
152
+
153
+ def fetch(request: DataRequest) -> pd.DataFrame:
154
+ if request.start > request.end:
155
+ raise UnsupportedRequest("start must be before or equal to end")
156
+ if not request.symbols:
157
+ raise UnsupportedRequest("at least one symbol is required")
158
+ frames = [frame for symbol in request.symbols if not (frame := fetch_symbol(request, symbol)[1]).empty]
159
+ if not frames:
160
+ return pd.DataFrame(columns=["symbol", "provider", "open", "high", "low", "close", "volume"])
161
+ return pd.concat(frames).sort_index()
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: arithmaxchest
3
+ Version: 0.1.0
4
+ Summary: Centralized market data API and Python client
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: fastapi>=0.110
9
+ Requires-Dist: httpx>=0.27
10
+ Requires-Dist: pandas>=2.0
11
+ Requires-Dist: pyarrow>=14.0
12
+ Requires-Dist: requests>=2.31
13
+ Provides-Extra: yahoo
14
+ Requires-Dist: yfinance>=0.2.40; extra == "yahoo"
15
+ Provides-Extra: futures
16
+ Requires-Dist: databento>=1.0; extra == "futures"
17
+ Provides-Extra: server
18
+ Requires-Dist: uvicorn[standard]>=0.29; extra == "server"
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=8.0; extra == "dev"
21
+ Provides-Extra: all
22
+ Requires-Dist: yfinance>=0.2.40; extra == "all"
23
+ Requires-Dist: databento>=1.0; extra == "all"
24
+ Requires-Dist: uvicorn[standard]>=0.29; extra == "all"
25
+ Dynamic: license-file
26
+
27
+ # Arithmax Chest
28
+
29
+ A standalone FastAPI service and Python client for querying centralized market data. Provider API keys stay on the server; research projects use only the client URL and token.
30
+
31
+ ## Server
32
+
33
+ ```bash
34
+ python -m pip install -e '.[all]'
35
+ export DATA_API_TOKEN='private-client-token'
36
+ export BINANCE_API_KEY='server-only-if-required'
37
+ export POLYGON_API_KEY='server-only'
38
+ export DATABENTO_API_KEY='server-only'
39
+ uvicorn achest.server:app --host 0.0.0.0 --port 8000
40
+ ```
41
+
42
+ ## Client
43
+
44
+ ```python
45
+ from achest import MarketDataClient
46
+
47
+ with MarketDataClient('https://data.example.com', token='private-client-token') as client:
48
+ frame = client.get(['BTCUSDT', 'ES.FUT'], '2024-01-01', '2024-01-31')
49
+ client.download(['BTCUSDT'], '2024-01-01', '2024-01-31', 'data/btc.parquet')
50
+ ```
51
+
52
+ `auto` routes crypto to Binance, futures to Databento first, and equities to Yahoo. Supported response formats are JSON, CSV, and Parquet. Large downloads should be moved to a background job endpoint before production use.
53
+
54
+ ## PyPI publishing
55
+
56
+ ```bash
57
+ python -m pip install build twine
58
+ python -m build
59
+ python -m twine upload dist/*
60
+ ```
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ achest/__init__.py
5
+ achest/client.py
6
+ achest/server.py
7
+ achest/service.py
8
+ arithmaxchest.egg-info/PKG-INFO
9
+ arithmaxchest.egg-info/SOURCES.txt
10
+ arithmaxchest.egg-info/dependency_links.txt
11
+ arithmaxchest.egg-info/requires.txt
12
+ arithmaxchest.egg-info/top_level.txt
13
+ tests/test_routing.py
@@ -0,0 +1,22 @@
1
+ fastapi>=0.110
2
+ httpx>=0.27
3
+ pandas>=2.0
4
+ pyarrow>=14.0
5
+ requests>=2.31
6
+
7
+ [all]
8
+ yfinance>=0.2.40
9
+ databento>=1.0
10
+ uvicorn[standard]>=0.29
11
+
12
+ [dev]
13
+ pytest>=8.0
14
+
15
+ [futures]
16
+ databento>=1.0
17
+
18
+ [server]
19
+ uvicorn[standard]>=0.29
20
+
21
+ [yahoo]
22
+ yfinance>=0.2.40
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "arithmaxchest"
7
+ version = "0.1.0"
8
+ description = "Centralized market data API and Python client"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "fastapi>=0.110",
13
+ "httpx>=0.27",
14
+ "pandas>=2.0",
15
+ "pyarrow>=14.0",
16
+ "requests>=2.31",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ yahoo = ["yfinance>=0.2.40"]
21
+ futures = ["databento>=1.0"]
22
+ server = ["uvicorn[standard]>=0.29"]
23
+ dev = ["pytest>=8.0"]
24
+ all = ["yfinance>=0.2.40", "databento>=1.0", "uvicorn[standard]>=0.29"]
25
+
26
+ [tool.setuptools.packages.find]
27
+ include = ["achest*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,18 @@
1
+ from achest.service import select_provider
2
+
3
+
4
+ def test_crypto_routes_to_binance():
5
+ assert select_provider("BTCUSDT", "auto", "daily") == "binance"
6
+
7
+
8
+ def test_futures_routes_to_databento():
9
+ assert select_provider("ES.FUT", "auto", "minute") == "databento"
10
+
11
+
12
+ def test_yahoo_cannot_be_used_for_futures():
13
+ try:
14
+ select_provider("ES.FUT", "yahoo", "daily")
15
+ except ValueError as error:
16
+ assert "does not support futures" in str(error)
17
+ else:
18
+ raise AssertionError("Yahoo must not be accepted for futures")