Files
A_Share_DP/src/ashare_dp/features/registry.py
T
jackyu66gitandClaude cc95bbb638 v10: Trading OS — 5-subsystem architecture + Signal Intelligence + Dashboard Command Center
Architecture:
- Restructure into 5 subsystems: data/, features/, market/, signals/, execution/, apps/
- Unified ts_code conversion in core/codes.py (idempotent, kills 4 duplicate copies)
- analytics_conn() + kline_glob() — zero hardcoded DB/Parquet paths
- Fixed double-suffix bug (.SZ.SZ) in backfill pipeline root cause

Signal Intelligence (the moat):
- 14 signal types: EMA52, Vegas, Chan, ORB, Gap, NR7, Inside Bar
- 640K+ historical signal instances across 8 backfilled types
- Multi-signal Expectancy Engine with breadth-similarity matching
- Signal backfill CLI: ashare-dp backfill signals

Market Intelligence:
- 8 engines: State, Leadership, Opportunity, Flow, Sentiment, Memory, Knowledge Graph, Recommendations
- Real limit-up/down sentiment via akshare (108 ZT, 19 DT, 52 broken board)
- Knowledge Graph: 8 themes × 30+ concepts with keyword matching
- Money-flow stock recommendations with entry/stop/target trade plans

Dashboard Command Center:
- Decision-first layout: COMMAND → WHERE → WHY → RISK → EXPECTANCY
- Multi-signal Expectancy comparison table (8 types ranked by WR)
- Theme Map visualization with rotation detection
- Intraday Replay infrastructure (30min state snapshots)
- RECOMMENDATIONS card with actionable trade plans

Trading Memory:
- trade_log table + POST/GET/PUT API for trade recording
- Performance stats aggregation

Code Quality:
- 0 hardcoded DB paths, 0 REPLACE hacks, 0 dead ts_code copies
- EMA52 screening deduplicated (CLI + scheduler share one function)
- read_parquet_sql() helper for 28 duplicate patterns
- 6 bugs fixed from code review (NR7 window, theme matching, column indices, etc.)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:11:42 +08:00

129 lines
4.3 KiB
Python

"""Feature Registry — centralized management of all market features.
Engine code only knows about the registry; individual feature functions
are looked up by name. Adding a new feature = register it, no engine changes.
"""
from __future__ import annotations
from datetime import date
from typing import Any, Callable
from ashare_dp.domain.features import FeatureDefinition
class FeatureRegistry:
"""Central registry for all market features.
Usage:
from ashare_dp.features.registry import registry
# Register a feature
registry.register(FeatureDefinition(
name="breadth_vector",
category="breadth",
description="Advance/decline ratio and related breadth metrics",
dependencies=[],
compute=_compute_breadth_vector,
))
# Compute all features for a date
features = registry.compute_all(db, trade_date)
# Get features by category
breadth_features = registry.get_by_category("breadth")
"""
def __init__(self):
self._features: dict[str, FeatureDefinition] = {}
def register(self, feature: FeatureDefinition) -> None:
"""Register a feature definition. Overwrites if name exists."""
self._features[feature.name] = feature
def get(self, name: str) -> FeatureDefinition | None:
"""Get a feature definition by name."""
return self._features.get(name)
def get_by_category(self, category: str) -> list[FeatureDefinition]:
"""Get all features in a category."""
return [f for f in self._features.values() if f.category == category]
def compute_all(self, db: Any, trade_date: date) -> dict[str, dict[str, Any]]:
"""Compute all registered features for a given trading date.
Args:
db: Database instance (read_only DuckDB connection).
trade_date: The trading date to compute features for.
Returns:
Dict mapping feature name → feature value dict.
Features are computed in dependency order (simple topological sort).
"""
results: dict[str, dict[str, Any]] = {}
computed: set[str] = set()
pending: set[str] = set(self._features.keys())
while pending:
ready = [
name for name in pending
if all(dep in computed for dep in self._features[name].dependencies)
]
if not ready:
# Circular dependency or missing dependency
remaining = ", ".join(sorted(pending))
raise RuntimeError(
f"Cannot resolve feature dependencies. "
f"Remaining: {remaining}. Computed: {computed}"
)
for name in ready:
feature = self._features[name]
if feature.compute is not None:
results[name] = feature.compute(db, trade_date, results)
computed.add(name)
pending.discard(name)
return results
def compute_category(
self, db: Any, trade_date: date, category: str
) -> dict[str, dict[str, Any]]:
"""Compute all features in a specific category."""
features = self.get_by_category(category)
# Compute dependencies first
all_needed: set[str] = set()
for f in features:
all_needed.add(f.name)
all_needed.update(f.dependencies)
full_results = self.compute_all(db, trade_date)
return {k: v for k, v in full_results.items() if k in all_needed}
def list_categories(self) -> list[str]:
"""List all unique feature categories."""
return sorted({f.category for f in self._features.values()})
def list_features(self) -> list[dict]:
"""List all registered features with metadata."""
return [
{
"name": f.name,
"category": f.category,
"description": f.description,
"dependencies": f.dependencies,
"version": f.version,
}
for f in sorted(self._features.values(), key=lambda x: x.name)
]
def __len__(self) -> int:
return len(self._features)
def __contains__(self, name: str) -> bool:
return name in self._features
# Module-level singleton
registry = FeatureRegistry()