quantcli 0.2.1__tar.gz → 0.2.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.
- {quantcli-0.2.1/quantcli.egg-info → quantcli-0.2.5}/PKG-INFO +1 -1
- {quantcli-0.2.1 → quantcli-0.2.5}/pyproject.toml +1 -1
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/cli.py +105 -15
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/core/data.py +3 -3
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/mysql.py +11 -4
- {quantcli-0.2.1 → quantcli-0.2.5/quantcli.egg-info}/PKG-INFO +1 -1
- {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_data_manager.py +9 -7
- {quantcli-0.2.1 → quantcli-0.2.5}/LICENSE +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/README.md +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/core/__init__.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/core/backtest.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/core/factor.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/__init__.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/akshare.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/baostock.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/base.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/cache.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/fundamentals/__init__.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/fundamentals/provider.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/mixed.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/sync/__init__.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/sync/akshare.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/sync/base.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/sync/gm.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/datasources/sync/gm_fundamental.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/__init__.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_001.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_002.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_003.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_004.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_005.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_006.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_007.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_008.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_009.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_010.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_011.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_012.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_013.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_014.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_015.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_016.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_017.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_018.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_019.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_020.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_021.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_022.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_023.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_024.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_025.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_026.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_027.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_028.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_029.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_030.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_031.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_032.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_033.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_034.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_035.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_036.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_037.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_038.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_039.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/alpha101/alpha_040.yaml +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/base.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/compute.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/loader.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/pipeline.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/ranking.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/ranking_executor.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/screening.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/factors/screening_executor.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/models/bar.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/parser/__init__.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/parser/constants.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/parser/formula.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/utils/__init__.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/utils/env.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/utils/logger.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/utils/path.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/utils/symbol_utils.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/utils/time.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli/utils/validate.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli.egg-info/SOURCES.txt +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli.egg-info/dependency_links.txt +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli.egg-info/entry_points.txt +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli.egg-info/requires.txt +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/quantcli.egg-info/top_level.txt +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/setup.cfg +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_akshare_integration.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_builtin_factors.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_cli.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_datasources.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_factors.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_gm_executors.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_mixed_datasource.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_multi_factor.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_pipeline_integration.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_symbol_utils.py +0 -0
- {quantcli-0.2.1 → quantcli-0.2.5}/tests/test_time.py +0 -0
|
@@ -107,10 +107,13 @@ def data(ctx):
|
|
|
107
107
|
@click.option(
|
|
108
108
|
"--source",
|
|
109
109
|
type=click.Choice(["akshare", "baostock", "mixed", "mysql"]),
|
|
110
|
-
default="
|
|
110
|
+
default="mysql",
|
|
111
111
|
help="数据源 (akshare/baostock/mixed/mysql)",
|
|
112
112
|
)
|
|
113
113
|
@click.option("--use-cache/--no-cache", default=True, help="是否使用缓存")
|
|
114
|
+
@click.option(
|
|
115
|
+
"--cache-raw/--no-cache-raw", default=False, help="是否缓存原始数据到parquet"
|
|
116
|
+
)
|
|
114
117
|
@click.option(
|
|
115
118
|
"--output",
|
|
116
119
|
"-o",
|
|
@@ -119,7 +122,7 @@ def data(ctx):
|
|
|
119
122
|
help="输出文件路径 (如: data.csv)",
|
|
120
123
|
)
|
|
121
124
|
@click.pass_context
|
|
122
|
-
def data_fetch(ctx, symbol, start, end, source, use_cache, output):
|
|
125
|
+
def data_fetch(ctx, symbol, start, end, source, use_cache, cache_raw, output):
|
|
123
126
|
"""获取股票日线数据
|
|
124
127
|
|
|
125
128
|
股票代码:
|
|
@@ -131,6 +134,7 @@ def data_fetch(ctx, symbol, start, end, source, use_cache, output):
|
|
|
131
134
|
quantcli data fetch 000001 --start 2025-01-01 --end 2025-12-31
|
|
132
135
|
quantcli data fetch 600519 --start 2025-01-01 --source mysql
|
|
133
136
|
quantcli data fetch 000001 --start 2025-01-01 -o data.csv
|
|
137
|
+
quantcli data fetch 000001 --start 2025-01-01 --cache-raw
|
|
134
138
|
"""
|
|
135
139
|
if end is None:
|
|
136
140
|
end = today()
|
|
@@ -139,7 +143,7 @@ def data_fetch(ctx, symbol, start, end, source, use_cache, output):
|
|
|
139
143
|
f"Fetching {symbol} data from {format_date(start)} to {format_date(end)}..."
|
|
140
144
|
)
|
|
141
145
|
|
|
142
|
-
config = DataConfig(source=source)
|
|
146
|
+
config = DataConfig(source=source, cache_raw=cache_raw)
|
|
143
147
|
dm = DataManager(config)
|
|
144
148
|
|
|
145
149
|
try:
|
|
@@ -169,7 +173,93 @@ def data_fetch(ctx, symbol, start, end, source, use_cache, output):
|
|
|
169
173
|
click.echo(f"Saved to {output}")
|
|
170
174
|
|
|
171
175
|
except Exception as e:
|
|
172
|
-
|
|
176
|
+
error_msg = str(e)
|
|
177
|
+
if "Access denied" in error_msg or "Connection refused" in error_msg:
|
|
178
|
+
click.echo(f"MySQL 连接失败: 请检查数据库配置", err=True)
|
|
179
|
+
click.echo(
|
|
180
|
+
"提示: 使用 --source akshare 或 --source baostock 可以切换到其他数据源",
|
|
181
|
+
err=True,
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
click.echo(f"Error: {e}", err=True)
|
|
185
|
+
sys.exit(1)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@data.command("fetch-fundamental")
|
|
189
|
+
@click.argument("symbols", type=str, nargs=-1)
|
|
190
|
+
@click.option(
|
|
191
|
+
"--date",
|
|
192
|
+
type=date_type,
|
|
193
|
+
default=None,
|
|
194
|
+
help="截止日期 (YYYY-MM-DD,默认今天)",
|
|
195
|
+
)
|
|
196
|
+
@click.option(
|
|
197
|
+
"--source",
|
|
198
|
+
type=click.Choice(["mysql", "baostock"]),
|
|
199
|
+
default="mysql",
|
|
200
|
+
help="数据源 (mysql/baostock)",
|
|
201
|
+
)
|
|
202
|
+
@click.option(
|
|
203
|
+
"--output",
|
|
204
|
+
"-o",
|
|
205
|
+
type=click.Path(),
|
|
206
|
+
default=None,
|
|
207
|
+
help="输出文件路径 (如: data.csv)",
|
|
208
|
+
)
|
|
209
|
+
@click.pass_context
|
|
210
|
+
def data_fetch_fundamental(ctx, symbols, date, source, output):
|
|
211
|
+
"""获取股票基本面数据
|
|
212
|
+
|
|
213
|
+
股票代码:
|
|
214
|
+
000001 - 平安银行
|
|
215
|
+
600519 - 贵州茅台
|
|
216
|
+
|
|
217
|
+
示例:
|
|
218
|
+
quantcli data fetch-fundamental 000001 600519
|
|
219
|
+
quantcli data fetch-fundamental 600519 --date 2024-12-31
|
|
220
|
+
quantcli data fetch-fundamental 000001 --source baostock -o fundamentals.csv
|
|
221
|
+
"""
|
|
222
|
+
if not symbols:
|
|
223
|
+
click.echo("Error: At least one symbol is required")
|
|
224
|
+
sys.exit(1)
|
|
225
|
+
|
|
226
|
+
if date is None:
|
|
227
|
+
date = today()
|
|
228
|
+
|
|
229
|
+
click.echo(
|
|
230
|
+
f"Fetching fundamental data for {len(symbols)} symbols at {format_date(date)}..."
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
config = DataConfig(source=source)
|
|
234
|
+
dm = DataManager(config)
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
df = dm.datasource.get_fundamental(list(symbols), date)
|
|
238
|
+
|
|
239
|
+
if df.empty:
|
|
240
|
+
click.echo(f"No fundamental data found for symbols")
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
click.echo(f"Retrieved {len(df)} rows")
|
|
244
|
+
|
|
245
|
+
click.echo(df.to_string())
|
|
246
|
+
|
|
247
|
+
if output:
|
|
248
|
+
if output.endswith(".csv"):
|
|
249
|
+
df.to_csv(output, index=False)
|
|
250
|
+
elif output.endswith(".parquet"):
|
|
251
|
+
df.to_parquet(output, index=False)
|
|
252
|
+
else:
|
|
253
|
+
df.to_csv(output, index=False)
|
|
254
|
+
click.echo(f"Saved to {output}")
|
|
255
|
+
|
|
256
|
+
except Exception as e:
|
|
257
|
+
error_msg = str(e)
|
|
258
|
+
if "Access denied" in error_msg or "Connection refused" in error_msg:
|
|
259
|
+
click.echo(f"MySQL 连接失败: 请检查数据库配置", err=True)
|
|
260
|
+
click.echo("提示: 使用 --source baostock 可以切换到其他数据源", err=True)
|
|
261
|
+
else:
|
|
262
|
+
click.echo(f"Error: {e}", err=True)
|
|
173
263
|
sys.exit(1)
|
|
174
264
|
|
|
175
265
|
|
|
@@ -191,7 +281,7 @@ def data_cache(ctx):
|
|
|
191
281
|
@click.pass_context
|
|
192
282
|
def data_cache_ls(ctx, pattern, sort):
|
|
193
283
|
"""列出缓存文件"""
|
|
194
|
-
config = DataConfig()
|
|
284
|
+
config = DataConfig(source="mysql")
|
|
195
285
|
dm = DataManager(config)
|
|
196
286
|
sizes = dm.get_cache_size()
|
|
197
287
|
|
|
@@ -229,7 +319,7 @@ def data_cache_ls(ctx, pattern, sort):
|
|
|
229
319
|
@click.pass_context
|
|
230
320
|
def data_cache_clean(ctx, older_than):
|
|
231
321
|
"""清理缓存文件"""
|
|
232
|
-
config = DataConfig()
|
|
322
|
+
config = DataConfig(source="mysql")
|
|
233
323
|
dm = DataManager(config)
|
|
234
324
|
|
|
235
325
|
count = dm.clear_cache(older_than=older_than)
|
|
@@ -240,7 +330,7 @@ def data_cache_clean(ctx, older_than):
|
|
|
240
330
|
@click.pass_context
|
|
241
331
|
def data_health(ctx):
|
|
242
332
|
"""检查数据源健康状态"""
|
|
243
|
-
config = DataConfig()
|
|
333
|
+
config = DataConfig(source="mysql")
|
|
244
334
|
dm = DataManager(config)
|
|
245
335
|
health = dm.health_check()
|
|
246
336
|
|
|
@@ -305,7 +395,7 @@ def factor_run(ctx, name, expr, symbol, start, end, output):
|
|
|
305
395
|
click.echo(f"Computing factor '{name}'...")
|
|
306
396
|
|
|
307
397
|
# 创建引擎
|
|
308
|
-
config = DataConfig()
|
|
398
|
+
config = DataConfig(source="mysql")
|
|
309
399
|
dm = DataManager(config)
|
|
310
400
|
engine = FactorEngine(dm)
|
|
311
401
|
|
|
@@ -366,7 +456,7 @@ def factor_eval(ctx, name, symbol, start, end, method):
|
|
|
366
456
|
|
|
367
457
|
click.echo(f"Evaluating factor '{name}'...")
|
|
368
458
|
|
|
369
|
-
config = DataConfig()
|
|
459
|
+
config = DataConfig(source="mysql")
|
|
370
460
|
dm = DataManager(config)
|
|
371
461
|
engine = FactorEngine(dm)
|
|
372
462
|
|
|
@@ -731,7 +821,7 @@ def filter_run(
|
|
|
731
821
|
if daily_conditions:
|
|
732
822
|
# 只对候选获取日线数据
|
|
733
823
|
click.echo(f"Fetching daily data for {len(candidates)} candidates...")
|
|
734
|
-
dm = DataManager(DataConfig(source="
|
|
824
|
+
dm = DataManager(DataConfig(source="mysql"))
|
|
735
825
|
|
|
736
826
|
price_data = {}
|
|
737
827
|
for symbol in candidates:
|
|
@@ -829,7 +919,7 @@ def filter_run(
|
|
|
829
919
|
# 阶段2: 日线
|
|
830
920
|
click.echo(f"\n=== Stage 2: Daily Screening ===")
|
|
831
921
|
click.echo(f"Fetching daily data for {len(candidates)} candidates...")
|
|
832
|
-
dm = DataManager(DataConfig(source="
|
|
922
|
+
dm = DataManager(DataConfig(source="mysql"))
|
|
833
923
|
|
|
834
924
|
price_data = {}
|
|
835
925
|
for symbol in candidates:
|
|
@@ -885,7 +975,7 @@ def filter_run(
|
|
|
885
975
|
return
|
|
886
976
|
|
|
887
977
|
click.echo(f"Fetching price data for {len(candidates)} candidates...")
|
|
888
|
-
dm = DataManager(DataConfig(source="
|
|
978
|
+
dm = DataManager(DataConfig(source="mysql"))
|
|
889
979
|
|
|
890
980
|
price_data = {}
|
|
891
981
|
for symbol in candidates:
|
|
@@ -1303,7 +1393,7 @@ def analyze_ic(ctx, expr, name, symbol, start, end, period, window, method):
|
|
|
1303
1393
|
click.echo(f"Forward period: {period} days, Window: {window}")
|
|
1304
1394
|
|
|
1305
1395
|
# 获取数据
|
|
1306
|
-
config = DataConfig()
|
|
1396
|
+
config = DataConfig(source="mysql")
|
|
1307
1397
|
dm = DataManager(config)
|
|
1308
1398
|
df = dm.get_daily(symbol, start, end)
|
|
1309
1399
|
|
|
@@ -1422,7 +1512,7 @@ def analyze_batch(ctx, dir, symbol, start, end, period, window, top, output):
|
|
|
1422
1512
|
click.echo(f"Analyzing with period={period}d, window={window}...\n")
|
|
1423
1513
|
|
|
1424
1514
|
# 获取数据
|
|
1425
|
-
config = DataConfig()
|
|
1515
|
+
config = DataConfig(source="mysql")
|
|
1426
1516
|
dm = DataManager(config)
|
|
1427
1517
|
df = dm.get_daily(symbol, start, end)
|
|
1428
1518
|
|
|
@@ -1541,7 +1631,7 @@ def config():
|
|
|
1541
1631
|
@click.pass_context
|
|
1542
1632
|
def config_show(ctx):
|
|
1543
1633
|
"""显示当前配置"""
|
|
1544
|
-
config = DataConfig()
|
|
1634
|
+
config = DataConfig(source="mysql")
|
|
1545
1635
|
click.echo("QuantCLI Configuration:")
|
|
1546
1636
|
click.echo(f" data.source: {config.source}")
|
|
1547
1637
|
click.echo(f" data.cache_dir: {config.cache_dir}")
|
|
@@ -30,6 +30,7 @@ logger = get_logger(__name__)
|
|
|
30
30
|
class DataConfig:
|
|
31
31
|
"""数据管理配置"""
|
|
32
32
|
|
|
33
|
+
cache_raw: bool = False
|
|
33
34
|
source: str = "akshare"
|
|
34
35
|
cache_dir: str = "./data"
|
|
35
36
|
parallel: int = 4
|
|
@@ -141,8 +142,7 @@ class DataManager:
|
|
|
141
142
|
logger.warning(f"No data for {symbol}")
|
|
142
143
|
return df
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
if use_cache:
|
|
145
|
+
if use_cache and self.config.cache_raw:
|
|
146
146
|
self._save_to_cache(cache_file, df, f"stock_daily_{symbol}")
|
|
147
147
|
|
|
148
148
|
return df
|
|
@@ -257,7 +257,7 @@ class DataManager:
|
|
|
257
257
|
|
|
258
258
|
df = self.datasource.get_index_daily(symbol, start_date, end_date)
|
|
259
259
|
|
|
260
|
-
if use_cache and not df.empty:
|
|
260
|
+
if use_cache and self.config.cache_raw and not df.empty:
|
|
261
261
|
self._save_to_cache(cache_file, df, f"index_daily_{symbol}")
|
|
262
262
|
|
|
263
263
|
return df
|
|
@@ -88,9 +88,14 @@ class MySQLDataSource(DataSource):
|
|
|
88
88
|
self._prefix = config_dict["table_prefix"]
|
|
89
89
|
self._autocommit = autocommit
|
|
90
90
|
self._conn = None
|
|
91
|
+
self._tables_initialized = False
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
def _ensure_tables(self):
|
|
94
|
+
"""确保数据库表已初始化(懒加载)"""
|
|
95
|
+
if self._tables_initialized:
|
|
96
|
+
return
|
|
93
97
|
self._init_tables()
|
|
98
|
+
self._tables_initialized = True
|
|
94
99
|
|
|
95
100
|
def _get_connection(self):
|
|
96
101
|
"""获取数据库连接"""
|
|
@@ -208,9 +213,8 @@ class MySQLDataSource(DataSource):
|
|
|
208
213
|
"""批量转换为 MySQL 新格式"""
|
|
209
214
|
return [to_mysql(s) for s in symbols]
|
|
210
215
|
|
|
211
|
-
def get_daily(
|
|
212
|
-
self
|
|
213
|
-
) -> pd.DataFrame:
|
|
216
|
+
def get_daily(self, symbol: str, start_date, end_date) -> pd.DataFrame:
|
|
217
|
+
self._ensure_tables()
|
|
214
218
|
"""获取日线数据
|
|
215
219
|
|
|
216
220
|
Args:
|
|
@@ -559,6 +563,7 @@ class MySQLDataSource(DataSource):
|
|
|
559
563
|
# ==================== 股票列表和日历 ====================
|
|
560
564
|
|
|
561
565
|
def get_stock_list(self, market: str = "all") -> pd.DataFrame:
|
|
566
|
+
self._ensure_tables()
|
|
562
567
|
"""获取股票列表"""
|
|
563
568
|
conn = self._get_connection()
|
|
564
569
|
|
|
@@ -595,6 +600,7 @@ class MySQLDataSource(DataSource):
|
|
|
595
600
|
)
|
|
596
601
|
|
|
597
602
|
def get_trading_calendar(self, exchange: str = "SSE") -> List[date]:
|
|
603
|
+
self._ensure_tables()
|
|
598
604
|
"""获取交易日历"""
|
|
599
605
|
conn = self._get_connection()
|
|
600
606
|
|
|
@@ -619,6 +625,7 @@ class MySQLDataSource(DataSource):
|
|
|
619
625
|
def get_fundamental(
|
|
620
626
|
self, symbols: List[str], date, indicators: Optional[List[str]] = None
|
|
621
627
|
) -> pd.DataFrame:
|
|
628
|
+
self._ensure_tables()
|
|
622
629
|
"""获取基本面数据
|
|
623
630
|
|
|
624
631
|
Args:
|
|
@@ -52,7 +52,7 @@ class TestGetDailyCacheLogic:
|
|
|
52
52
|
self, temp_cache_dir, mock_datasource
|
|
53
53
|
):
|
|
54
54
|
"""缓存存在且完整 → 直接返回缓存"""
|
|
55
|
-
config = DataConfig(cache_dir=temp_cache_dir, source="mock")
|
|
55
|
+
config = DataConfig(cache_dir=temp_cache_dir, source="mock", cache_raw=True)
|
|
56
56
|
dm = DataManager(config)
|
|
57
57
|
dm._datasource = mock_datasource
|
|
58
58
|
|
|
@@ -95,7 +95,7 @@ class TestGetDailyCacheLogic:
|
|
|
95
95
|
|
|
96
96
|
def test_cache_incomplete_fetches_full_data(self, temp_cache_dir, mock_datasource):
|
|
97
97
|
"""缓存不完整 → 全量拉取"""
|
|
98
|
-
config = DataConfig(cache_dir=temp_cache_dir, source="mock")
|
|
98
|
+
config = DataConfig(cache_dir=temp_cache_dir, source="mock", cache_raw=True)
|
|
99
99
|
dm = DataManager(config)
|
|
100
100
|
dm._datasource = mock_datasource
|
|
101
101
|
|
|
@@ -138,7 +138,7 @@ class TestGetDailyCacheLogic:
|
|
|
138
138
|
|
|
139
139
|
def test_no_cache_fetches_full_data(self, temp_cache_dir, mock_datasource):
|
|
140
140
|
"""缓存不存在 → 全量拉取"""
|
|
141
|
-
config = DataConfig(cache_dir=temp_cache_dir, source="mock")
|
|
141
|
+
config = DataConfig(cache_dir=temp_cache_dir, source="mock", cache_raw=True)
|
|
142
142
|
dm = DataManager(config)
|
|
143
143
|
dm._datasource = mock_datasource
|
|
144
144
|
|
|
@@ -165,7 +165,7 @@ class TestGetDailyCacheLogic:
|
|
|
165
165
|
|
|
166
166
|
def test_no_calendar_fetches_full_data(self, temp_cache_dir, mock_datasource):
|
|
167
167
|
"""交易日历不可用 → 直接拉取,不使用缓存"""
|
|
168
|
-
config = DataConfig(cache_dir=temp_cache_dir, source="mock")
|
|
168
|
+
config = DataConfig(cache_dir=temp_cache_dir, source="mock", cache_raw=True)
|
|
169
169
|
dm = DataManager(config)
|
|
170
170
|
dm._datasource = mock_datasource
|
|
171
171
|
|
|
@@ -200,7 +200,7 @@ class TestGetDailyCacheLogic:
|
|
|
200
200
|
self, temp_cache_dir, mock_datasource
|
|
201
201
|
):
|
|
202
202
|
"""交易日历不可用 + 无缓存 → 全量拉取"""
|
|
203
|
-
config = DataConfig(cache_dir=temp_cache_dir, source="mock")
|
|
203
|
+
config = DataConfig(cache_dir=temp_cache_dir, source="mock", cache_raw=True)
|
|
204
204
|
dm = DataManager(config)
|
|
205
205
|
dm._datasource = mock_datasource
|
|
206
206
|
|
|
@@ -222,7 +222,9 @@ class TestGetDailyBatch:
|
|
|
222
222
|
|
|
223
223
|
def test_batch_uses_parallel_when_enabled(self, temp_cache_dir, mock_datasource):
|
|
224
224
|
"""批量获取启用并行"""
|
|
225
|
-
config = DataConfig(
|
|
225
|
+
config = DataConfig(
|
|
226
|
+
cache_dir=temp_cache_dir, source="mock", parallel=2, cache_raw=True
|
|
227
|
+
)
|
|
226
228
|
dm = DataManager(config)
|
|
227
229
|
dm._datasource = mock_datasource
|
|
228
230
|
|
|
@@ -240,7 +242,7 @@ class TestCacheCleanup:
|
|
|
240
242
|
|
|
241
243
|
def test_clear_cache_removes_files(self, temp_cache_dir, mock_datasource):
|
|
242
244
|
"""清理缓存删除文件"""
|
|
243
|
-
config = DataConfig(cache_dir=temp_cache_dir, source="mock")
|
|
245
|
+
config = DataConfig(cache_dir=temp_cache_dir, source="mock", cache_raw=True)
|
|
244
246
|
dm = DataManager(config)
|
|
245
247
|
dm._datasource = mock_datasource
|
|
246
248
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|