Update project

This commit is contained in:
wanghep
2026-04-08 20:04:40 +08:00
parent 2eab960303
commit 862235ea89
130 changed files with 60206 additions and 231 deletions

View File

@ -0,0 +1,265 @@
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel
SecurityType = Literal["stock", "index", "etf", "unknown"]
CycleName = Literal["week", "day", "15m", "30m", "60m", "90m", "120m"]
ResonanceLevel = Literal["normal", "medium", "strong", "very_strong"]
ResonanceType = Literal["time", "space", "spacetime", "trend", "abc"]
SignalBias = Literal["bullish", "bearish", "neutral"]
ToolMode = Literal["auto", "manual_ready"]
ToolType = Literal["fibonacci_ruler", "extension_ruler", "wave_ruler", "trend_line", "horizontal_line", "range_measure"]
MarkerKind = Literal["support", "resistance", "current", "trigger", "target"]
class SymbolInfo(BaseModel):
query: str
code: str
name: str
market: str
security_type: SecurityType
secid: str
class SnapshotInfo(BaseModel):
latest_price: float | None = None
change_percent: float | None = None
updated_at: str | None = None
source_name: str
class MaValues(BaseModel):
ma5: float | None = None
ma13: float | None = None
ma21: float | None = None
ma34: float | None = None
ma55: float | None = None
ma89: float | None = None
class MacdValues(BaseModel):
dif: float | None = None
dea: float | None = None
histogram: float | None = None
class RsiValues(BaseModel):
rsi5: float | None = None
rsi13: float | None = None
rsi21: float | None = None
class IndicatorSnapshot(BaseModel):
macd: MacdValues
rsi: RsiValues
signal_summary: str
class CycleSummary(BaseModel):
cycle: CycleName
close: float | None = None
trend_label: str
ma_status: str
volume_status: str
ma_values: MaValues
indicator_snapshot: IndicatorSnapshot
class AbcPoint(BaseModel):
label: Literal["A", "B", "C"]
timestamp: str
price: float
k_index: int
class AbcStructure(BaseModel):
cycle: CycleName
direction: Literal["bullish", "bearish", "neutral"]
status: str
a_point: AbcPoint | None = None
b_point: AbcPoint | None = None
c_point: AbcPoint | None = None
reasoning: list[str]
class FibonacciLevel(BaseModel):
ratio: float
label: str
value: float
distance_to_price: float | None = None
class FibonacciSpace(BaseModel):
cycle: CycleName
anchor_start_label: str
anchor_start_time: str
anchor_start_price: float
anchor_end_label: str
anchor_end_time: str
anchor_end_price: float
levels: list[FibonacciLevel]
current_position_summary: str
class FibonacciTime(BaseModel):
cycle: Literal["15m", "30m", "60m", "90m", "120m"]
start_point_time: str
start_point_label: str
current_count: int
current_hit: list[int]
next_key_counts: list[int]
next_window_summary: str
class TimeSequenceTrack(BaseModel):
track_type: Literal["major", "minor"]
cycle: Literal["15m", "30m", "60m", "90m", "120m"]
start_point_time: str
start_point_label: str
current_count: int
current_hit: list[int]
next_key_counts: list[int]
next_window_summary: str
class TimeSequenceBundle(BaseModel):
major: TimeSequenceTrack | None = None
minor: TimeSequenceTrack | None = None
class ResonanceItem(BaseModel):
level: ResonanceLevel
type: ResonanceType
cycles: list[str]
summary: str
bias: SignalBias
class ConclusionSummary(BaseModel):
stage: str
bias: SignalBias
confidence: int
headline: str
summary: str
tags: list[str]
class AlertItem(BaseModel):
level: ResonanceLevel
trigger_type: str
title: str
summary: str
action: str
class EvidenceItem(BaseModel):
title: str
detail: str
cycles: list[str]
score: int
class StrategyScenario(BaseModel):
key: Literal["A", "B", "C"]
title: str
trigger_condition: str
system_view: str
user_action: str
next_watch: str
class MonitoringTask(BaseModel):
title: str
cadence: str
focus: str
trigger_condition: str
class ToolPreset(BaseModel):
name: str
cycle: CycleName
tool_type: ToolType
mode: ToolMode
anchors: list[str]
summary: str
class AnalysisReportResponse(BaseModel):
symbol: SymbolInfo
snapshot: SnapshotInfo
cycles: list[CycleSummary]
abc_structures: list[AbcStructure]
fibonacci_space: list[FibonacciSpace]
fibonacci_time: list[FibonacciTime]
resonance: list[ResonanceItem]
conclusion_summary: ConclusionSummary
alerts: list[AlertItem]
evidence_chain: list[EvidenceItem]
strategy_scenarios: list[StrategyScenario]
monitoring_tasks: list[MonitoringTask]
tool_presets: list[ToolPreset]
tomorrow_strategy: list[str]
follow_strategy: list[str]
signal_conclusion: list[str]
calculation_steps: list[str]
class ChartCandle(BaseModel):
timestamp: str
open: float
close: float
high: float
low: float
volume: float
ma5: float | None = None
ma13: float | None = None
ma21: float | None = None
ma34: float | None = None
ma55: float | None = None
ma89: float | None = None
dif: float | None = None
dea: float | None = None
macd_histogram: float | None = None
rsi5: float | None = None
rsi13: float | None = None
rsi21: float | None = None
class ChartTimeMarker(BaseModel):
track_type: Literal["major", "minor"] = "major"
target_count: int
current_count: int
candle_index: int | None = None
reached: bool
label: str
class ChartPriceMarker(BaseModel):
label: str
value: float
kind: MarkerKind
emphasis: ResonanceLevel
class ChartToolLayer(BaseModel):
name: str
tool_type: ToolType
mode: ToolMode
summary: str
class AnalysisChartResponse(BaseModel):
symbol: SymbolInfo
cycle: CycleName
candles: list[ChartCandle]
abc_structure: AbcStructure
fibonacci_space: FibonacciSpace
fibonacci_time: FibonacciTime | None = None
time_sequences: TimeSequenceBundle | None = None
time_markers: list[ChartTimeMarker]
price_markers: list[ChartPriceMarker]
tool_layers: list[ChartToolLayer]
signal_tags: list[str]

