quant1x 0.7.2__tar.gz → 0.7.5__tar.gz

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.
Files changed (104) hide show
  1. {quant1x-0.7.2 → quant1x-0.7.5}/PKG-INFO +1 -1
  2. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/cache/__init__.py +2 -0
  3. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/cache/f10.py +13 -1
  4. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/cache/market.py +3 -3
  5. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/cache/sector.py +2 -2
  6. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/cache/security.py +1 -1
  7. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/config/__init__.py +10 -1
  8. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/config/config.py +80 -0
  9. quant1x-0.7.5/quant1x/datasets/__init__.py +22 -0
  10. quant1x-0.7.5/quant1x/datasets/kline.py +286 -0
  11. quant1x-0.7.5/quant1x/datasets/trans.py +462 -0
  12. quant1x-0.7.5/quant1x/datasets/xdxr.py +94 -0
  13. quant1x-0.7.5/quant1x/exchange/__init__.py +62 -0
  14. quant1x-0.7.5/quant1x/exchange/calendar.py +336 -0
  15. quant1x-0.7.5/quant1x/exchange/code.py +363 -0
  16. quant1x-0.7.5/quant1x/exchange/margin_trading.py +177 -0
  17. quant1x-0.7.5/quant1x/exchange/markets.py +102 -0
  18. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/exchange/security.py +1 -1
  19. quant1x-0.7.5/quant1x/exchange/session.py +348 -0
  20. quant1x-0.7.5/quant1x/exchange/timestamp.py +216 -0
  21. quant1x-0.7.5/quant1x/factors/__init__.py +21 -0
  22. quant1x-0.7.5/quant1x/factors/base.py +169 -0
  23. quant1x-0.7.5/quant1x/factors/f10.py +301 -0
  24. quant1x-0.7.5/quant1x/factors/financial_report.py +340 -0
  25. quant1x-0.7.5/quant1x/factors/notice.py +455 -0
  26. quant1x-0.7.5/quant1x/factors/safety_score.py +129 -0
  27. quant1x-0.7.5/quant1x/factors/share_holder.py +141 -0
  28. quant1x-0.7.5/quant1x/level1/block_info.py +68 -0
  29. quant1x-0.7.5/quant1x/level1/block_meta.py +69 -0
  30. quant1x-0.7.5/quant1x/level1/client.py +185 -0
  31. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/level1/config.py +7 -16
  32. quant1x-0.7.5/quant1x/level1/finance_info.py +164 -0
  33. quant1x-0.7.5/quant1x/level1/heartbeat.py +47 -0
  34. quant1x-0.7.5/quant1x/level1/hello1.py +45 -0
  35. quant1x-0.7.5/quant1x/level1/hello2.py +45 -0
  36. quant1x-0.7.5/quant1x/level1/helpers.py +147 -0
  37. quant1x-0.7.5/quant1x/level1/minute_time.py +91 -0
  38. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/level1/protocol.py +6 -10
  39. quant1x-0.7.5/quant1x/level1/security_bars.py +172 -0
  40. quant1x-0.7.5/quant1x/level1/security_count.py +45 -0
  41. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/level1/security_list.py +29 -14
  42. quant1x-0.7.5/quant1x/level1/security_quote.py +391 -0
  43. quant1x-0.7.5/quant1x/level1/transaction.py +220 -0
  44. quant1x-0.7.5/quant1x/level1/xdxr_info.py +227 -0
  45. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/numerics.py +22 -0
  46. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/std/__init__.py +3 -0
  47. quant1x-0.7.5/quant1x/std/time.py +40 -0
  48. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x.egg-info/PKG-INFO +1 -1
  49. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x.egg-info/SOURCES.txt +39 -2
  50. {quant1x-0.7.2 → quant1x-0.7.5}/tests/test_cache.py +2 -2
  51. {quant1x-0.7.2 → quant1x-0.7.5}/tests/test_handshake.py +7 -3
  52. quant1x-0.7.5/tests/test_kline.py +194 -0
  53. quant1x-0.7.5/tests/test_margin_trading.py +22 -0
  54. quant1x-0.7.5/tests/test_notice.py +53 -0
  55. quant1x-0.7.5/tests/test_protocol_kline.py +220 -0
  56. quant1x-0.7.5/tests/test_real_kline.py +53 -0
  57. quant1x-0.7.5/tests/test_safety_score.py +29 -0
  58. quant1x-0.7.5/tests/test_share_holder.py +61 -0
  59. quant1x-0.7.5/tests/test_trans.py +137 -0
  60. quant1x-0.7.5/tests/test_xdxr.py +68 -0
  61. quant1x-0.7.2/quant1x/exchange/__init__.py +0 -22
  62. quant1x-0.7.2/quant1x/exchange/code.py +0 -76
  63. quant1x-0.7.2/quant1x/exchange/markets.py +0 -340
  64. quant1x-0.7.2/quant1x/level1/client.py +0 -200
  65. quant1x-0.7.2/quant1x/timestamp.py +0 -170
  66. {quant1x-0.7.2 → quant1x-0.7.5}/LICENSE +0 -0
  67. {quant1x-0.7.2 → quant1x-0.7.5}/README.md +0 -0
  68. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/__init__.py +0 -0
  69. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/device.py +0 -0
  70. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/devp.py +0 -0
  71. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/encoding/__init__.py +0 -0
  72. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/encoding/yaml.py +0 -0
  73. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/formula/__init__.py +0 -0
  74. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/formula/formula.py +0 -0
  75. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/formula/formula_plus.py +0 -0
  76. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/indicators/__init__.py +0 -0
  77. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/indicators/cdtd.py +0 -0
  78. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/indicators/f89k.py +0 -0
  79. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/indicators/hjk.py +0 -0
  80. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/indicators/ma1x.py +0 -0
  81. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/indicators/p2fb.py +0 -0
  82. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/level1/__init__.py +0 -0
  83. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/log/__init__.py +0 -0
  84. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/log/logger.py +0 -0
  85. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/net/__init__.py +0 -0
  86. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/net/connection_pool.py +0 -0
  87. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/net/endpoint.py +0 -0
  88. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/net/operation_handler.py +0 -0
  89. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/network.py +0 -0
  90. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/std/pattern.py +0 -0
  91. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/system.py +0 -0
  92. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/util/__init__.py +0 -0
  93. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/util/chart.py +0 -0
  94. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/util/font.py +0 -0
  95. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x/version.py +0 -0
  96. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x.egg-info/dependency_links.txt +0 -0
  97. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x.egg-info/not-zip-safe +0 -0
  98. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x.egg-info/requires.txt +0 -0
  99. {quant1x-0.7.2 → quant1x-0.7.5}/quant1x.egg-info/top_level.txt +0 -0
  100. {quant1x-0.7.2 → quant1x-0.7.5}/setup.cfg +0 -0
  101. {quant1x-0.7.2 → quant1x-0.7.5}/setup.py +0 -0
  102. {quant1x-0.7.2 → quant1x-0.7.5}/tests/test_connection_pool.py +0 -0
  103. {quant1x-0.7.2 → quant1x-0.7.5}/tests/test_protocol_fixtures.py +0 -0
  104. {quant1x-0.7.2 → quant1x-0.7.5}/tests/test_protocol_real_network.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quant1x
