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,152 @@
1
+ """Secret storage with keyring preference + plaintext fallback.
2
+
3
+ DESIGN §7.1: keyring is the default; if unavailable (headless Linux, CI, some
4
+ Docker images), fall back to plaintext-in-DuckDB with an EXPLICIT warning so
5
+ the user knows the risk.
6
+
7
+ The encryption_method column on secret_store distinguishes the two paths so
8
+ ``deeptrade config show`` can warn when any secret is plaintext-stored.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ from dataclasses import dataclass
15
+ from typing import Protocol
16
+
17
+ from deeptrade.core.db import Database
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # keyring service name namespace
22
+ _KR_SERVICE = "deeptrade"
23
+
24
+
25
+ class _KeyringBackend(Protocol):
26
+ def get_password(self, service_name: str, username: str) -> str | None: ...
27
+ def set_password(self, service_name: str, username: str, password: str) -> None: ...
28
+ def delete_password(self, service_name: str, username: str) -> None: ...
29
+
30
+
31
+ def _try_load_keyring() -> _KeyringBackend | None:
32
+ """Attempt to import & probe keyring. Return backend or None on failure."""
33
+ try:
34
+ import keyring # noqa: PLC0415 — deferred import on purpose
35
+ from keyring.errors import KeyringError # noqa: PLC0415
36
+ except ImportError:
37
+ return None
38
+ try:
39
+ # Probe: round-trip a sentinel
40
+ keyring.set_password(_KR_SERVICE, "__probe__", "ok")
41
+ if keyring.get_password(_KR_SERVICE, "__probe__") != "ok":
42
+ return None
43
+ keyring.delete_password(_KR_SERVICE, "__probe__")
44
+ return keyring # type: ignore[return-value]
45
+ except (KeyringError, Exception) as e: # noqa: BLE001 — capture all backend errors
46
+ logger.warning("keyring unavailable: %s; falling back to plaintext", e)
47
+ return None
48
+
49
+
50
+ @dataclass
51
+ class SecretRecord:
52
+ key: str
53
+ value: str
54
+ method: str # 'keyring' | 'plaintext'
55
+
56
+
57
+ class SecretStore:
58
+ """Get/set secrets with automatic keyring/plaintext routing.
59
+
60
+ Notes:
61
+ * Plaintext fallback is intentional: users on headless Linux / CI
62
+ environments need a path that works without an interactive key store.
63
+ * `secret_store` table records which method was used so callers can
64
+ warn on plaintext storage.
65
+ """
66
+
67
+ def __init__(self, db: Database, *, force_plaintext: bool = False) -> None:
68
+ self._db = db
69
+ self._keyring = None if force_plaintext else _try_load_keyring()
70
+
71
+ @property
72
+ def using_keyring(self) -> bool:
73
+ return self._keyring is not None
74
+
75
+ def get(self, key: str) -> str | None:
76
+ method, value = self._read_record(key)
77
+ if method is None:
78
+ return None
79
+ if method == "keyring":
80
+ # B3.3 / M1 fix — gracefully degrade when the secret was originally
81
+ # stored via keyring but the current environment doesn't have one
82
+ # (e.g. user moved the DB across machines, or fell from a TTY env
83
+ # to headless CI). Return None + clear log so callers can prompt
84
+ # the user to re-run `deeptrade config set`.
85
+ if self._keyring is None:
86
+ logger.error(
87
+ "secret %r is stored in keyring but no keyring is available "
88
+ "in this environment; re-run `deeptrade config set` to "
89
+ "migrate to plaintext or fix keyring access.",
90
+ key,
91
+ )
92
+ return None
93
+ return self._keyring.get_password(_KR_SERVICE, key)
94
+ # plaintext: value column holds the raw bytes
95
+ return value
96
+
97
+ def set(self, key: str, value: str) -> None:
98
+ if self._keyring is not None:
99
+ self._keyring.set_password(_KR_SERVICE, key, value)
100
+ self._upsert_record(key, encrypted_value=b"", method="keyring")
101
+ else:
102
+ logger.warning(
103
+ "Storing %r as plaintext in secret_store (keyring unavailable). "
104
+ "Anyone with read access to the DuckDB file can read this secret.",
105
+ key,
106
+ )
107
+ self._upsert_record(
108
+ key,
109
+ encrypted_value=value.encode("utf-8"),
110
+ method="plaintext",
111
+ )
112
+
113
+ def delete(self, key: str) -> None:
114
+ method, _ = self._read_record(key)
115
+ if method == "keyring" and self._keyring is not None:
116
+ try:
117
+ self._keyring.delete_password(_KR_SERVICE, key)
118
+ except Exception: # noqa: BLE001 — keyring may not have it
119
+ pass
120
+ self._db.execute("DELETE FROM secret_store WHERE key = ?", (key,))
121
+
122
+ def list_records(self) -> list[SecretRecord]:
123
+ rows = self._db.fetchall(
124
+ "SELECT key, encryption_method, encrypted_value FROM secret_store ORDER BY key"
125
+ )
126
+ out: list[SecretRecord] = []
127
+ for key, method, blob in rows:
128
+ value = (blob.decode("utf-8") if blob else "") if method == "plaintext" else ""
129
+ out.append(SecretRecord(key=key, value=value, method=method))
130
+ return out
131
+
132
+ # --- internal -----------------------------------------------------
133
+
134
+ def _read_record(self, key: str) -> tuple[str | None, str | None]:
135
+ row = self._db.fetchone(
136
+ "SELECT encryption_method, encrypted_value FROM secret_store WHERE key = ?",
137
+ (key,),
138
+ )
139
+ if row is None:
140
+ return None, None
141
+ method, blob = row
142
+ return method, (blob.decode("utf-8") if blob else "")
143
+
144
+ def _upsert_record(self, key: str, *, encrypted_value: bytes, method: str) -> None:
145
+ # DuckDB lacks ON CONFLICT in older versions; do delete+insert in a tx
146
+ with self._db.transaction():
147
+ self._db.execute("DELETE FROM secret_store WHERE key = ?", (key,))
148
+ self._db.execute(
149
+ "INSERT INTO secret_store(key, encrypted_value, encryption_method) "
150
+ "VALUES (?, ?, ?)",
151
+ (key, encrypted_value, method),
152
+ )