View File

@ -0,0 +1,33 @@
from pydantic import BaseModel, Field
class CapitalImageRecordBase(BaseModel):
id: str
trade_date: str | None
subject: str | None
snapshot_time: str | None
main_force_amount_yi: float | None
institution_amount_yi: float | None
large_household_amount_yi: float | None
retail_amount_yi: float | None
overall_trend: str | None
intraday_summary: str | None
review_status: str
extraction_method: str
image_name: str
image_url: str
created_at: str
updated_at: str
class CapitalImageRecordDetail(CapitalImageRecordBase):
raw_extraction: dict = Field(default_factory=dict)
class CapitalImageListResponse(BaseModel):
items: list[CapitalImageRecordBase]
total: int
class CapitalImageUploadResponse(BaseModel):
item: CapitalImageRecordDetail

View File

@ -0,0 +1,67 @@
from pydantic import BaseModel, Field
class MainCapitalFlowRecordBase(BaseModel):
id: str
trade_date: str
subject: str | None
snapshot_time: str | None
institution_amount_yi: float | None
main_force_amount_yi: float | None
large_household_amount_yi: float | None
retail_amount_yi: float | None
trend: str | None
summary: str
image_name: str
image_url: str
created_at: str
updated_at: str
class MainCapitalFlowRecordDetail(MainCapitalFlowRecordBase):
raw_extraction: dict = Field(default_factory=dict)
class MainCapitalFlowListResponse(BaseModel):
items: list[MainCapitalFlowRecordBase]
total: int
class MainCapitalFlowRecognizeResponse(BaseModel):
temp_image_name: str
image_name: str
image_url: str
trade_date: str | None
subject: str | None
snapshot_time: str | None
institution_amount_yi: float | None
main_force_amount_yi: float | None
large_household_amount_yi: float | None
retail_amount_yi: float | None
trend: str | None
summary: str | None
raw_extraction: dict = Field(default_factory=dict)
class MainCapitalFlowCreateRequest(BaseModel):
temp_image_name: str
image_name: str
trade_date: str
subject: str | None = None
snapshot_time: str | None = None
institution_amount_yi: float | None = None
main_force_amount_yi: float | None = None
large_household_amount_yi: float | None = None
retail_amount_yi: float | None = None
trend: str | None = None
summary: str
raw_extraction: dict = Field(default_factory=dict)
class MainCapitalFlowCreateResponse(BaseModel):
item: MainCapitalFlowRecordDetail
class MainCapitalFlowDeleteResponse(BaseModel):
deleted: bool
id: str