3
- Version: 0.7.2
3
+ Version: 0.7.5
4
4
  Summary: Quant1X量化研究
5
5
  Home-page: https://gitee.com/quant1x/quant1x
6
6
  Author: wangfeng
@@ -5,3 +5,5 @@ from .security import securities, stock_name
5
5
  from .market import klines, get_period_name, convert_klines_trading, get_minutes_data, get_tick_transaction, date_format
6
6
  from .sector import block_list, sector_filename, get_sector_list, get_sector_constituents
7
7
  from .f10 import cache_f10, get_f10
8
+
9
+ trains_begin_date = "2024-10-01"
@@ -8,12 +8,24 @@ from .. import exchange, config
8
8
 
9
9
  @lru_cache(maxsize=None)
10
10
  def cache_f10(date: str = None) -> DataFrame:
11
+ """
12
+ 从闪存数据中读取F10因子数据
13
+
14
+ Args:
15
+ date (str, optional): 交易日期,格式为'YYYYMMDD'。如果未提供,则使用交易所最后一个交易日
16
+
17
+ Returns:
18
+ DataFrame: 包含F10因子数据的Pandas DataFrame
19
+
20
+ Raises:
21
+ FileNotFoundError: 如果指定日期的数据文件不存在
22
+ """
11
23
  factor_name = 'f10'
