tse-tick 0.3.0__py3-none-any.whl
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.
- tse_tick/__init__.py +174 -0
- tse_tick/cli.py +268 -0
- tse_tick/constants.py +51 -0
- tse_tick/core.py +525 -0
- tse_tick/enhanced.py +851 -0
- tse_tick/event_window.py +237 -0
- tse_tick/features.py +275 -0
- tse_tick/ingest.py +477 -0
- tse_tick/io/__init__.py +0 -0
- tse_tick/io/parquet.py +267 -0
- tse_tick/py.typed +1 -0
- tse_tick/query.py +259 -0
- tse_tick/schemas.py +339 -0
- tse_tick/translate.py +164 -0
- tse_tick-0.3.0.dist-info/METADATA +450 -0
- tse_tick-0.3.0.dist-info/RECORD +20 -0
- tse_tick-0.3.0.dist-info/WHEEL +5 -0
- tse_tick-0.3.0.dist-info/entry_points.txt +2 -0
- tse_tick-0.3.0.dist-info/licenses/LICENSE +21 -0
- tse_tick-0.3.0.dist-info/top_level.txt +1 -0
tse_tick/__init__.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tse_tick — Tokyo Stock Exchange tick data processing for NEEDS data.
|
|
3
|
+
|
|
4
|
+
Authors and contributions:
|
|
5
|
+
Kazumi Li — Schema definitions, package architecture, maintainer
|
|
6
|
+
Masataka Hayashi — Initial pandas-based prototype
|
|
7
|
+
Peter Romero — Original concept and initial project design
|
|
8
|
+
|
|
9
|
+
Developed at Keio University, Nakatsuma Seminar.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
# tse_tick/__init__.py
|
|
13
|
+
import polars as pl
|
|
14
|
+
|
|
15
|
+
__version__ = "0.3.0"
|
|
16
|
+
__author__ = "Kazumi Li"
|
|
17
|
+
__email__ = "kaiwenli@keio.jp"
|
|
18
|
+
__license__ = "MIT"
|
|
19
|
+
__copyright__ = "Copyright 2025-2026"
|
|
20
|
+
|
|
21
|
+
from .enhanced import create_df, export_to_csv, discover_zips, parse_period, read_ticks
|
|
22
|
+
|
|
23
|
+
from .schemas import (
|
|
24
|
+
get_schema_individual_stock_95,
|
|
25
|
+
get_schema_summary_83,
|
|
26
|
+
get_schema_indices_23,
|
|
27
|
+
get_schema_indices_summary,
|
|
28
|
+
get_japanese_column_mapping,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
from .ingest import (
|
|
32
|
+
ingest_single_zip,
|
|
33
|
+
ingest_directory,
|
|
34
|
+
ingest_year,
|
|
35
|
+
ingest_year_from_root,
|
|
36
|
+
ingest_period,
|
|
37
|
+
ingest_event_windows_period,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
from .io.parquet import (
|
|
41
|
+
write_partitioned_parquet,
|
|
42
|
+
read_parquet_partition,
|
|
43
|
+
write_event_window_parquet,
|
|
44
|
+
read_partitioned_parquet,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
from .event_window import extract_event_window, extract_batch_event_windows
|
|
48
|
+
|
|
49
|
+
from .features import (
|
|
50
|
+
compute_spread,
|
|
51
|
+
compute_depth,
|
|
52
|
+
compute_flow_imbalance,
|
|
53
|
+
compute_volatility,
|
|
54
|
+
compute_all_features,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
from .constants import DataType, Language
|
|
58
|
+
from .translate import translate, mapping
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
from .query import (
|
|
62
|
+
query_ticks,
|
|
63
|
+
query_sql,
|
|
64
|
+
get_available_dates,
|
|
65
|
+
get_available_tickers,
|
|
66
|
+
)
|
|
67
|
+
_DUCKDB_AVAILABLE = True
|
|
68
|
+
except ImportError:
|
|
69
|
+
_DUCKDB_AVAILABLE = False
|
|
70
|
+
|
|
71
|
+
def _duckdb_unavailable(*args, **kwargs):
|
|
72
|
+
raise ImportError(
|
|
73
|
+
"DuckDB is required for query functions. Install it with: pip install duckdb>=0.9.0"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
query_ticks = _duckdb_unavailable
|
|
77
|
+
query_sql = _duckdb_unavailable
|
|
78
|
+
get_available_dates = _duckdb_unavailable
|
|
79
|
+
get_available_tickers = _duckdb_unavailable
|
|
80
|
+
|
|
81
|
+
__all__ = [
|
|
82
|
+
"create_df",
|
|
83
|
+
"read_ticks",
|
|
84
|
+
"export_to_csv",
|
|
85
|
+
"discover_zips",
|
|
86
|
+
"parse_period",
|
|
87
|
+
"get_schema_individual_stock_95",
|
|
88
|
+
"get_schema_summary_83",
|
|
89
|
+
"get_schema_indices_23",
|
|
90
|
+
"get_schema_indices_summary",
|
|
91
|
+
"get_japanese_column_mapping",
|
|
92
|
+
"ingest_single_zip",
|
|
93
|
+
"ingest_directory",
|
|
94
|
+
"ingest_year",
|
|
95
|
+
"ingest_year_from_root",
|
|
96
|
+
"ingest_period",
|
|
97
|
+
"ingest_event_windows_period",
|
|
98
|
+
"write_partitioned_parquet",
|
|
99
|
+
"read_parquet_partition",
|
|
100
|
+
"write_event_window_parquet",
|
|
101
|
+
"read_partitioned_parquet",
|
|
102
|
+
"query_ticks",
|
|
103
|
+
"query_sql",
|
|
104
|
+
"get_available_dates",
|
|
105
|
+
"get_available_tickers",
|
|
106
|
+
"extract_event_window",
|
|
107
|
+
"extract_batch_event_windows",
|
|
108
|
+
"compute_spread",
|
|
109
|
+
"compute_depth",
|
|
110
|
+
"compute_flow_imbalance",
|
|
111
|
+
"compute_volatility",
|
|
112
|
+
"compute_all_features",
|
|
113
|
+
"translate",
|
|
114
|
+
"mapping",
|
|
115
|
+
"DataType",
|
|
116
|
+
"Language",
|
|
117
|
+
"__version__",
|
|
118
|
+
"__author__",
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_version():
|
|
123
|
+
return __version__
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_supported_data_types():
|
|
127
|
+
# Derive from the DataType enum so this list can never drift from it.
|
|
128
|
+
return DataType.values()
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_supported_years():
|
|
132
|
+
from datetime import datetime as _dt
|
|
133
|
+
return (2016, _dt.now().year)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def get_info():
|
|
137
|
+
info = f"""
|
|
138
|
+
tse_tick v{__version__}
|
|
139
|
+
========================
|
|
140
|
+
|
|
141
|
+
Author: {__author__}
|
|
142
|
+
License: {__license__}
|
|
143
|
+
|
|
144
|
+
Supported Data Types (output fields):
|
|
145
|
+
- individual_stock (TICST120) - 95 fields
|
|
146
|
+
- stock_summary (TICSS110) - 82 fields (83 raw)
|
|
147
|
+
- indices (TICIT110) - 10 fields (23 raw, 15 in 2016)
|
|
148
|
+
- indices_summary (TICIS110) - 17 fields
|
|
149
|
+
|
|
150
|
+
Year Range: 2016-2025
|
|
151
|
+
|
|
152
|
+
Languages: English (en), Japanese (jp)
|
|
153
|
+
|
|
154
|
+
Quick Start (two access paths):
|
|
155
|
+
# One-shot - read raw ZIPs straight to a filtered DataFrame (no store):
|
|
156
|
+
>>> import tse_tick
|
|
157
|
+
>>> df = tse_tick.read_ticks("DATA_ROOT", ticker_filter={{"7203"}},
|
|
158
|
+
... date="20240201", start_time="09:00:00",
|
|
159
|
+
... end_time="11:30:00")
|
|
160
|
+
|
|
161
|
+
# Two-stage - ingest once into a Parquet store, then query repeatedly:
|
|
162
|
+
>>> from tse_tick import DataType
|
|
163
|
+
>>> tse_tick.query_ticks(store, data_type=DataType.INDIVIDUAL_STOCK,
|
|
164
|
+
... ticker=7203, date="20240201",
|
|
165
|
+
... start_time="09:00:00", end_time="11:30:00")
|
|
166
|
+
|
|
167
|
+
CLI Usage:
|
|
168
|
+
>>> tse-tick ingest --data-type individual_stock --period 2024 \\
|
|
169
|
+
--input-root /path/to/data --output-root /path/to/store
|
|
170
|
+
|
|
171
|
+
For more information, visit:
|
|
172
|
+
https://github.com/tse-tick/tse_tick
|
|
173
|
+
"""
|
|
174
|
+
print(info)
|
tse_tick/cli.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# tse_tick/cli.py
|
|
3
|
+
import argparse
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from tse_tick.ingest import ingest_directory, ingest_year_from_root, ingest_period, ingest_event_windows_period
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _parse_years(years_str: str) -> list[int]:
|
|
13
|
+
result: list[int] = []
|
|
14
|
+
for part in years_str.split(","):
|
|
15
|
+
part = part.strip()
|
|
16
|
+
if "-" in part:
|
|
17
|
+
try:
|
|
18
|
+
start, end = part.split("-")
|
|
19
|
+
result.extend(range(int(start), int(end) + 1))
|
|
20
|
+
except ValueError:
|
|
21
|
+
raise ValueError(f"Invalid year range: {part}")
|
|
22
|
+
else:
|
|
23
|
+
try:
|
|
24
|
+
result.append(int(part))
|
|
25
|
+
except ValueError:
|
|
26
|
+
raise ValueError(f"Invalid year: {part}")
|
|
27
|
+
return sorted(set(result))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _parse_months(months_str: str) -> list[int]:
|
|
31
|
+
result: list[int] = []
|
|
32
|
+
for part in months_str.split(","):
|
|
33
|
+
part = part.strip()
|
|
34
|
+
if "-" in part:
|
|
35
|
+
try:
|
|
36
|
+
start, end = part.split("-")
|
|
37
|
+
result.extend(range(int(start), int(end) + 1))
|
|
38
|
+
except ValueError:
|
|
39
|
+
raise ValueError(f"Invalid month range: {part}")
|
|
40
|
+
else:
|
|
41
|
+
try:
|
|
42
|
+
result.append(int(part))
|
|
43
|
+
except ValueError:
|
|
44
|
+
raise ValueError(f"Invalid month: {part}")
|
|
45
|
+
return sorted(set(result))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _parse_tickers(tickers_str: str) -> set[str]:
|
|
49
|
+
ticker_str = tickers_str.strip()
|
|
50
|
+
if ticker_str.startswith("@"):
|
|
51
|
+
filepath = ticker_str[1:]
|
|
52
|
+
with open(filepath, "r") as f:
|
|
53
|
+
return {line.strip() for line in f if line.strip()}
|
|
54
|
+
return {t.strip() for t in ticker_str.split(",") if t.strip()}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def cmd_ingest(args: argparse.Namespace) -> None:
|
|
58
|
+
valid_types = {"individual_stock", "stock_summary", "indices", "indices_summary"}
|
|
59
|
+
if args.data_type not in valid_types:
|
|
60
|
+
print(f"Error: --data-type must be one of {sorted(valid_types)}", file=sys.stderr)
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
|
|
63
|
+
input_root = args.input_root
|
|
64
|
+
output_root = args.output_root
|
|
65
|
+
|
|
66
|
+
if args.filter_csv and args.data_type != "individual_stock":
|
|
67
|
+
print("Error: --filter-csv is only supported with --data-type individual_stock", file=sys.stderr)
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
|
|
70
|
+
if args.period is not None:
|
|
71
|
+
mode_str = "full"
|
|
72
|
+
if args.filter_csv:
|
|
73
|
+
mode_str = "event-window"
|
|
74
|
+
elif args.tickers:
|
|
75
|
+
mode_str = f"ticker-filter ({len(_parse_tickers(args.tickers))} tickers)"
|
|
76
|
+
print(f"Ingesting by period: {args.period} [{mode_str}]")
|
|
77
|
+
print(f" Data type: {args.data_type}")
|
|
78
|
+
print(f" Output: {output_root}")
|
|
79
|
+
|
|
80
|
+
if args.filter_csv:
|
|
81
|
+
ingest_event_windows_period(
|
|
82
|
+
input_root, output_root,
|
|
83
|
+
period=args.period,
|
|
84
|
+
filter_csv=args.filter_csv,
|
|
85
|
+
window_minutes=args.window,
|
|
86
|
+
resume=not args.no_resume,
|
|
87
|
+
max_workers=args.parallel,
|
|
88
|
+
)
|
|
89
|
+
print("Done")
|
|
90
|
+
else:
|
|
91
|
+
ticker_filter = _parse_tickers(args.tickers) if args.tickers else None
|
|
92
|
+
results = ingest_period(
|
|
93
|
+
input_root, output_root,
|
|
94
|
+
period=args.period,
|
|
95
|
+
data_type=args.data_type,
|
|
96
|
+
language=args.language,
|
|
97
|
+
resume=not args.no_resume,
|
|
98
|
+
max_workers=args.parallel,
|
|
99
|
+
ticker_filter=ticker_filter,
|
|
100
|
+
)
|
|
101
|
+
success = sum(1 for r in results if "error" not in r)
|
|
102
|
+
failed = sum(1 for r in results if "error" in r)
|
|
103
|
+
print(f"Done: {success} succeeded, {failed} failed")
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
years = _parse_years(args.years) if args.years else [args.year] if args.year else None
|
|
107
|
+
if years is None:
|
|
108
|
+
print("Error: --years, --year, or --period is required", file=sys.stderr)
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
|
|
111
|
+
ticker_filter = _parse_tickers(args.tickers) if args.tickers else None
|
|
112
|
+
|
|
113
|
+
if args.flat:
|
|
114
|
+
print(f"Ingesting all ZIPs from flat directory: {input_root}")
|
|
115
|
+
print(f" Data type: {args.data_type}")
|
|
116
|
+
print(f" Output: {output_root}")
|
|
117
|
+
results = ingest_directory(
|
|
118
|
+
input_root, output_root,
|
|
119
|
+
data_type=args.data_type,
|
|
120
|
+
language=args.language,
|
|
121
|
+
max_workers=args.parallel,
|
|
122
|
+
ticker_filter=ticker_filter,
|
|
123
|
+
)
|
|
124
|
+
success = sum(1 for r in results if "error" not in r)
|
|
125
|
+
failed = sum(1 for r in results if "error" in r)
|
|
126
|
+
print(f"Done: {success} succeeded, {failed} failed")
|
|
127
|
+
else:
|
|
128
|
+
print(f"Ingesting from root with discovery: {input_root}")
|
|
129
|
+
print(f" Data type: {args.data_type}")
|
|
130
|
+
print(f" Years: {years}")
|
|
131
|
+
print(f" Output: {output_root}")
|
|
132
|
+
for year in years:
|
|
133
|
+
print(f"\n--- Year {year} ---")
|
|
134
|
+
results = ingest_year_from_root(
|
|
135
|
+
input_root, output_root,
|
|
136
|
+
year=year,
|
|
137
|
+
data_type=args.data_type,
|
|
138
|
+
language=args.language,
|
|
139
|
+
resume=not args.no_resume,
|
|
140
|
+
ticker_filter=ticker_filter,
|
|
141
|
+
)
|
|
142
|
+
success = sum(1 for r in results if "error" not in r)
|
|
143
|
+
failed = sum(1 for r in results if "error" in r)
|
|
144
|
+
print(f"Year {year}: {success} succeeded, {failed} failed")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
148
|
+
parser = argparse.ArgumentParser(
|
|
149
|
+
prog="tse-tick",
|
|
150
|
+
description="High-performance Nikkei NEEDS tick data processing pipeline",
|
|
151
|
+
)
|
|
152
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
153
|
+
|
|
154
|
+
ingest_parser = subparsers.add_parser("ingest", help="Ingest NEEDS ZIP files into Parquet store")
|
|
155
|
+
ingest_parser.add_argument(
|
|
156
|
+
"--data-type",
|
|
157
|
+
required=True,
|
|
158
|
+
choices=["individual_stock", "stock_summary", "indices", "indices_summary"],
|
|
159
|
+
help="Type of NEEDS data to ingest",
|
|
160
|
+
)
|
|
161
|
+
ingest_parser.add_argument(
|
|
162
|
+
"--years",
|
|
163
|
+
type=str,
|
|
164
|
+
default=None,
|
|
165
|
+
help='Year(s) to process, e.g. "2016-2023" or "2018,2019,2020"',
|
|
166
|
+
)
|
|
167
|
+
ingest_parser.add_argument(
|
|
168
|
+
"--year",
|
|
169
|
+
type=int,
|
|
170
|
+
default=None,
|
|
171
|
+
help="Single year to process (alternative to --years)",
|
|
172
|
+
)
|
|
173
|
+
ingest_parser.add_argument(
|
|
174
|
+
"--period",
|
|
175
|
+
type=str,
|
|
176
|
+
default=None,
|
|
177
|
+
help=(
|
|
178
|
+
"Date range to process: YYYY (entire year), "
|
|
179
|
+
"YYYYMM-YYYYMM (month range), or YYYYMMDD-YYYYMMDD (day range). "
|
|
180
|
+
"Takes precedence over --years/--year."
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
ingest_parser.add_argument(
|
|
184
|
+
"--input-root",
|
|
185
|
+
required=True,
|
|
186
|
+
help="Root directory containing NEEDS data in {year}/{yearmonth}/ layout",
|
|
187
|
+
)
|
|
188
|
+
ingest_parser.add_argument(
|
|
189
|
+
"--output-root",
|
|
190
|
+
required=True,
|
|
191
|
+
help="Root directory for Parquet output store",
|
|
192
|
+
)
|
|
193
|
+
ingest_parser.add_argument(
|
|
194
|
+
"--language",
|
|
195
|
+
default="en",
|
|
196
|
+
choices=["en", "jp"],
|
|
197
|
+
help="Column name language (default: en)",
|
|
198
|
+
)
|
|
199
|
+
ingest_parser.add_argument(
|
|
200
|
+
"--parallel",
|
|
201
|
+
type=int,
|
|
202
|
+
default=1,
|
|
203
|
+
help="Number of parallel worker processes (default: 1)",
|
|
204
|
+
)
|
|
205
|
+
ingest_parser.add_argument(
|
|
206
|
+
"--no-resume",
|
|
207
|
+
action="store_true",
|
|
208
|
+
help="Disable resume (reprocess all files even if output exists)",
|
|
209
|
+
)
|
|
210
|
+
ingest_parser.add_argument(
|
|
211
|
+
"--flat",
|
|
212
|
+
action="store_true",
|
|
213
|
+
help="Input directory is a flat folder of ZIPs (no year/month structure)",
|
|
214
|
+
)
|
|
215
|
+
ingest_parser.add_argument(
|
|
216
|
+
"--log-level",
|
|
217
|
+
default="INFO",
|
|
218
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
|
|
219
|
+
help="Logging verbosity level",
|
|
220
|
+
)
|
|
221
|
+
ingest_parser.add_argument(
|
|
222
|
+
"--tickers",
|
|
223
|
+
type=str,
|
|
224
|
+
default=None,
|
|
225
|
+
help=(
|
|
226
|
+
"Comma-separated ticker codes to keep, or @file.txt with one per line. "
|
|
227
|
+
"When provided, only these tickers are included in output."
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
ingest_parser.add_argument(
|
|
231
|
+
"--filter-csv",
|
|
232
|
+
type=str,
|
|
233
|
+
default=None,
|
|
234
|
+
help="Path to event filter CSV (enables event-window mode). Overrides --tickers.",
|
|
235
|
+
)
|
|
236
|
+
ingest_parser.add_argument(
|
|
237
|
+
"--window",
|
|
238
|
+
type=int,
|
|
239
|
+
default=120,
|
|
240
|
+
help="Window in minutes around each event (default: 120). Only used with --filter-csv.",
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
return parser
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def main() -> None:
|
|
247
|
+
parser = _build_parser()
|
|
248
|
+
args = parser.parse_args()
|
|
249
|
+
|
|
250
|
+
if args.command is None:
|
|
251
|
+
parser.print_help()
|
|
252
|
+
sys.exit(1)
|
|
253
|
+
|
|
254
|
+
logging.basicConfig(
|
|
255
|
+
level=getattr(logging, args.log_level, logging.INFO),
|
|
256
|
+
format="%(asctime)s %(levelname)-8s %(message)s",
|
|
257
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
if args.command == "ingest":
|
|
261
|
+
cmd_ingest(args)
|
|
262
|
+
else:
|
|
263
|
+
parser.print_help()
|
|
264
|
+
sys.exit(1)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
if __name__ == "__main__":
|
|
268
|
+
main()
|
tse_tick/constants.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# tse_tick/constants.py
|
|
2
|
+
"""Enumerations for the public API.
|
|
3
|
+
|
|
4
|
+
``DataType`` and ``Language`` give IDE autocomplete and a single source of truth
|
|
5
|
+
for the magic strings used throughout the package. Both subclass ``str``, so a
|
|
6
|
+
member is accepted anywhere the equivalent string is today — comparisons,
|
|
7
|
+
dict/set membership, ``pathlib`` path building and f-strings all see the value::
|
|
8
|
+
|
|
9
|
+
>>> from tse_tick import DataType
|
|
10
|
+
>>> DataType.INDIVIDUAL_STOCK == "individual_stock"
|
|
11
|
+
True
|
|
12
|
+
>>> f"{DataType.INDIVIDUAL_STOCK}"
|
|
13
|
+
'individual_stock'
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from typing import List
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DataType(str, Enum):
|
|
21
|
+
"""NEEDS data type. The value is the canonical string used across the API."""
|
|
22
|
+
|
|
23
|
+
INDIVIDUAL_STOCK = "individual_stock"
|
|
24
|
+
STOCK_SUMMARY = "stock_summary"
|
|
25
|
+
INDICES = "indices"
|
|
26
|
+
INDICES_SUMMARY = "indices_summary"
|
|
27
|
+
|
|
28
|
+
def __str__(self) -> str:
|
|
29
|
+
# Return the bare value (not "DataType.INDIVIDUAL_STOCK") so f-strings and
|
|
30
|
+
# str() match the legacy magic-string behaviour on every Python version.
|
|
31
|
+
return self.value
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def values(cls) -> List[str]:
|
|
35
|
+
"""All data-type values as plain strings, in declaration order."""
|
|
36
|
+
return [member.value for member in cls]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Language(str, Enum):
|
|
40
|
+
"""Output column-name language."""
|
|
41
|
+
|
|
42
|
+
EN = "en"
|
|
43
|
+
JP = "jp"
|
|
44
|
+
|
|
45
|
+
def __str__(self) -> str:
|
|
46
|
+
return self.value
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def values(cls) -> List[str]:
|
|
50
|
+
"""All language values as plain strings, in declaration order."""
|
|
51
|
+
return [member.value for member in cls]
|