180 lines
6.5 KiB
Python
180 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
交易信号数据模型
|
|
纯分析结果,不含仓位资金
|
|
"""
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime, timedelta
|
|
from typing import Dict, Optional
|
|
import uuid
|
|
|
|
|
|
class SignalSource:
|
|
"""信号来源"""
|
|
PIVOT = "pivot" # 转折点信号
|
|
KEY_LEVEL = "key_level" # 关键点位信号
|
|
AI_ENTRY = "ai_entry" # AI入场信号
|
|
|
|
|
|
class SignalStatus:
|
|
"""信号状态"""
|
|
ACTIVE = "active" # 活跃
|
|
EXPIRED = "expired" # 已过期
|
|
USED = "used" # 已被使用(生成决策)
|
|
|
|
|
|
@dataclass
|
|
class TradingSignal:
|
|
"""交易信号 - 纯分析结果,不含仓位资金"""
|
|
|
|
# ==================== 基本信息 ====================
|
|
symbol: str # 品种
|
|
action: str # 方向: buy/sell
|
|
confidence: int = 50 # 置信度 0-100
|
|
|
|
# ==================== 来源 ====================
|
|
source: str = "" # pivot/key_level/ai_entry
|
|
source_period: str = "" # 来源周期 (H4/H1/M15/M5/M1)
|
|
|
|
# ==================== 触发信息 ====================
|
|
trigger_price: float = 0.0 # 触发价格
|
|
trigger_time: datetime = None # 触发时间
|
|
trigger_reason: str = "" # 触发原因
|
|
|
|
# ==================== 建议参数 ====================
|
|
suggested_entry: float = 0.0 # 建议入场价
|
|
suggested_sl: float = 0.0 # 建议止损
|
|
suggested_tp: float = 0.0 # 建议止盈
|
|
risk_reward_ratio: float = 0.0 # 风险回报比
|
|
|
|
# ==================== 来源特有参数 ====================
|
|
# Pivot信号
|
|
pivot_price: Optional[float] = None
|
|
pivot_type: Optional[str] = None # high/low
|
|
|
|
# KeyLevel信号
|
|
key_level: Optional[float] = None
|
|
distance_pct: Optional[float] = None
|
|
|
|
# AI Entry信号
|
|
ai_analysis_period: Optional[str] = None
|
|
|
|
# ==================== 自动生成字段 ====================
|
|
signal_id: str = ""
|
|
status: str = SignalStatus.ACTIVE
|
|
created_at: datetime = None
|
|
expires_at: datetime = None
|
|
|
|
# 默认信号有效期(秒)
|
|
DEFAULT_TTL: int = field(default=300, repr=False) # 5分钟
|
|
|
|
def __post_init__(self):
|
|
if not self.signal_id:
|
|
self.signal_id = str(uuid.uuid4())[:8]
|
|
if not self.created_at:
|
|
self.created_at = datetime.now()
|
|
if not self.trigger_time:
|
|
self.trigger_time = self.created_at
|
|
if not self.expires_at:
|
|
self.expires_at = self.created_at + timedelta(seconds=self.DEFAULT_TTL)
|
|
|
|
def is_expired(self) -> bool:
|
|
"""检查是否已过期"""
|
|
return datetime.now() > self.expires_at
|
|
|
|
def is_active(self) -> bool:
|
|
"""检查是否活跃"""
|
|
return self.status == SignalStatus.ACTIVE and not self.is_expired()
|
|
|
|
def mark_used(self) -> None:
|
|
"""标记为已使用"""
|
|
self.status = SignalStatus.USED
|
|
|
|
def mark_expired(self) -> None:
|
|
"""标记为已过期"""
|
|
self.status = SignalStatus.EXPIRED
|
|
|
|
def get_risk_points(self) -> float:
|
|
"""获取风险点数"""
|
|
if self.action == "buy":
|
|
return abs(self.suggested_entry - self.suggested_sl)
|
|
else:
|
|
return abs(self.suggested_sl - self.suggested_entry)
|
|
|
|
def get_reward_points(self) -> float:
|
|
"""获取回报点数"""
|
|
if self.action == "buy":
|
|
return abs(self.suggested_tp - self.suggested_entry)
|
|
else:
|
|
return abs(self.suggested_entry - self.suggested_tp)
|
|
|
|
def to_dict(self) -> Dict:
|
|
"""转换为字典"""
|
|
return {
|
|
"signal_id": self.signal_id,
|
|
"symbol": self.symbol,
|
|
"action": self.action,
|
|
"confidence": self.confidence,
|
|
"source": self.source,
|
|
"source_period": self.source_period,
|
|
"trigger_price": self.trigger_price,
|
|
"trigger_time": self.trigger_time.isoformat() if self.trigger_time else None,
|
|
"trigger_reason": self.trigger_reason,
|
|
"suggested_entry": self.suggested_entry,
|
|
"suggested_sl": self.suggested_sl,
|
|
"suggested_tp": self.suggested_tp,
|
|
"risk_reward_ratio": self.risk_reward_ratio,
|
|
"pivot_price": self.pivot_price,
|
|
"pivot_type": self.pivot_type,
|
|
"key_level": self.key_level,
|
|
"distance_pct": self.distance_pct,
|
|
"ai_analysis_period": self.ai_analysis_period,
|
|
"status": self.status,
|
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
"expires_at": self.expires_at.isoformat() if self.expires_at else None,
|
|
"risk_points": self.get_risk_points(),
|
|
"reward_points": self.get_reward_points(),
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict) -> 'TradingSignal':
|
|
"""从字典创建"""
|
|
trigger_time = data.get('trigger_time')
|
|
if isinstance(trigger_time, str):
|
|
trigger_time = datetime.fromisoformat(trigger_time)
|
|
|
|
created_at = data.get('created_at')
|
|
if isinstance(created_at, str):
|
|
created_at = datetime.fromisoformat(created_at)
|
|
elif created_at is None:
|
|
created_at = datetime.now()
|
|
|
|
expires_at = data.get('expires_at')
|
|
if isinstance(expires_at, str):
|
|
expires_at = datetime.fromisoformat(expires_at)
|
|
|
|
return cls(
|
|
symbol=data.get('symbol', ''),
|
|
action=data.get('action', ''),
|
|
confidence=data.get('confidence', 50),
|
|
source=data.get('source', ''),
|
|
source_period=data.get('source_period', ''),
|
|
trigger_price=data.get('trigger_price', 0.0),
|
|
trigger_time=trigger_time,
|
|
trigger_reason=data.get('trigger_reason', ''),
|
|
suggested_entry=data.get('suggested_entry', 0.0),
|
|
suggested_sl=data.get('suggested_sl', 0.0),
|
|
suggested_tp=data.get('suggested_tp', 0.0),
|
|
risk_reward_ratio=data.get('risk_reward_ratio', 0.0),
|
|
pivot_price=data.get('pivot_price'),
|
|
pivot_type=data.get('pivot_type'),
|
|
key_level=data.get('key_level'),
|
|
distance_pct=data.get('distance_pct'),
|
|
ai_analysis_period=data.get('ai_analysis_period'),
|
|
signal_id=data.get('signal_id', ''),
|
|
status=data.get('status', SignalStatus.ACTIVE),
|
|
created_at=created_at,
|
|
expires_at=expires_at,
|
|
) |