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 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]