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.
- deeptrade/__init__.py +8 -0
- deeptrade/channels_builtin/__init__.py +0 -0
- deeptrade/channels_builtin/stdout/__init__.py +0 -0
- deeptrade/channels_builtin/stdout/deeptrade_plugin.yaml +25 -0
- deeptrade/channels_builtin/stdout/migrations/20260429_001_init.sql +13 -0
- deeptrade/channels_builtin/stdout/stdout_channel/__init__.py +0 -0
- deeptrade/channels_builtin/stdout/stdout_channel/channel.py +180 -0
- deeptrade/cli.py +214 -0
- deeptrade/cli_config.py +396 -0
- deeptrade/cli_data.py +33 -0
- deeptrade/cli_plugin.py +176 -0
- deeptrade/core/__init__.py +8 -0
- deeptrade/core/config.py +344 -0
- deeptrade/core/config_migrations.py +138 -0
- deeptrade/core/db.py +176 -0
- deeptrade/core/llm_client.py +591 -0
- deeptrade/core/llm_manager.py +174 -0
- deeptrade/core/logging_config.py +61 -0
- deeptrade/core/migrations/__init__.py +0 -0
- deeptrade/core/migrations/core/20260427_001_init.sql +121 -0
- deeptrade/core/migrations/core/20260501_002_drop_llm_calls_stage.sql +10 -0
- deeptrade/core/migrations/core/__init__.py +0 -0
- deeptrade/core/notifier.py +302 -0
- deeptrade/core/paths.py +49 -0
- deeptrade/core/plugin_manager.py +616 -0
- deeptrade/core/run_status.py +29 -0
- deeptrade/core/secrets.py +152 -0
- deeptrade/core/tushare_client.py +824 -0
- deeptrade/plugins_api/__init__.py +44 -0
- deeptrade/plugins_api/base.py +66 -0
- deeptrade/plugins_api/channel.py +42 -0
- deeptrade/plugins_api/events.py +61 -0
- deeptrade/plugins_api/llm.py +46 -0
- deeptrade/plugins_api/metadata.py +84 -0
- deeptrade/plugins_api/notify.py +67 -0
- deeptrade/strategies_builtin/__init__.py +0 -0
- deeptrade/strategies_builtin/limit_up_board/__init__.py +0 -0
- deeptrade/strategies_builtin/limit_up_board/deeptrade_plugin.yaml +101 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/__init__.py +0 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/calendar.py +65 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/cli.py +269 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/config.py +76 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/data.py +1191 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/pipeline.py +869 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/plugin.py +30 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/profiles.py +85 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/prompts.py +485 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/render.py +890 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/runner.py +1087 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/runtime.py +172 -0
- deeptrade/strategies_builtin/limit_up_board/limit_up_board/schemas.py +178 -0
- deeptrade/strategies_builtin/limit_up_board/migrations/20260430_001_init.sql +150 -0
- deeptrade/strategies_builtin/limit_up_board/migrations/20260501_002_lub_stage_results_llm_provider.sql +8 -0
- deeptrade/strategies_builtin/limit_up_board/migrations/20260508_001_lub_lhb_tables.sql +36 -0
- deeptrade/strategies_builtin/limit_up_board/migrations/20260508_002_lub_cyq_perf.sql +18 -0
- deeptrade/strategies_builtin/limit_up_board/migrations/20260508_003_lub_lhb_pk_fix.sql +46 -0
- deeptrade/strategies_builtin/limit_up_board/migrations/20260508_004_lub_lhb_drop_pk.sql +53 -0
- deeptrade/strategies_builtin/limit_up_board/migrations/20260508_005_lub_config.sql +17 -0
- deeptrade/strategies_builtin/volume_anomaly/__init__.py +0 -0
- deeptrade/strategies_builtin/volume_anomaly/deeptrade_plugin.yaml +59 -0
- deeptrade/strategies_builtin/volume_anomaly/migrations/20260430_001_init.sql +94 -0
- deeptrade/strategies_builtin/volume_anomaly/migrations/20260601_001_realized_returns.sql +44 -0
- deeptrade/strategies_builtin/volume_anomaly/migrations/20260601_002_dimension_scores.sql +13 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/__init__.py +0 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/calendar.py +52 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/cli.py +247 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/data.py +2154 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/pipeline.py +327 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/plugin.py +22 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/profiles.py +49 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/prompts.py +187 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/prompts_examples.py +84 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/render.py +906 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/runner.py +772 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/runtime.py +90 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/schemas.py +97 -0
- deeptrade/strategies_builtin/volume_anomaly/volume_anomaly/stats.py +174 -0
- deeptrade/theme.py +48 -0
- deeptrade_quant-0.0.2.dist-info/METADATA +166 -0
- deeptrade_quant-0.0.2.dist-info/RECORD +83 -0
- deeptrade_quant-0.0.2.dist-info/WHEEL +4 -0
- deeptrade_quant-0.0.2.dist-info/entry_points.txt +2 -0
- 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
|
+
)
|