deeptrade-quant 0.4.0__tar.gz → 0.4.1__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 (62) hide show
  1. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/PKG-INFO +1 -1
  2. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/__init__.py +1 -1
  3. deeptrade_quant-0.4.1/deeptrade/core/migrations/core/20260512_001_drop_legacy_tushare_cache.sql +10 -0
  4. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/tushare_client.py +40 -3
  5. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/pyproject.toml +1 -1
  6. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_db.py +2 -2
  7. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_tushare_client.py +34 -0
  8. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/.gitignore +0 -0
  9. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/LICENSE +0 -0
  10. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/README.md +0 -0
  11. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/cli.py +0 -0
  12. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/cli_config.py +0 -0
  13. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/cli_data.py +0 -0
  14. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/cli_plugin.py +0 -0
  15. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/__init__.py +0 -0
  16. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/config.py +0 -0
  17. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/config_migrations.py +0 -0
  18. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/db.py +0 -0
  19. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/dep_installer.py +0 -0
  20. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/github_fetch.py +0 -0
  21. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/llm_client.py +0 -0
  22. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/llm_manager.py +0 -0
  23. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/logging_config.py +0 -0
  24. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/migrations/__init__.py +0 -0
  25. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/migrations/core/20260509_001_init.sql +0 -0
  26. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/migrations/core/__init__.py +0 -0
  27. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/paths.py +0 -0
  28. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/plugin_manager.py +0 -0
  29. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/plugin_source.py +0 -0
  30. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/registry.py +0 -0
  31. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/run_status.py +0 -0
  32. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/core/secrets.py +0 -0
  33. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/plugins_api/__init__.py +0 -0
  34. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/plugins_api/base.py +0 -0
  35. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/plugins_api/events.py +0 -0
  36. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/plugins_api/llm.py +0 -0
  37. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/plugins_api/metadata.py +0 -0
  38. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/deeptrade/theme.py +0 -0
  39. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/__init__.py +0 -0
  40. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/cli/__init__.py +0 -0
  41. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/cli/test_config_cmd.py +0 -0
  42. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/cli/test_plugin_cmd.py +0 -0
  43. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/cli/test_routing.py +0 -0
  44. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/conftest.py +0 -0
  45. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/__init__.py +0 -0
  46. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_config.py +0 -0
  47. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_config_migrations.py +0 -0
  48. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_github_fetch.py +0 -0
  49. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_llm_client.py +0 -0
  50. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_llm_manager.py +0 -0
  51. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_paths.py +0 -0
  52. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_plugin_dependencies.py +0 -0
  53. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_plugin_install.py +0 -0
  54. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_plugin_source.py +0 -0
  55. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_plugin_upgrade.py +0 -0
  56. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_registry.py +0 -0
  57. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_secrets.py +0 -0
  58. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_tushare_classifier.py +0 -0
  59. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/core/test_tushare_retry_r1.py +0 -0
  60. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/plugins_api/__init__.py +0 -0
  61. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/plugins_api/test_protocol.py +0 -0
  62. {deeptrade_quant-0.4.0 → deeptrade_quant-0.4.1}/tests/test_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deeptrade-quant
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: LLM-driven A-share (Shanghai/Shenzhen main board) stock screening CLI
5
5
  Project-URL: Homepage, https://github.com/ty19880929/deeptrade
6
6
  Project-URL: Repository, https://github.com/ty19880929/deeptrade
@@ -2,5 +2,5 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.4.0"
5
+ __version__ = "0.4.1"
6
6
  __all__ = ["__version__"]
@@ -0,0 +1,10 @@
1
+ -- v0.4.1 — drop legacy tushare_cache_blob entries.
2
+ --
3
+ -- Pre-v0.4.1 payloads were stored as a bare JSON array, then read back via
4
+ -- pd.read_json which triggered a pandas FutureWarning on string columns whose
5
+ -- names matched its date heuristic (trade_date, cal_date, ...). The cache
6
+ -- read/write path now wraps payloads as {version:1, schema:{...}, data:[...]}
7
+ -- and restores dtypes explicitly. Old rows are incompatible with that reader,
8
+ -- so wipe the table; TushareClient._ensure_cache_table re-creates it lazily on
9
+ -- the next write.
10
+ DROP TABLE IF EXISTS tushare_cache_blob;
@@ -19,7 +19,6 @@ Cache class buckets:
19
19
  from __future__ import annotations
20
20
 
21
21
  import hashlib
22
- import io
23
22
  import json
24
23
  import logging
25
24
  import threading
@@ -896,6 +895,15 @@ class TushareClient:
896
895
  ")"
897
896
  )
898
897
 
