-
Notifications
You must be signed in to change notification settings - Fork 54.3k
Expand file tree
/
Copy pathstock_code_utils.py
More file actions
102 lines (85 loc) · 3.08 KB
/
Copy pathstock_code_utils.py
File metadata and controls
102 lines (85 loc) · 3.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# -*- coding: utf-8 -*-
"""
Shared stock code utilities.
"""
from __future__ import annotations
import re
from typing import Optional
from data_provider.base import is_bse_code
# Known exchange prefixes (case-insensitive) and the digit lengths they accept.
# e.g. SH600519 -> 600519, HK00700 -> 00700
_PREFIX_DIGIT_LENS: dict = {
"SH": (6,),
"SZ": (6,),
"SS": (6,),
"BJ": (6,),
"HK": (1, 2, 3, 4, 5),
}
_SUFFIX_DIGIT_LENS: dict = {
".SH": (6,),
".SZ": (6,),
".SS": (6,),
".BJ": (6,),
".HK": (1, 2, 3, 4, 5),
}
def _valid_exchange_code(exchange: str, base: str, digit_lens: tuple[int, ...]) -> bool:
if not (base.isdigit() and len(base) in digit_lens):
return False
if exchange == "BJ":
return is_bse_code(base)
return True
def _strip_exchange_prefix(text: str) -> Optional[str]:
"""Strip leading exchange prefix (SH/SZ/HK etc.) and return the bare digits, or None."""
for prefix, digit_lens in _PREFIX_DIGIT_LENS.items():
if text.startswith(prefix):
base = text[len(prefix):]
if _valid_exchange_code(prefix, base, digit_lens):
return base.zfill(5) if prefix == "HK" else base
return None
def _strip_exchange_suffix(text: str) -> Optional[str]:
"""Strip exchange suffix (.SH/.SZ/.SS/.HK) and return normalized bare digits, or None."""
for suffix, digit_lens in _SUFFIX_DIGIT_LENS.items():
if text.endswith(suffix):
base = text[: -len(suffix)].strip()
exchange = suffix.lstrip(".")
if _valid_exchange_code(exchange, base, digit_lens):
return base.zfill(5) if suffix == ".HK" else base
return None
def is_code_like(value: str) -> bool:
"""Check if string looks like a stock code (5-6 digits, 1-5 letters, or prefixed code)."""
text = value.strip().upper()
if not text:
return False
if text.isdigit() and len(text) in (5, 6):
return True
if _strip_exchange_suffix(text) is not None:
return True
if re.match(r"^[A-Z]{1,5}(?:\.(?:US|[A-Z]))?$", text):
return True
# Support exchange-prefixed codes: SH600519, SZ000001, BJ920493, HK00700
if _strip_exchange_prefix(text) is not None:
return True
return False
def normalize_code(raw: str) -> Optional[str]:
"""Normalize and validate a single stock code.
Supports:
- Plain digit codes: 600519, 00700
- Suffix format: 600519.SH, 600519.SZ, 920493.BJ, 00700.HK
- Prefix format: SH600519, SZ000001, BJ920493, HK00700 (case-insensitive)
- US ticker symbols: AAPL, TSLA
"""
text = raw.strip().upper()
if not text:
return None
if text.isdigit() and len(text) in (5, 6):
return text
if re.match(r"^[A-Z]{1,5}(?:\.(?:US|[A-Z]))?$", text):
return text
stripped_suffix = _strip_exchange_suffix(text)
if stripped_suffix is not None:
return stripped_suffix
# Support exchange-prefixed codes: SH600519 -> 600519, BJ920493 -> 920493
stripped = _strip_exchange_prefix(text)
if stripped is not None:
return stripped
return None