12
24
  trade_date = date or exchange.last_trade_date()
13
25
  file_extension = exchange.fix_trade_date(trade_date)
14
26
  filename = f"{factor_name}.{file_extension}"
15
27
  year = trade_date[:4]
16
- base_path = os.path.join(config.quant1x_config.data_path, 'flash')
28
+ base_path = os.path.join(config.data_path, 'flash')
17
29
  return pd.read_csv(os.path.join(base_path, year, filename))
18
30
 
19
31
  def get_f10(code: str, date: str = None) -> Dict[str, Any]:
@@ -14,7 +14,7 @@ def klines(code: str, freq:str = 'D') -> pd.DataFrame | None:
14
14
  corrected_symbol = exchange.correct_security_code(code)
15
15
  suffix_length = 3 # 修正拼写并明确表示后缀长度
16
16
  freq_path = 'day' if freq == 'D' else freq
17
- symbol_directory = os.path.join(config.quant1x_config.data_path, freq_path, corrected_symbol[:-suffix_length]) # 更清晰表达目录用途
17
+ symbol_directory = os.path.join(config.data_path, freq_path, corrected_symbol[:-suffix_length]) # 更清晰表达目录用途
18
18
  file_extension = '.csv'
19
19
  filename = f"{corrected_symbol}{file_extension}" # 使用f-string格式化
20
20
  full_path = os.path.join(symbol_directory, filename)
@@ -106,7 +106,7 @@ def get_minutes_data(code: str, date: str) -> DataFrame | None:
106
106
  # 获取年份
107
107
  cache_date = date_format(cache_date, layout='%Y%m%d')
108
108
  year = cache_date[:4]
109
- base_path = os.path.join(config.quant1x_config.data_path, 'minutes')
109
+ base_path = os.path.join(config.data_path, 'minutes')
110
110
  full_path = os.path.join(base_path, year, cache_date, filename)
111
111
 
112
112
  if os.path.isfile(full_path):
@@ -126,7 +126,7 @@ def get_tick_transaction(code: str, date: str) -> DataFrame | None:
126
126
  # 获取年份
127
127
  cache_date = date_format(cache_date, layout='%Y%m%d')
128
128
  year = cache_date[:4]
129
- base_path = os.path.join(config.quant1x_config.data_path, 'trans')
129
+ base_path = os.path.join(config.data_path, 'trans')
130
130
  full_path = os.path.join(base_path, year, cache_date, filename)
131
131
 
132
132
  if os.path.isfile(full_path):
@@ -15,10 +15,10 @@ def sector_filename(date: str = '') -> str:
15
15
  cache_date = date.strip()
16
16
  if len(cache_date) == 0:
17
17
  cache_date = exchange.last_trade_date()
18
- filename = os.path.join(config.quant1x_config.meta_path, f'{name}.{cache_date}')
18
+ filename = os.path.join(config.meta_path, f'{name}.{cache_date}')
19
19
  if not os.path.isfile(filename):
20
20
  # fallback to blocks.csv
21
- filename = os.path.join(config.quant1x_config.meta_path, 'blocks.csv')
21
+ filename = os.path.join(config.meta_path, 'blocks.csv')
22
22
  return filename
23
23
 
24
24
  @lru_cache(maxsize=None)
@@ -9,7 +9,7 @@ def securities() -> pd.DataFrame:
9
9
  """
10
10
  证券列表
11
11
  """
12
- full_path = os.path.join(config.quant1x_config.meta_path, 'securities.csv')
12
+ full_path = os.path.join(config.meta_path, 'securities.csv')
13
13
  if not os.path.isfile(full_path):