898
+ # Payload wrapper format (v0.4.1+):
899
+ # {"version": 1, "schema": {col: dtype_str}, "data": [...records...]}
900
+ # Bypasses pd.read_json's date-column heuristic (which warns on string
901
+ # columns named like dates — trade_date / cal_date / ann_date) and
902
+ # restores dtypes explicitly from the recorded schema. Pre-v0.4.1 rows
903
+ # were a bare records array; those are wiped by core migration
904
+ # 20260512_001_drop_legacy_tushare_cache.sql, so no legacy branch here.
905
+ _CACHE_PAYLOAD_VERSION = 1
906
+
899
907
  def _write_cached(
900
908
  self,
901
909
  api_name: str,
@@ -906,7 +914,14 @@ class TushareClient:
906
914
  self._ensure_cache_table()
907
915
  body = json.dumps(params, sort_keys=True, default=str)
908
916
  h = hashlib.sha256(body.encode("utf-8")).hexdigest()
909
- payload = df.to_json(orient="records", date_format="iso")
917
+ records_json = df.to_json(orient="records", date_format="iso")
918
+ payload = json.dumps(
919
+ {
920
+ "version": self._CACHE_PAYLOAD_VERSION,
921
+ "schema": {col: str(dt) for col, dt in df.dtypes.items()},
922
+ "data": json.loads(records_json) if records_json else [],
923
+ }
924
+ )
910
925
  with self._db.transaction():
911
926
  self._db.execute(
912
927
  "DELETE FROM tushare_cache_blob "
@@ -937,12 +952,34 @@ class TushareClient:
937
952
  )
938
953
  if row is None:
939
954
  return pd.DataFrame()
940
- df = pd.read_json(io.StringIO(row[0]), orient="records")
955
+ wrapper = json.loads(row[0])
956
+ df = self._restore_cached_frame(wrapper)
941
957
  if fields:
942
958
  cols = [c.strip() for c in fields.split(",") if c.strip() in df.columns]
943
959
  df = df[cols]
944
960
  return df
945
961
 
962
+ @classmethod
963
+ def _restore_cached_frame(cls, wrapper: dict[str, Any]) -> pd.DataFrame:
964
+ schema: dict[str, str] = wrapper["schema"]
965
+ data: list[dict[str, Any]] = wrapper["data"]
966
+ df = pd.DataFrame.from_records(data, columns=list(schema.keys()))
967
+ for col, dtype_str in schema.items():
968
+ if col not in df.columns:
969
+ continue
970
+ if dtype_str.startswith("datetime"):
971
+ df[col] = pd.to_datetime(df[col], errors="coerce")
972
+ elif dtype_str == "object":
973
+ continue
974
+ else:
975
+ try:
976
+ df[col] = df[col].astype(dtype_str)
977
+ except (TypeError, ValueError):
978
+ # Best-effort: if a numeric/bool column can't be coerced
979
+ # back (e.g. all-null), leave the inferred dtype.
980
+ pass
981
+ return df
982
+
946
983
 
947
984
  # ---------------------------------------------------------------------------
948
985
  # Fallback predicate (DESIGN §13.2 + S4)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "deeptrade-quant"
7
- version = "0.4.0"
7
+ version = "0.4.1"
8
8
  description = "LLM-driven A-share (Shanghai/Shenzhen main board) stock screening CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -50,9 +50,9 @@ def test_init_is_idempotent(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) ->
50
50
  def test_apply_core_migrations_records_version(fresh_db: Database) -> None:
51
51
  """Framework owns no business tables."""
52
52
  applied = apply_core_migrations(fresh_db)
53
- assert applied == ["20260509_001"]
53
+ assert applied == ["20260509_001", "20260512_001"]
54
54
  rows = fresh_db.fetchall("SELECT version FROM schema_migrations ORDER BY version")
55
- assert tuple(rows) == (("20260509_001",),)
55
+ assert tuple(rows) == (("20260509_001",), ("20260512_001",))
56
56
 
57
57
 
58
58
  def test_apply_core_migrations_skips_applied_versions(fresh_db: Database) -> None:
@@ -5,6 +5,7 @@
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ import warnings
8
9
  from datetime import datetime, timedelta
9
10
  from pathlib import Path
10
11
 
@@ -452,3 +453,36 @@ def test_cache_hit_requires_payload_present(
452
453
  client.call("limit_list_d", trade_date="20260427")
453
454
  # Should hit transport (because no payload despite state=ok)
454
455
  assert len(transport.calls) == 1
456
+
457
+
458
+ # ---------------------------------------------------------------------------
459
+ # Cache payload round-trip — dtypes preserved, no pandas FutureWarning
460
+ # ---------------------------------------------------------------------------
461
+
462
+
463
+ def test_cache_payload_round_trip_preserves_dtypes(client: TushareClient) -> None:
464
+ """v0.4.1 wrapper format restores dtypes explicitly so plugins don't see
465
+ pandas' read_json date-heuristic warnings on tushare-style date columns."""
466
+ df = pd.DataFrame(
467
+ {
468
+ "trade_date": ["20260427", "20260428"], # YYYYMMDD strings — triggers heuristic
469
+ "ts_code": ["000001.SZ", "000002.SZ"],
470
+ "close": [12.5, 14.7],
471
+ "vol": [1000, 2000],
472
+ "is_st": [True, False],
473
+ "ann_dt": pd.to_datetime(["2026-04-27", "2026-04-28"]), # real datetime64[ns]
474
+ }
475
+ )
476
+ api, td, params = "limit_list_d", "20260427", {"trade_date": "20260427"}
477
+
478
+ client._write_cached(api, td, params, df) # noqa: SLF001 — exercising internal contract
479
+
480
+ with warnings.catch_warnings():
481
+ warnings.simplefilter("error", FutureWarning)
482
+ out = client._read_cached(api, td, params, fields=None) # noqa: SLF001
483
+
484
+ assert list(out.columns) == list(df.columns)
485
+ for col in df.columns:
486
+ assert out[col].dtype == df[col].dtype, f"dtype drift on {col}: {out[col].dtype}"
487
+ assert out["trade_date"].tolist() == ["20260427", "20260428"]
488
+ assert (out["ann_dt"] == df["ann_dt"]).all()
File without changes