deeptrade-quant 0.0.2__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.
Files changed (83) hide show
  1. deeptrade/__init__.py +8 -0
  2. deeptrade/channels_builtin/__init__.py +0 -0
  3. deeptrade/channels_builtin/stdout/__init__.py +0 -0
  4. deeptrade/channels_builtin/stdout/deeptrade_plugin.yaml +25 -0
  5. deeptrade/channels_builtin/stdout/migrations/20260429_001_init.sql +13 -0
  6. deeptrade/channels_builtin/stdout/stdout_channel/__init__.py +0 -0
  7. deeptrade/channels_builtin/stdout/stdout_channel/channel.py +180 -0
  8. deeptrade/cli.py +214 -0
  9. deeptrade/cli_config.py +396 -0
  10. deeptrade/cli_data.py +33 -0
  11. deeptrade/cli_plugin.py +176 -0
  12. deeptrade/core/__init__.py +8 -0
  13. deeptrade/core/config.py +344 -0
  14. deeptrade/core/config_migrations.py +138 -0
  15. deeptrade/core/db.py +176 -0
  16. deeptrade/core/llm_client.py +591 -0
  17. deeptrade/core/llm_manager.py +174 -0
  18. deeptrade/core/logging_config.py +61 -0
  19. deeptrade/core/migrations/__init__.py +0 -0
  20. deeptrade/core/migrations/core/20260427_001_init.sql +121 -0
  21. deeptrade/core/migrations/core/20260501_002_drop_llm_calls_stage.sql +10 -0
  22. deeptrade/core/migrations/core/__init__.py +0 -0
  23. deeptrade/core/notifier.py +302 -0
  24. deeptrade/core/paths.py +49 -0
  25. deeptrade/core/plugin_manager.py +616 -0
  26. deeptrade/core/run_status.py +29 -0
  27. deeptrade/core/secrets.py +152 -0
  28. deeptrade/core/tushare_client.py +824 -0
  29. deeptrade/plugins_api/__init__.py +44 -0
  30. deeptrade/plugins_api/base.py +66 -0
  31. deeptrade/plugins_api/channel.py +42 -0
  32. deeptrade/plugins_api/events.py +61 -0
  33. deeptrade/plugins_api/llm.py +46 -0
  34. deeptrade/plugins_api/metadata.py +84 -0
  35. deeptrade/plugins_api/notify.py +67 -0
  36. deeptrade/strategies_builtin/__init__.py +0 -0
  37. deeptrade/strategies_builtin/limit_up_board/__init__.py +0 -0
  38. deeptrade/strategies_builtin/limit_up_board/deeptrade_plugin.yaml +101 -0
  39. deeptrade/strategies_builtin/limit_up_board/limit_up_board/__init__.py +0 -0
  40. deeptrade/strategies_builtin/limit_up_board/limit_up_board/calendar.py +65 -0
  41. deeptrade/strategies_builtin/limit_up_board/limit_up_board/cli.py +269 -0
  42. deeptrade/strategies_builtin/limit_up_board/limit_up_board/config.py +76 -0
  43. deeptrade/strategies_builtin/limit_up_board/limit_up_board/data.py +1191 -0
  44. deeptrade/strategies_builtin/limit_up_board/limit_up_board/pipeline.py +869 -0
  45. deeptrade/strategies_builtin/limit_up_board/limit_up_board/plugin.py +30 -0
  46. deeptrade/strategies_builtin/limit_up_board/limit_up_board/profiles.py +85 -0
  47. deeptrade/strategies_builtin/limit_up_board/limit_up_board/prompts.py +485 -0
  48. deeptrade/strategies_builtin/limit_up_board/limit_up_board/render.py +890 -0
  49. deeptrade/strategies_builtin/limit_up_board/limit_up_board/runner.py +1087 -0
  50. deeptrade/strategies_builtin/limit_up_board/limit_up_board/runtime.py +172 -0
  51. deeptrade/strategies_builtin/limit_up_board/limit_up_board/schemas.py +178 -0
  52. deeptrade/strategies_builtin/limit_up_board/migrations/20260430_001_init.sql +150 -0
  53. deeptrade/strategies_builtin/limit_up_board/migrations/20260501_002_lub_stage_results_llm_provider.sql +8 -0
  54. deeptrade/strategies_builtin/limit_up_board/migrations/20260508_001_lub_lhb_tables.sql +36 -0
  55. deeptrade/strategies_builtin/limit_up_board/migrations/20260508_002_lub_cyq_perf.sql +18 -0
  56. deeptrade/strategies_builtin/limit_up_board/migrations/20260508_003_lub_lhb_pk_fix.sql +46 -0
  57. deeptrade/strategies_builtin/limit_up_board/migrations/20260508_004_lub_lhb_drop_pk.sql +53 -0
  58. deeptrade/strategies_builtin/limit_up_board/migrations/20260508_005_lub_config.sql +17 -0
  59. deeptrade/strategies_builtin/volume_anomaly/__init__.py +0 -0
  60. deeptrade/strategies_builtin/volume_anomaly/deeptrade_plugin.yaml +59 -0
  61. deeptrade/strategies_builtin/volume_anomaly/migrations/20260430_001_init.sql +94 -0
  62. deeptrade/strategies_builtin/volume_anomaly/migrations/20260601_001_realized_returns.sql +44 -0
  63. deeptrade/strategies_builtin/volume_anomaly/migrations/20260601_002_dimension_scores.sql +13 -0
  64. deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/__init__.py +0 -0
  65. deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/calendar.py +52 -0
  66. deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/cli.py +247 -0
  67. deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/data.py +2154 -0
  68. deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/pipeline.py +327 -0
  69. deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/plugin.py +22 -0
  70. deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/profiles.py +49 -0
  71. deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/prompts.py +187 -0
  72. deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/prompts_examples.py +84 -0
  73. deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/render.py +906 -0
  74. deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/runner.py +772 -0
  75. deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/runtime.py +90 -0
  76. deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/schemas.py +97 -0
  77. deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/stats.py +174 -0
  78. deeptrade/theme.py +48 -0
  79. deeptrade_quant-0.0.2.dist-info/METADATA +166 -0
  80. deeptrade_quant-0.0.2.dist-info/RECORD +83 -0
  81. deeptrade_quant-0.0.2.dist-info/WHEEL +4 -0
  82. deeptrade_quant-0.0.2.dist-info/entry_points.txt +2 -0
  83. deeptrade_quant-0.0.2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,269 @@