14
14
  return pd.DataFrame(columns=['code', 'name'])
15
15
  # with open(full_path, 'rb') as f:
@@ -10,10 +10,19 @@
10
10
  """
11
11
  from .config import (
12
12
  get_quant1x_config_filename,
13
+ get_historical_trade_filename,
13
14
  quant1x_config
14
15
  )
15
16
 
17
+ # 导出常用配置路径
18
+ data_path = quant1x_config.data_path
19
+ meta_path = quant1x_config.meta_path
20
+ kline_path = quant1x_config.kline_path
21
+
16
22
  __all__ = [
17
- "quant1x_config",
18
23
  "get_quant1x_config_filename",
24
+ "get_historical_trade_filename",
25
+ "data_path",
26
+ "meta_path",
27
+ "kline_path",
19
28
  ]
@@ -117,3 +117,83 @@ class Quant1XConfig:
117
117
 
118
118
  # 创建配置单例
119
119
  quant1x_config = Quant1XConfig()
120
+
121
+ def get_historical_trade_filename(code: str, date: str) -> str:
122
+ """
123
+ 获取历史成交记录文件路径
124
+ 目录结构: ${trans}/${YYYY}/${YYYYMMDD}/${SecurityCode}.csv
125
+ """
126
+ date_str = date.replace('-', '').replace('/', '')
127
+ year = date_str[:4]
128
+ base_path = os.path.join(quant1x_config.data_path, 'trans')
129
+ return os.path.join(base_path, year, date_str, f"{code}.csv")
130
+
131
+ def get_xdxr_path() -> str:
132
+ """获取除权除息文件路径"""
133
+ return os.path.join(quant1x_config.data_path, 'xdxr')
134
+
135
+ def get_xdxr_filename(code: str) -> str:
136
+ """
137
+ 获取除权除息文件路径
138
+ 目录结构: ${xdxr}/${subpath}/${SecurityCode}.csv
139
+ subpath: code[:-3]
140
+ """
141
+ if len(code) <= 3:
142
+ sub = ""
143
+ else:
144
+ sub = code[:-3]
145
+
146
+ base_path = get_xdxr_path()
147
+ return os.path.join(base_path, sub, f"{code}.csv")
148
+
149
+ def get_day_path() -> str:
150
+ """获取日K线文件路径"""
151
+ return quant1x_config.kline_path
152
+
153
+ def get_kline_filename(code: str, forward: bool = True) -> str:
154
+ """
155
+ 获取日K线文件路径
156
+ 目录结构: ${day}/${subpath}/${SecurityCode}.${ext}
157
+ subpath: code[:-3]
158
+ ext: csv if forward else raw
159
+ """
160
+ if len(code) <= 3:
161
+ sub = ""
162
+ else:
163
+ sub = code[:-3]
164
+
165
+ base_path = get_day_path()
166
+ ext = "csv" if forward else "raw"
167
+ return os.path.join(base_path, sub, f"{code}.{ext}")
168
+
169
+ from quant1x import std
170
+
171
+ def get_holding_path() -> str:
172
+ return os.path.join(quant1x_config.data_path, "holding")
173
+
174
+ def cache_id_path(code: str) -> str:
175
+ """
176
+ Generate cache ID path.
177
+ SH600000 -> SH600/SH600000
178
+ """
179
+ if len(code) <= 3:
180
+ return code
181
+
182
+ prefix = code[:-3]
183
+ return os.path.join(prefix, code)
184
+
185
+ def top10_holders_filename(code: str, date_str: str) -> str:
186
+ quarter, _, _ = std.get_quarter_by_date(date_str)
187
+ id_path = cache_id_path(code)
188
+ return os.path.join(get_holding_path(), quarter, f"{id_path}.csv")
189
+
190
+ def quarterly_cache_path(date: str) -> str:
191
+ quarter, _, _ = std.get_quarter_by_date(date)
192
+ return os.path.join(quant1x_config.data_path, "infoq", quarter)
193
+
194
+ def quarterly_filename(date: str, keyword: str) -> str:
195
+ return os.path.join(quarterly_cache_path(date), f"{keyword}.csv")
196
+
197
+ def reports_filename(date: str) -> str:
198
+ return quarterly_filename(date, "reports")
199
+
@@ -0,0 +1,22 @@
1
+
2
+ from .trans import (
3
+ TickTransaction,
4
+ TurnoverDataSummary,
5
+ checkout_transaction_data,
6
+ count_inflow,
7
+ DataTrans
8
+ )
9
+
10
+ from .xdxr import (
11
+ load_xdxr,
12
+ save_xdxr,
13
+ DataXdxr
14
+ )
15
+
16
+ from .kline import (
17
+ KLine,
18
+ load_kline,
19
+ save_kline,
20
+ fetch_kline,
21
+ DataKLine
22
+ )
@@ -0,0 +1,286 @@
1
+ # -*- coding: UTF-8 -*-
2
+ import os
3
+ import pandas as pd
4
+ from dataclasses import dataclass, field
5
+ from typing import List, Optional, Any
6
+ import logging
7
+
8
+ from quant1x.exchange import Timestamp
9
+ from quant1x.level1 import protocol, client
10
+ from quant1x.level1.security_bars import SecurityBarsRequest, SecurityBarsResponse, KLineType, SecurityBar, SECURITY_BARS_MAX
11
+ from quant1x.factors import base as factors
12
+ from quant1x.level1.xdxr_info import XdxrInfo
13
+ from quant1x.datasets import xdxr
14
+ from quant1x.config import config
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ MAX_KLINE_LOOKBACK_DAYS = 1
19
+
20
+ @dataclass
21
+ class KLine:
22
+ Date: str = ""
23
+ Open: float = 0.0
24
+ Close: float = 0.0
25
+ High: float = 0.0
26
+ Low: float = 0.0
27
+ Volume: float = 0.0
28
+ Amount: float = 0.0
29
+ Up: int = 0
30
+ Down: int = 0
31
+ Datetime: str = ""
32
+ AdjustmentCount: int = 0
33
+
34
+ def adjust(self, adj: factors.CumulativeAdjustment):
35
+ self.Open = self.Open * adj.m + adj.a
36
+ self.Close = self.Close * adj.m + adj.a
37
+ self.High = self.High * adj.m + adj.a
38
+ self.Low = self.Low * adj.m + adj.a
39
+
40
+ # 成交量复权
41
+ if self.Volume != 0:
42
+ # 1. 计算均价
43
+ ap = self.Amount / self.Volume
44
+ # 2. 均价复权
45
+ ap_adjusted = ap * adj.m + adj.a
46
+ # 3. 成交量复权
47
+ self.Volume *= (1 + adj.share_adjustment_ratio)
48
+ # 4. 以新成交量*均价计算成交额
49
+ self.Amount = self.Volume * ap_adjusted
50
+
51
+ # 5. 更新除权除息次数
52
+ self.AdjustmentCount = adj.no
53
+
54
+ @staticmethod
55
+ def headers() -> List[str]:
56
+ return ["Date", "Open", "Close", "High", "Low", "Volume", "Amount", "Up", "Down", "Datetime", "AdjustmentCount"]
57
+
58
+ def save_kline(filename: str, values: List[KLine]):
59
+ if not values:
60
+ return
61
+
62
+ dirname = os.path.dirname(filename)
63
+ if dirname:
64
+ os.makedirs(dirname, exist_ok=True)
65
+
66
+ data = [
67
+ {
68
+ "Date": v.Date,
69
+ "Open": v.Open,
70
+ "Close": v.Close,
71
+ "High": v.High,
72
+ "Low": v.Low,
73
+ "Volume": v.Volume,
74
+ "Amount": v.Amount,
75
+ "Up": v.Up,
76
+ "Down": v.Down,
77
+ "Datetime": v.Datetime,
78
+ "AdjustmentCount": v.AdjustmentCount
79
+ }
80
+ for v in values
81
+ ]
82
+
83
+ df = pd.DataFrame(data, columns=KLine.headers())
84
+ df.to_csv(filename, index=False)
85
+
86
+ def read_kline_from_csv(filename: str) -> List[KLine]:
87
+ klines = []
88
+ if not os.path.exists(filename):
89
+ return klines
90
+
91
+ try:
92
+ df = pd.read_csv(filename)
93
+ # Ensure columns exist
94
+ required_cols = KLine.headers()
95
+ if not all(col in df.columns for col in required_cols):
96
+ return klines
97
+
98
+ for _, row in df.iterrows():
99
+ kline = KLine(
100
+ Date=str(row['Date']),
101
+ Open=float(row['Open']),
102
+ Close=float(row['Close']),
103
+ High=float(row['High']),
104
+ Low=float(row['Low']),
105
+ Volume=float(row['Volume']),
106
+ Amount=float(row['Amount']),
107
+ Up=int(row['Up']),
108
+ Down=int(row['Down']),
109
+ Datetime=str(row['Datetime']),
110
+ AdjustmentCount=int(row['AdjustmentCount'])
111
+ )
112
+ klines.append(kline)
113
+ except Exception as e:
114
+ logger.error(f"Failed to read kline csv {filename}: {e}")
115
+
116
+ return klines
117
+
118
+ def load_kline(code: str) -> List[KLine]:
119
+ filename = config.get_kline_filename(code)
120
+ logger.debug(f"[dataset::KLine] kline file: {filename}")
121
+ return read_kline_from_csv(filename)
122
+
123
+ def fetch_kline(code: str, start: int, count: int, kline_type: KLineType = KLineType.DAILY) -> List[SecurityBar]:
124
+ try:
125
+ with client.client() as conn:
126
+ req = SecurityBarsRequest(code, kline_type.value, start, count)
127
+ # Determine is_index from request logic or pass it explicitly if needed
128
+ # SecurityBarsRequest constructor determines is_index
129
+
130
+ resp = SecurityBarsResponse(req.is_index, kline_type.value)
131
+ protocol.process(conn.socket, req, resp)
132
+ return resp.list
133
+ except Exception as e:
134
+ logger.error(f"[dataset::KLine] fetch_kline error: {e}")
135
+ return []
136
+
137
+ def apply_forward_adjustment_for_event(klines: List[KLine],
138
+ current_start_date: Timestamp,
139
+ dividends: List[XdxrInfo]):
140
+ if not klines:
141
+ return
142
+
143
+ # 最后一根K线的日期
144
+ last_day = klines[-1].Date
145
+ # 转成时间戳且对齐时间
146
+ ts_last_day = Timestamp.parse(last_day).get_pre_market_time()
147
+ # 计算最后一根K线的下一个交易日的日期
148
+ # TODO: Implement next_trading_day properly. For now, use +1 day approximation or just rely on date comparison
149
+ last_day_next = ts_last_day.offset(hour=24).only_date() # Approximation
150
+ start_date_str = current_start_date.only_date()
151
+
152
+ # Filter dividends
153
+ xdxr_infos = [
154
+ x for x in dividends
155
+ if x.Date <= last_day_next and x.Category == 1
156
+ ]
157
+
158
+ # Sort by date? C++ uses std::views::filter, order depends on input.
159
+ # Assuming dividends are sorted by date.
160
+
161
+ times = len(xdxr_infos)
162
+
163
+ for info in xdxr_infos:
164
+ if info.Date <= start_date_str:
165
+ # IPO check logic in C++ is commented out or specific?
166
+ # "除权除息数据在日线第一条数据之前... continue" is commented out in C++.
167
+ pass
168
+ else:
169
+ m, a = info.adjust_factor()
170
+ share_ratio = info.compute_share_adjustment_ratio()
171
+
172
+ for kline in klines:
173
+ if kline.Date >= info.Date:
174
+ break
175
+
176
+ if kline.Date < info.Date:
177
+ kline.Open = kline.Open * m + a
178
+ kline.Close = kline.Close * m + a
179
+ kline.High = kline.High * m + a
180
+ kline.Low = kline.Low * m + a
181
+
182
+ if kline.Volume != 0:
183
+ ap = kline.Amount / kline.Volume
184
+ ap_adjusted = ap * m + a
185
+ kline.Volume *= (1 + share_ratio)
186
+ kline.Amount = kline.Volume * ap_adjusted
187
+
188
+ kline.AdjustmentCount += 1
189
+
190
+ times -= 1
191
+
192
+ class DataKLine:
193
+ def update(self, code: str, date: Timestamp):
194
+ # 1. Determine start date from local cache
195
+ current_start_date = Timestamp.parse("1990-12-19") # market_first_date
196
+ cache_filename = config.get_kline_filename(code)
197
+ cache_klines = read_kline_from_csv(cache_filename)
198
+
199
+ klines_length = len(cache_klines)
200
+ klines_offset_days = MAX_KLINE_LOOKBACK_DAYS
201
+ adjust_times = 0
202
+
203
+ if klines_length > 0:
204
+ if klines_offset_days > klines_length:
205
+ klines_offset_days = klines_length
206
+
207
+ kline = cache_klines[klines_length - klines_offset_days]
208
+ current_start_date = Timestamp.parse(kline.Date)
209
+ adjust_times = kline.AdjustmentCount
210
+
211
+ # 2. Determine end date
212
+ current_end_date = Timestamp.now().get_pre_market_time()
213
+ logger.debug(f"[dataset::KLine] [{code}]: from {current_start_date.only_date()} to {current_end_date.only_date()}")
214
+
215
+ step = SECURITY_BARS_MAX
216
+ start = 0
217
+ hs: List[List[SecurityBar]] = []
218
+ element_count = 0
219
+
220
+ while True:
221
+ count = step
222
+ reply = fetch_kline(code, start, count)
223
+ if not reply:
224
+ break
225
+
226
+ element_count += len(reply)
227
+ hs.append(reply)
228
+
229
+ last_bar = reply[-1]
230
+ last_bar_date = Timestamp.parse(f"{last_bar.Year}-{last_bar.Month:02d}-{last_bar.Day:02d}").get_pre_market_time()
231
+
232
+ if last_bar_date < current_start_date:
233
+ break
234
+
235
+ if len(reply) < count:
236
+ break
237
+
238
+ start += count
239
+
240
+ hs.reverse()
241
+
242
+ incremental_klines: List[KLine] = []
243
+
244
+ for vec in hs:
245
+ for row in vec:
246
+ date_time = Timestamp.parse(f"{row.Year}-{row.Month:02d}-{row.Day:02d}").get_pre_market_time()
247
+
248
+ if date_time < current_start_date or date_time > current_end_date:
249
+ continue
250
+
251
+ kx = KLine(
252
+ Date=date_time.only_date(),
253
+ Open=row.Open,
254
+ Close=row.Close,
255
+ High=row.High,
256
+ Low=row.Low,
257
+ Volume=row.Vol * 100, # Convert to shares
258
+ Amount=row.Amount,
259
+ Up=row.UpCount,
260
+ Down=row.DownCount,
261
+ Datetime=row.DateTime,
262
+ AdjustmentCount=0
263
+ )
264
+ incremental_klines.append(kx)
265
+
266
+ # 6. Adjustment logic
267
+ is_fresh_fetch_require_adjustment = (adjust_times == 1)
268
+ dividends = xdxr.load_xdxr(code)
269
+
270
+ if is_fresh_fetch_require_adjustment:
271
+ apply_forward_adjustment_for_event(incremental_klines, current_start_date, dividends)
272
+
273
+ # 7. Merge
274
+ klines = []
275
+ if klines_length > klines_offset_days:
276
+ klines.extend(cache_klines[:klines_length - klines_offset_days])
277
+
278
+ klines.extend(incremental_klines)
279
+
280
+ # 8. Forward adjust
281
+ if not is_fresh_fetch_require_adjustment:
282
+ apply_forward_adjustment_for_event(klines, current_start_date, dividends)
283
+
284
+ # 9. Save
285
+ save_kline(cache_filename, klines)
286
+