Update project
This commit is contained in:
265
backend/app/api/analysis_schemas.py
Normal file
265
backend/app/api/analysis_schemas.py
Normal 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]
|
||||
33
backend/app/api/capital_image_schemas.py
Normal file
33
backend/app/api/capital_image_schemas.py
Normal 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
|
||||
67
backend/app/api/main_capital_flow_schemas.py
Normal file
67
backend/app/api/main_capital_flow_schemas.py
Normal 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
|
||||
@ -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))
|
||||
|
||||
@ -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]
|
||||
|
||||
Reference in New Issue
Block a user