Merge PR #1543: feat: add Yahoo market-data routing for Argentina .BA
This commit is contained in:
commit
5397f230ae
12 files changed
+75
-8
No files matched your search
@@ -63,6 +63,9 @@ _MARKET_PATTERNS = [
|
||||
# Canada equities: Toronto Stock Exchange (TD.TO) and TSX Venture
|
||||
# (PNG.V). Yahoo carries both suffixes verbatim.
|
||||
(re.compile(r"^[A-Z0-9&.\-]+\.(TO|V)$", re.I), "ca_equity"),
|
||||
# Argentina: BYMA listings use Yahoo's canonical .BA suffix. Keep this
|
||||
# as a distinct market so ARS can never be mixed with USD/CNY accounting.
|
||||
(re.compile(r"^[A-Z0-9&.\-]+\.BA$", re.I), "ar_equity"),
|
||||
# UK equities: London Stock Exchange (VOD.L, SHEL.L). Yahoo carries the
|
||||
# suffix verbatim.
|
||||
(re.compile(r"^[A-Z0-9&.\-]+\.L$", re.I), "uk_equity"),
|
||||
@@ -164,6 +167,7 @@ _MARKET_CURRENCY = {
|
||||
"india_equity": "INR",
|
||||
"kr_equity": "KRW",
|
||||
"ca_equity": "CAD",
|
||||
"ar_equity": "ARS",
|
||||
"uk_equity": "GBP",
|
||||
"vietnam_equity": "VND",
|
||||
# Every crypto pattern in _MARKET_PATTERNS is USDT-quoted, and USDT is
|
||||
@@ -236,7 +240,7 @@ def _detect_market(code: str) -> str:
|
||||
|
||||
Returns:
|
||||
Market type (a_share/us_equity/hk_equity/india_equity/kr_equity/
|
||||
ca_equity/crypto/futures/forex).
|
||||
ca_equity/ar_equity/crypto/futures/forex).
|
||||
Bare 1-5 letter alphabetic tickers resolve to ``us_equity``;
|
||||
bare 6-letter codes that start with a precious-metal or G10
|
||||
currency code (whitelist) resolve to ``forex``; concatenated
|
||||
|
||||
@@ -50,6 +50,11 @@ def _build_rule_engines(config: dict, codes: List[str]) -> Dict[str, BaseEngine]
|
||||
elif market == "vietnam_equity":
|
||||
from backtest.engines.vietnam_equity import VietnamEquityEngine
|
||||
engines["vietnam_equity"] = VietnamEquityEngine(config)
|
||||
elif market == "ar_equity":
|
||||
raise ValueError(
|
||||
"Argentina .BA market data is supported, but Argentina backtest "
|
||||
"execution rules are not modeled yet"
|
||||
)
|
||||
elif market == "ca_equity":
|
||||
from backtest.engines.global_equity import GlobalEquityEngine
|
||||
engines["ca_equity"] = GlobalEquityEngine(config, market="ca")
|
||||
|
||||
@@ -220,6 +220,8 @@ FALLBACK_CHAINS: dict[str, list[str]] = {
|
||||
"kr_equity": ["pykrx", "yahoo", "yfinance", "local"],
|
||||
# TSX (.TO) / TSX Venture (.V): direct Yahoo first, SDK fallback second.
|
||||
"ca_equity": ["yahoo", "yfinance", "local"],
|
||||
# Argentina (BYMA .BA): Yahoo public chart first, yfinance fallback.
|
||||
"ar_equity": ["yahoo", "yfinance", "local"],
|
||||
# UK (LSE .L): direct Yahoo first, SDK fallback second.
|
||||
"uk_equity": ["yahoo", "yfinance", "local"],
|
||||
# Vietnam (.VN): Yahoo lists HOSE only — HNX and UPCOM are unsupported,
|
||||
|
||||
@@ -64,7 +64,7 @@ def _is_supported(code: str) -> bool:
|
||||
return True
|
||||
return upper.endswith(
|
||||
(
|
||||
".US", ".HK", ".NS", ".BO", ".KS", ".KQ", ".TO", ".V", ".VN",
|
||||
".US", ".HK", ".NS", ".BO", ".KS", ".KQ", ".TO", ".V", ".VN", ".BA",
|
||||
".L", "=F", "=X",
|
||||
)
|
||||
)
|
||||
@@ -192,7 +192,7 @@ class DataLoader:
|
||||
name = "yahoo"
|
||||
markets = {
|
||||
"us_equity", "hk_equity", "india_equity", "kr_equity", "ca_equity",
|
||||
"vietnam_equity", "uk_equity",
|
||||
"vietnam_equity", "uk_equity", "ar_equity",
|
||||
}
|
||||
# Yahoo chart volume is single shares for US/HK equities
|
||||
# (HKUDS/Vibe-Trading#1062; HK verified 2026-08-11, 00700.HK ratio 1.00
|
||||
|
||||
@@ -255,7 +255,7 @@ class DataLoader:
|
||||
name = "yfinance"
|
||||
markets = {
|
||||
"us_equity", "hk_equity", "india_equity", "kr_equity", "ca_equity",
|
||||
"vietnam_equity", "uk_equity", "crypto",
|
||||
"vietnam_equity", "uk_equity", "ar_equity", "crypto",
|
||||
}
|
||||
# yfinance volume is single shares for US/HK equities
|
||||
# (HKUDS/Vibe-Trading#1062; HK verified 2026-08-11, 00700.HK ratio 1.00
|
||||
|
||||
@@ -849,6 +849,7 @@ _MARKET_TO_SOURCE = {
|
||||
"india_equity": "yahoo",
|
||||
"kr_equity": "pykrx",
|
||||
"ca_equity": "yahoo",
|
||||
"ar_equity": "yahoo",
|
||||
"uk_equity": "yahoo",
|
||||
"vietnam_equity": "yahoo",
|
||||
"crypto": "okx",
|
||||
@@ -1531,6 +1532,15 @@ def _create_market_engine(source: str, config: dict, codes: List[str]):
|
||||
if "vietnam_equity" in markets:
|
||||
from backtest.engines.vietnam_equity import VietnamEquityEngine
|
||||
return VietnamEquityEngine(config)
|
||||
# Argentina market-data routing is supported, but BYMA execution rules
|
||||
# are not modeled yet. Fail closed instead of silently applying US/crypto
|
||||
# commissions, lot sizes, settlement, or short-selling assumptions.
|
||||
if "ar_equity" in markets:
|
||||
raise ValueError(
|
||||
"Argentina .BA market data is supported, but Argentina backtest "
|
||||
"execution rules are not modeled yet"
|
||||
)
|
||||
|
||||
# Index symbols (^SPX, ^FTSE, ...) — priced like a US/global-listed
|
||||
# instrument (GlobalEquityEngine, US rules) and never the China/crypto
|
||||
# default the source-based fallback would pick.
|
||||
|
||||
@@ -28,6 +28,8 @@ _SOURCE_PATTERNS = [
|
||||
(re.compile(r"^[A-Z0-9&.\-]+\.(NS|BO)$", re.I), "yahoo"),
|
||||
# Canada: Toronto Stock Exchange (TD.TO) / TSX Venture (PNG.V).
|
||||
(re.compile(r"^[A-Z0-9&.\-]+\.(TO|V)$", re.I), "yahoo"),
|
||||
# Argentina: BYMA equities and CEDEARs use Yahoo's canonical .BA suffix.
|
||||
(re.compile(r"^[A-Z0-9&.\-]+\.BA$", re.I), "yahoo"),
|
||||
# UK: London Stock Exchange (VOD.L, SHEL.L). Yahoo serves the suffix
|
||||
# verbatim; without this they fell through to the tushare default and were
|
||||
# routed to China-market loaders that cannot resolve them.
|
||||
|
||||
@@ -232,6 +232,8 @@ def _market_for(ticker: str) -> str:
|
||||
return "hk"
|
||||
if upper.endswith(".L"):
|
||||
return "uk"
|
||||
if upper.endswith(".BA"):
|
||||
return "ar"
|
||||
return "us"
|
||||
|
||||
|
||||
@@ -240,7 +242,8 @@ class StockProfileTool(BaseTool):
|
||||
|
||||
name = "get_stock_profile"
|
||||
description = (
|
||||
"Fetch a read-only company profile for a US, Hong Kong, or UK (LSE .L) "
|
||||
"Fetch a read-only company profile for a US, Hong Kong, UK (LSE .L), or "
|
||||
"Argentina (BYMA .BA) "
|
||||
"listing from Yahoo Finance: valuation key statistics, analyst price "
|
||||
"targets and earnings/revenue estimates, institutional and insider "
|
||||
"ownership, and the analyst recommendation trend. Use this for "
|
||||
@@ -254,7 +257,7 @@ class StockProfileTool(BaseTool):
|
||||
"ticker": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"US, HK, or UK (LSE .L) symbol. US uses a bare or .US suffix "
|
||||
"US, HK, UK (LSE .L), or Argentina (BYMA .BA) symbol. US uses a bare or .US suffix "
|
||||
"(AAPL or AAPL.US); HK uses a zero-padded .HK code "
|
||||
"(00700.HK); UK uses the .L suffix (VOD.L)."
|
||||
),
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Regression tests for Yahoo-backed Argentina (.BA) market routing.
|
||||
|
||||
These tests are deterministic and do not make network requests. They pin the
|
||||
routing contract separately from Yahoo data availability.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from backtest.engines._market_hooks import _detect_market, code_currency
|
||||
from backtest.loaders.registry import FALLBACK_CHAINS
|
||||
from backtest.loaders.yahoo_loader import _is_supported as yahoo_is_supported
|
||||
from backtest.loaders.yfinance_loader import _to_yfinance_symbol
|
||||
from backtest.runner import _create_market_engine, _detect_source
|
||||
from src.market_data import detect_source
|
||||
from src.tools.stock_profile_tool import _market_for
|
||||
|
||||
|
||||
@pytest.mark.parametrize("symbol", ["GGAL.BA", "PAMP.BA", "GOOGL.BA"])
|
||||
def test_ba_symbols_share_one_market_data_contract(symbol: str) -> None:
|
||||
assert _detect_market(symbol) == "ar_equity"
|
||||
assert code_currency(symbol) == "ARS"
|
||||
assert detect_source(symbol) == "yahoo"
|
||||
assert _detect_source(symbol) == "yahoo"
|
||||
assert yahoo_is_supported(symbol)
|
||||
assert _to_yfinance_symbol(symbol) == symbol
|
||||
assert _market_for(symbol) == "ar"
|
||||
|
||||
|
||||
def test_argentina_fallback_chain_is_yahoo_first() -> None:
|
||||
assert FALLBACK_CHAINS["ar_equity"] == ["yahoo", "yfinance", "local"]
|
||||
|
||||
|
||||
def test_argentina_backtest_fails_closed_until_execution_rules_exist() -> None:
|
||||
with pytest.raises(ValueError, match="Argentina backtest execution rules are not modeled"):
|
||||
_create_market_engine("yahoo", {}, ["GGAL.BA"])
|
||||
@@ -202,6 +202,7 @@ class TestFallbackChains:
|
||||
"india_equity",
|
||||
"kr_equity",
|
||||
"ca_equity",
|
||||
"ar_equity",
|
||||
"uk_equity",
|
||||
"vietnam_equity",
|
||||
"crypto",
|
||||
@@ -216,6 +217,9 @@ class TestFallbackChains:
|
||||
def test_canada_chain_uses_only_compatible_sources(self) -> None:
|
||||
assert FALLBACK_CHAINS["ca_equity"] == ["yahoo", "yfinance", "local"]
|
||||
|
||||
def test_argentina_chain_uses_only_compatible_sources(self) -> None:
|
||||
assert FALLBACK_CHAINS["ar_equity"] == ["yahoo", "yfinance", "local"]
|
||||
|
||||
def test_vietnam_chain_uses_only_compatible_sources(self) -> None:
|
||||
assert FALLBACK_CHAINS["vietnam_equity"] == ["yahoo", "yfinance", "local"]
|
||||
|
||||
|
||||
@@ -690,7 +690,7 @@ def test_get_data_source_settings_lists_default_source_orders(
|
||||
orders = {entry["market"]: entry for entry in entries}
|
||||
assert set(orders) == {
|
||||
"a_share", "us_equity", "hk_equity", "india_equity", "kr_equity",
|
||||
"ca_equity", "vietnam_equity", "uk_equity", "crypto", "futures",
|
||||
"ca_equity", "ar_equity", "vietnam_equity", "uk_equity", "crypto", "futures",
|
||||
"fund", "macro", "forex", "index",
|
||||
}
|
||||
a_share = orders["a_share"]
|
||||
|
||||
@@ -390,7 +390,7 @@ class TestLoaderMetadata:
|
||||
assert loader.name == "yahoo"
|
||||
assert loader.markets == {
|
||||
"us_equity", "hk_equity", "india_equity", "kr_equity", "ca_equity",
|
||||
"vietnam_equity", "uk_equity",
|
||||
"vietnam_equity", "uk_equity", "ar_equity",
|
||||
}
|
||||
assert loader.requires_auth is False
|
||||
assert loader.is_available() is True
|
||||
Reference in new issue
Block a user