fix(journal): keep fiat currency pairs (EUR/USD) out of the crypto market

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
This commit is contained in:
Rudy Celekli committed 2026-10-15 01:41:04 -04:00
1 parent d7f1b9e22b
commit 5ef6d37b84
2 files changed
+54 -1

No files matched your search

+15 -1
View File
@@ -24,6 +24,14 @@ _CURRENCY_TOKEN_RE = re.compile(
r"(?i)(?<![A-Za-z])(?:USDT|USDC|USD|EUR|GBP|JPY|CNY|HKD)(?![A-Za-z])|[$€£¥¥]"
)
# ISO codes of the fiat currencies a broker export can quote as a pair
# (EUR/USD); a pair with two fiat legs is forex, never crypto.
_FIAT_CODES = frozenset({
"USD", "EUR", "GBP", "JPY", "CHF", "AUD", "CAD", "NZD", "CNY", "CNH", "HKD",
"SGD", "SEK", "NOK", "DKK", "MXN", "ZAR", "INR", "KRW", "TRY", "PLN", "CZK",
"HUF", "ILS", "THB",
})
FormatName = str # "tonghuashun" | "eastmoney" | "futu" | "generic" | "unknown"
_A_SHARE_EXCHANGE_MAP = {
@@ -658,7 +666,13 @@ def _infer_market_from_symbol(symbol: str) -> str:
return "uk"
if s.endswith(".SH") or s.endswith(".SZ") or s.endswith(".BJ"):
return "china_a"
if ("-" in s or "/" in s) and any(quote in s for quote in ("USDT", "USDC", "BTC", "USD")):
if "-" in s or "/" in s:
legs = [leg for leg in re.split(r"[-/]", s) if leg]
if len(legs) == 2 and all(leg in _FIAT_CODES for leg in legs):
# EUR/USD, USD-JPY ...: a currency cross, not a crypto pair. The
# shadow-account markets have no forex bucket, so it stays "other".
return "other"
if any(quote in s for quote in ("USDT", "USDC", "BTC", "USD")):
return "crypto"
# Binance-style concatenated pairs (BTCUSDT) are purely alphabetic, so the
# isalpha() US-equity branch below would mis-label them without this check.
@@ -0,0 +1,39 @@
"""A fiat/fiat currency pair in a trade journal must not be labelled crypto."""
from __future__ import annotations
import pytest
import pandas as pd
from src.tools.trade_journal_parsers import _infer_market_from_symbol, parse_generic
@pytest.mark.parametrize(
"symbol",
["EUR/USD", "GBP/USD", "USD/JPY", "usd-cnh", "AUD-USD", "USD/CHF"],
)
def test_fiat_cross_is_not_crypto(symbol: str) -> None:
assert _infer_market_from_symbol(symbol) == "other"
@pytest.mark.parametrize(
"symbol",
["BTC-USD", "BTC/USD", "ETH/BTC", "ETH-USDT", "SOL/USDC", "BTC-EUR", "ETH-USD"],
)
def test_crypto_pairs_stay_crypto(symbol: str) -> None:
assert _infer_market_from_symbol(symbol) == "crypto"
def test_generic_journal_row_for_a_currency_cross_gets_other_market() -> None:
df = pd.DataFrame(
{
"datetime": ["2026-01-02 10:00:00"],
"symbol": ["EUR/USD"],
"side": ["buy"],
"quantity": ["1000"],
"price": ["1.10"],
}
)
(record,) = parse_generic(df)
assert record.market == "other"