1
+ """Plugin-managed CLI for limit-up-board.
2
+
3
+ Subcommands:
4
+ run — full pipeline (Step 0..5)
5
+ sync — data-only path (no LLM)
6
+ history — list recent runs
7
+ report — re-render a finished run's terminal summary
8
+
9
+ Invoked via the framework's pure pass-through dispatch:
10
+ deeptrade limit-up-board <subcommand> [...]
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import sys
16
+
17
+ import questionary
18
+ import typer
19
+ from rich.console import Console
20
+ from rich.table import Table
21
+
22
+ from deeptrade.core import paths
23
+ from deeptrade.core.config import ConfigService
24
+ from deeptrade.core.db import Database
25
+ from deeptrade.core.llm_manager import LLMManager
26
+
27
+ from .config import LubConfig, list_for_show, load_config, save_config
28
+ from .runner import LubRunner, PreconditionError, RunParams, render_finished_run
29
+ from .runtime import LubRuntime
30
+
31
+ app = typer.Typer(
32
+ name="limit-up-board",
33
+ help="打板策略 — A 股涨停板双轮 LLM 漏斗。",
34
+ no_args_is_help=True,
35
+ add_completion=False,
36
+ )
37
+
38
+ settings_app = typer.Typer(
39
+ name="settings",
40
+ help="本插件可持久化的运行参数(流通市值 / 当前股价 上限)。",
41
+ no_args_is_help=False,
42
+ add_completion=False,
43
+ invoke_without_command=True,
44
+ )
45
+ app.add_typer(settings_app, name="settings")
46
+
47
+
48
+ def _open_runtime() -> tuple[Database, LubRuntime]:
49
+ db = Database(paths.db_path())
50
+ cfg = ConfigService(db)
51
+ rt = LubRuntime(db=db, config=cfg, llms=LLMManager(db, cfg))
52
+ return db, rt
53
+
54
+
55
+ @app.command("run")
56
+ def cmd_run(
57
+ trade_date: str | None = typer.Option(None, "--trade-date", help="YYYYMMDD"),
58
+ allow_intraday: bool = typer.Option(False, "--allow-intraday"),
59
+ force_sync: bool = typer.Option(False, "--force-sync"),
60
+ daily_lookback: int = typer.Option(30, "--daily-lookback"),
61
+ moneyflow_lookback: int = typer.Option(5, "--moneyflow-lookback"),
62
+ debate: bool = typer.Option(
63
+ False,
64
+ "--debate",
65
+ help="启用多 LLM 辩论模式(需要 ≥2 个已配置的 LLM provider)",
66
+ ),
67
+ debate_llms: str | None = typer.Option(
68
+ None,
69
+ "--debate-llms",
70
+ help=(
71
+ "逗号分隔的 LLM provider 子集(如 'deepseek,kimi'),"
72
+ "必须配合 --debate 使用;不指定则使用全部已配置 provider"
73
+ ),
74
+ ),
75
+ ) -> None:
76
+ """Run the full打板策略 pipeline."""
77
+ debate_llms_list: list[str] | None = None
78
+ if debate_llms is not None:
79
+ if not debate:
80
+ typer.echo("✘ --debate-llms 必须配合 --debate 使用")
81
+ raise typer.Exit(2)
82
+ debate_llms_list = [s.strip() for s in debate_llms.split(",") if s.strip()]
83
+ if not debate_llms_list:
84
+ typer.echo("✘ --debate-llms 解析后为空")
85
+ raise typer.Exit(2)
86
+
87
+ db, rt = _open_runtime()
88
+ try:
89
+ params = RunParams(
90
+ trade_date=trade_date,
91
+ allow_intraday=allow_intraday,
92
+ force_sync=force_sync,
93
+ daily_lookback=daily_lookback,
94
+ moneyflow_lookback=moneyflow_lookback,
95
+ debate=debate,
96
+ debate_llms=debate_llms_list,
97
+ )
98
+ runner = LubRunner(rt)
99
+ outcome = runner.execute(params)
100
+ typer.echo(f"\nstatus: {outcome.status.value} run_id: {outcome.run_id}")
101
+ if outcome.error:
102
+ typer.echo(f"error: {outcome.error}")
103
+ if outcome.status.value not in {"success", "partial_failed"}:
104
+ raise typer.Exit(1)
105
+ # Print the terminal summary right after a successful run.
106
+ render_finished_run(outcome.run_id)
107
+ finally:
108
+ db.close()
109
+
110
+
111
+ @app.command("sync")
112
+ def cmd_sync(
113
+ trade_date: str | None = typer.Option(None, "--trade-date", help="YYYYMMDD"),
114
+ allow_intraday: bool = typer.Option(False, "--allow-intraday"),
115
+ force_sync: bool = typer.Option(False, "--force-sync"),
116
+ daily_lookback: int = typer.Option(30, "--daily-lookback"),
117
+ moneyflow_lookback: int = typer.Option(5, "--moneyflow-lookback"),
118
+ ) -> None:
119
+ """Fetch + persist data only (no LLM stages)."""
120
+ db, rt = _open_runtime()
121
+ try:
122
+ params = RunParams(
123
+ trade_date=trade_date,
124
+ allow_intraday=allow_intraday,
125
+ force_sync=force_sync,
126
+ daily_lookback=daily_lookback,
127
+ moneyflow_lookback=moneyflow_lookback,
128
+ )
129
+ runner = LubRunner(rt)
130
+ outcome = runner.execute_sync_only(params)
131
+ typer.echo(f"\nstatus: {outcome.status.value} run_id: {outcome.run_id}")
132
+ if outcome.error:
133
+ typer.echo(f"error: {outcome.error}")
134
+ if outcome.status.value != "success":
135
+ raise typer.Exit(1)
136
+ finally:
137
+ db.close()
138
+
139
+
140
+ @app.command("history")
141
+ def cmd_history(limit: int = typer.Option(20, "--limit")) -> None:
142
+ """List recent runs of this plugin."""
143
+ db = Database(paths.db_path())
144
+ try:
145
+ rows = db.fetchall(
146
+ "SELECT run_id, trade_date, status, started_at, finished_at FROM lub_runs "
147
+ "ORDER BY started_at DESC LIMIT ?",
148
+ (limit,),
149
+ )
150
+ finally:
151
+ db.close()
152
+ if not rows:
153
+ typer.echo("(no runs)")
154
+ return
155
+ for r in rows:
156
+ typer.echo(f"{r[0]} {r[1]:<10} {r[2]:<15} {r[3]} → {r[4] or '-'}")
157
+
158
+
159
+ @app.command("report")
160
+ def cmd_report(
161
+ run_id: str = typer.Argument(..., help="Run UUID to view"),
162
+ full: bool = typer.Option(
163
+ False, "--full", help="Print the full markdown summary instead of the concise view."
164
+ ),
165
+ ) -> None:
166
+ """Re-display a finished run's report."""
167
+ if full:
168
+ from rich.console import Console
169
+ from rich.markdown import Markdown
170
+
171
+ from deeptrade.theme import EVA_THEME
172
+
173
+ report_dir = paths.reports_dir() / run_id
174
+ summary = report_dir / "summary.md"
175
+ if not summary.is_file():
176
+ typer.echo(f"✘ no report at {summary}")
177
+ raise typer.Exit(2)
178
+ Console(theme=EVA_THEME).print(Markdown(summary.read_text(encoding="utf-8")))
179
+ typer.echo(f"\nReport directory: {summary.parent}")
180
+ return
181
+ render_finished_run(run_id)
182
+
183
+
184
+ # ---------------------------------------------------------------------------
185
+ # settings — plugin-local persisted run filters (v0.4)
186
+ # ---------------------------------------------------------------------------
187
+
188
+
189
+ def _prompt_positive_float(prompt: str, current: float) -> float:
190
+ raw = questionary.text(f"{prompt} [{current}]:", default=str(current)).ask()
191
+ if raw is None:
192
+ raise typer.Exit(1)
193
+ raw = raw.strip()
194
+ if not raw:
195
+ return current
196
+ try:
197
+ v = float(raw)
198
+ except ValueError as e:
199
+ typer.echo(f"✘ 无法解析为数字: {raw!r}")
200
+ raise typer.Exit(2) from e
201
+ if v <= 0:
202
+ typer.echo(f"✘ 必须大于 0: {v}")
203
+ raise typer.Exit(2)
204
+ return v
205
+
206
+
207
+ @settings_app.callback()
208
+ def cmd_settings(ctx: typer.Context) -> None:
209
+ """交互式编辑当前持久化设置(不带子命令时进入交互;`show` 子命令仅展示)。"""
210
+ if ctx.invoked_subcommand is not None:
211
+ return
212
+ db = Database(paths.db_path())
213
+ try:
214
+ cur = load_config(db)
215
+ new_mv = _prompt_positive_float("流通市值上限(亿)", cur.max_float_mv_yi)
216
+ new_close = _prompt_positive_float("当前股价上限(元)", cur.max_close_yuan)
217
+ new_cfg = LubConfig(max_float_mv_yi=new_mv, max_close_yuan=new_close)
218
+ save_config(db, new_cfg)
219
+ typer.echo(
220
+ f"✔ Saved: 流通市值 < {new_cfg.max_float_mv_yi}亿、股价 < {new_cfg.max_close_yuan}元"
221
+ )
222
+ finally:
223
+ db.close()
224
+
225
+
226
+ @settings_app.command("show")
227
+ def cmd_settings_show() -> None:
228
+ """展示当前生效的设置(来源 = persisted / default)。"""
229
+ db = Database(paths.db_path())
230
+ try:
231
+ rows = list_for_show(db)
232
+ finally:
233
+ db.close()
234
+ console = Console()
235
+ table = Table(title="limit-up-board settings")
236
+ table.add_column("Key", style="cyan")
237
+ table.add_column("Value", overflow="fold")
238
+ table.add_column("Source", style="yellow")
239
+ for key, value, source in rows:
240
+ table.add_row(key, "" if value is None else str(value), source)
241
+ console.print(table)
242
+
243
+
244
+ # ---------------------------------------------------------------------------
245
+ # entry point
246
+ # ---------------------------------------------------------------------------
247
+
248
+
249
+ def main(argv: list[str]) -> int:
250
+ """Plugin's dispatch entry. Returns exit code."""
251
+ try:
252
+ app(argv, standalone_mode=False)
253
+ return 0
254
+ except typer.Exit as e:
255
+ return int(e.exit_code or 0)
256
+ except SystemExit as e:
257
+ try:
258
+ return int(e.code or 0)
259
+ except (TypeError, ValueError):
260
+ return 1
261
+ except KeyboardInterrupt:
262
+ sys.stderr.write("\n✘ cancelled by user\n")
263
+ return 130
264
+ except PreconditionError as e:
265
+ sys.stderr.write(f"✘ {e}\n")
266
+ return 2
267
+ except Exception as e: # noqa: BLE001 — reflect to framework as exit 1
268
+ sys.stderr.write(f"✘ {type(e).__name__}: {e}\n")
269
+ return 1
@@ -0,0 +1,76 @@
1
+ """Plugin-local settings (v0.4).
2
+
3
+ Persisted in the ``lub_config`` table. Defaults live on :class:`LubConfig` and
4
+ are re-applied automatically when a row is missing — DB rows only override.
5
+
6
+ Currently exposed via ``deeptrade limit-up-board settings``:
7
+ * ``max_float_mv_yi`` — 流通市值上限(亿)
8
+ * ``max_close_yuan`` — 当前股价上限(元)
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ from dataclasses import dataclass, fields
15
+ from typing import TYPE_CHECKING, Any
16
+
17
+ if TYPE_CHECKING: # pragma: no cover
18
+ from deeptrade.core.db import Database
19
+
20
+
21
+ @dataclass
22
+ class LubConfig:
23
+ """User-tunable run filters. Defaults reflect a typical 打板 watchlist."""
24
+
25
+ max_float_mv_yi: float = 100.0
26
+ max_close_yuan: float = 15.0
27
+
28
+
29
+ _KEY_PREFIX = "lub."
30
+
31
+
32
+ def _full_key(field_name: str) -> str:
33
+ return f"{_KEY_PREFIX}{field_name}"
34
+
35
+
36
+ def load_config(db: Database) -> LubConfig:
37
+ """Materialize a :class:`LubConfig` from ``lub_config``; missing rows fall
38
+ through to the dataclass default."""
39
+ overrides: dict[str, Any] = {}
40
+ for f in fields(LubConfig):
41
+ row = db.fetchone("SELECT value_json FROM lub_config WHERE key = ?", (_full_key(f.name),))
42
+ if row is not None:
43
+ overrides[f.name] = json.loads(row[0])
44
+ return LubConfig(**overrides)
45
+
46
+
47
+ def save_config(db: Database, cfg: LubConfig) -> None:
48
+ """Upsert every field of *cfg* into ``lub_config``."""
49
+ with db.transaction():
50
+ for f in fields(LubConfig):
51
+ key = _full_key(f.name)
52
+ value = getattr(cfg, f.name)
53
+ payload = json.dumps(value)
54
+ db.execute("DELETE FROM lub_config WHERE key = ?", (key,))
55
+ db.execute(
56
+ "INSERT INTO lub_config(key, value_json) VALUES (?, ?)",
57
+ (key, payload),
58
+ )
59
+
60
+
61
+ def list_for_show(db: Database) -> list[tuple[str, Any, str]]:
62
+ """``[(key, value, source)]`` for the ``settings show`` table.
63
+
64
+ ``source`` is ``"persisted"`` if the field has a row in ``lub_config``,
65
+ otherwise ``"default"``.
66
+ """
67
+ out: list[tuple[str, Any, str]] = []
68
+ defaults = LubConfig()
69
+ for f in fields(LubConfig):
70
+ key = _full_key(f.name)
71
+ row = db.fetchone("SELECT value_json FROM lub_config WHERE key = ?", (key,))
72
+ if row is not None:
73
+ out.append((key, json.loads(row[0]), "persisted"))
74
+ else:
75
+ out.append((key, getattr(defaults, f.name), "default"))
76
+ return out