View File

@ -1,8 +1,23 @@
from fastapi import APIRouter, Query
from fastapi import APIRouter, File, Form, Query, UploadFile
from app.api.analysis_schemas import AnalysisChartResponse, AnalysisReportResponse
from app.api.capital_image_schemas import (
CapitalImageListResponse,
CapitalImageRecordDetail,
CapitalImageUploadResponse,
)
from app.api.main_capital_flow_schemas import (
MainCapitalFlowCreateRequest,
MainCapitalFlowCreateResponse,
MainCapitalFlowDeleteResponse,
MainCapitalFlowListResponse,
MainCapitalFlowRecognizeResponse,
MainCapitalFlowRecordDetail,
)
from app.api.schemas import (
AShareIndexFlowResponse,
AShareSectorFlowResponse,
EtfRealtimeResponse,
HealthResponse,
HistoryResponse,
MetaResponse,
@ -12,8 +27,12 @@ from app.api.schemas import (
RulesResponse,
SourceDiagnosticsResponse,
)
from app.services.analysis_service import analysis_service
from app.services.ashare_flow_service import ashare_flow_service
from app.services.alert_service import alert_service
from app.services.capital_image_service import capital_image_service
from app.services.etf_monitor_service import etf_monitor_service
from app.services.main_capital_flow_service import main_capital_flow_service
from app.services.monitoring_service import monitoring_service
router = APIRouter()
@ -24,6 +43,74 @@ def health() -> HealthResponse:
return HealthResponse(status="ok")
@router.get("/capital-images", response_model=CapitalImageListResponse)
def capital_image_list(
trade_date: str | None = Query(default=None),
subject: str | None = Query(default=None),
) -> CapitalImageListResponse:
return CapitalImageListResponse(**capital_image_service.list_records(trade_date, subject))
@router.get("/capital-images/{record_id}", response_model=CapitalImageRecordDetail)
def capital_image_detail(record_id: str) -> CapitalImageRecordDetail:
return CapitalImageRecordDetail(**capital_image_service.get_record(record_id))
@router.post("/capital-images/upload", response_model=CapitalImageUploadResponse)
async def capital_image_upload(
image: UploadFile = File(...),
trade_date: str | None = Form(default=None),
subject: str | None = Form(default=None),
) -> CapitalImageUploadResponse:
return CapitalImageUploadResponse(
**await capital_image_service.create_record(image, trade_date=trade_date, subject=subject)
)
@router.get("/main-capital-flows", response_model=MainCapitalFlowListResponse)
def main_capital_flow_list() -> MainCapitalFlowListResponse:
return MainCapitalFlowListResponse(**main_capital_flow_service.list_records())
@router.get("/main-capital-flows/{record_id}", response_model=MainCapitalFlowRecordDetail)
def main_capital_flow_detail(record_id: str) -> MainCapitalFlowRecordDetail:
return MainCapitalFlowRecordDetail(**main_capital_flow_service.get_record(record_id))
@router.post("/main-capital-flows/recognize", response_model=MainCapitalFlowRecognizeResponse)
async def main_capital_flow_recognize(
image: UploadFile = File(...),
trade_date: str | None = Form(default=None),
subject: str | None = Form(default=None),
) -> MainCapitalFlowRecognizeResponse:
return MainCapitalFlowRecognizeResponse(
**await main_capital_flow_service.recognize_image(image, trade_date=trade_date, subject=subject)
)
@router.post("/main-capital-flows", response_model=MainCapitalFlowCreateResponse)
def main_capital_flow_create(payload: MainCapitalFlowCreateRequest) -> MainCapitalFlowCreateResponse:
return MainCapitalFlowCreateResponse(**main_capital_flow_service.create_record(payload.model_dump()))
@router.delete("/main-capital-flows/{record_id}", response_model=MainCapitalFlowDeleteResponse)
def main_capital_flow_delete(record_id: str) -> MainCapitalFlowDeleteResponse:
return MainCapitalFlowDeleteResponse(**main_capital_flow_service.delete_record(record_id))
@router.get("/analysis/report", response_model=AnalysisReportResponse)
def analysis_report(q: str = Query(..., min_length=2)) -> AnalysisReportResponse:
return analysis_service.build_report(q)
@router.get("/analysis/chart", response_model=AnalysisChartResponse)
def analysis_chart(
q: str = Query(..., min_length=2),
cycle: str = Query(default="day"),
) -> AnalysisChartResponse:
return analysis_service.build_chart(q, cycle)
@router.get("/meta", response_model=MetaResponse)
def meta() -> MetaResponse:
return monitoring_service.get_meta()
@ -77,3 +164,23 @@ def ashare_sector_realtime() -> AShareSectorFlowResponse:
@router.get("/ashare/sector-flows/daily", response_model=AShareSectorFlowResponse)
def ashare_sector_daily(trade_date: str | None = Query(default=None)) -> AShareSectorFlowResponse:
return AShareSectorFlowResponse(**ashare_flow_service.get_sector_daily(trade_date))
@router.get("/etf/broad/realtime", response_model=EtfRealtimeResponse)
def etf_broad_realtime() -> EtfRealtimeResponse:
return EtfRealtimeResponse(**etf_monitor_service.get_group_realtime("broad"))
@router.get("/etf/sector/realtime", response_model=EtfRealtimeResponse)
def etf_sector_realtime() -> EtfRealtimeResponse:
return EtfRealtimeResponse(**etf_monitor_service.get_group_realtime("sector"))
@router.get("/etf/broad/daily", response_model=EtfRealtimeResponse)
def etf_broad_daily(trade_date: str | None = Query(default=None)) -> EtfRealtimeResponse:
return EtfRealtimeResponse(**etf_monitor_service.get_group_daily("broad", trade_date))
@router.get("/etf/sector/daily", response_model=EtfRealtimeResponse)
def etf_sector_daily(trade_date: str | None = Query(default=None)) -> EtfRealtimeResponse:
return EtfRealtimeResponse(**etf_monitor_service.get_group_daily("sector", trade_date))

View File

@ -199,3 +199,40 @@ class AShareSectorFlowResponse(BaseModel):
source_url: str | None = None
precision: Precision
sector_types: dict[str, AShareSectorGroup]
class EtfRealtimeRecord(BaseModel):
trade_date: str
code: str
name: str
fund_name: str | None = None
detail_url: str | None = None
source_url: str | None = None
latest_price: float | None = None
change_amount: float | None = None
change_percent: float | None = None
previous_close: float | None = None
open_price: float | None = None
high_price: float | None = None
low_price: float | None = None
volume: int | None = None
turnover_amount: float | None = None
turnover_rate: float | None = None
change_percent_1m: float | None = None
change_percent_3m: float | None = None
change_percent_4m: float | None = None
updated_at: str | None = None
snapshot_time: str | None = None
source_name: str
precision: Precision
is_trading: bool = False
class EtfRealtimeResponse(BaseModel):
trade_date: str
updated_at: str | None = None
source_name: str
source_url: str | None = None
precision: Precision
group: str
records: list[EtfRealtimeRecord]