128 lines
4.9 KiB
Python
128 lines
4.9 KiB
Python
"""
|
||
模拟交易数据模型
|
||
"""
|
||
from enum import Enum
|
||
from datetime import datetime
|
||
from sqlalchemy import Column, Integer, String, Float, DateTime, JSON, Text, Enum as SQLEnum
|
||
from app.models.database import Base
|
||
|
||
|
||
class OrderStatus(str, Enum):
|
||
"""订单状态"""
|
||
PENDING = "pending" # 等待入场
|
||
OPEN = "open" # 持仓中
|
||
CLOSED_TP = "closed_tp" # 止盈平仓
|
||
CLOSED_SL = "closed_sl" # 止损平仓
|
||
CLOSED_BE = "closed_be" # 保本止损平仓
|
||
CLOSED_MANUAL = "closed_manual" # 手动平仓
|
||
CANCELLED = "cancelled" # 已取消
|
||
|
||
|
||
class OrderSide(str, Enum):
|
||
"""订单方向"""
|
||
LONG = "long" # 做多
|
||
SHORT = "short" # 做空
|
||
|
||
|
||
class SignalGrade(str, Enum):
|
||
"""信号等级"""
|
||
A = "A"
|
||
B = "B"
|
||
C = "C"
|
||
D = "D"
|
||
|
||
|
||
class EntryType(str, Enum):
|
||
"""入场类型"""
|
||
MARKET = "market" # 现价入场
|
||
LIMIT = "limit" # 挂单入场
|
||
|
||
|
||
class PaperOrder(Base):
|
||
"""模拟交易订单表"""
|
||
__tablename__ = "paper_orders"
|
||
|
||
id = Column(Integer, primary_key=True, index=True)
|
||
|
||
# 订单标识
|
||
order_id = Column(String(64), unique=True, nullable=False, index=True)
|
||
|
||
# 交易对信息
|
||
symbol = Column(String(20), nullable=False, index=True)
|
||
side = Column(SQLEnum(OrderSide), nullable=False)
|
||
|
||
# 价格信息
|
||
entry_price = Column(Float, nullable=False) # 目标入场价
|
||
stop_loss = Column(Float, nullable=False) # 止损价
|
||
take_profit = Column(Float, nullable=False) # 止盈价
|
||
filled_price = Column(Float, nullable=True) # 实际成交价
|
||
exit_price = Column(Float, nullable=True) # 出场价
|
||
|
||
# 仓位信息
|
||
quantity = Column(Float, default=1000) # 仓位大小 (USDT)
|
||
|
||
# 信号信息
|
||
signal_grade = Column(SQLEnum(SignalGrade), default=SignalGrade.D)
|
||
signal_type = Column(String(20), default="swing") # swing / short_term
|
||
confidence = Column(Float, default=0) # 置信度 (0-100)
|
||
trend = Column(String(20), nullable=True) # 趋势方向
|
||
entry_type = Column(SQLEnum(EntryType, values_callable=lambda x: [e.value for e in x]), default=EntryType.MARKET) # 入场类型
|
||
|
||
# 订单状态
|
||
status = Column(SQLEnum(OrderStatus), default=OrderStatus.PENDING, index=True)
|
||
|
||
# 盈亏信息
|
||
pnl_amount = Column(Float, default=0) # 盈亏金额 (USDT)
|
||
pnl_percent = Column(Float, default=0) # 盈亏百分比
|
||
|
||
# 风险指标
|
||
max_drawdown = Column(Float, default=0) # 持仓期间最大回撤
|
||
max_profit = Column(Float, default=0) # 持仓期间最大盈利
|
||
breakeven_triggered = Column(Integer, default=0) # 是否触发过保本止损(0=否,1=是)
|
||
|
||
# 移动止损相关
|
||
trailing_stop_triggered = Column(Integer, default=0) # 是否触发过移动止损(0=否,1=是)
|
||
trailing_stop_base_profit = Column(Float, default=0) # 移动止损触发时的盈利百分比
|
||
|
||
# 时间戳
|
||
created_at = Column(DateTime, default=datetime.utcnow)
|
||
opened_at = Column(DateTime, nullable=True) # 开仓时间
|
||
closed_at = Column(DateTime, nullable=True) # 平仓时间
|
||
|
||
# 附加数据
|
||
entry_reasons = Column(JSON, nullable=True) # 入场原因
|
||
indicators = Column(JSON, nullable=True) # 技术指标快照
|
||
notes = Column(Text, nullable=True) # 备注
|
||
|
||
def to_dict(self):
|
||
"""转换为字典"""
|
||
return {
|
||
'id': self.id,
|
||
'order_id': self.order_id,
|
||
'symbol': self.symbol,
|
||
'side': self.side.value if self.side else None,
|
||
'entry_price': self.entry_price,
|
||
'stop_loss': self.stop_loss,
|
||
'take_profit': self.take_profit,
|
||
'filled_price': self.filled_price,
|
||
'exit_price': self.exit_price,
|
||
'quantity': self.quantity,
|
||
'signal_grade': self.signal_grade.value if self.signal_grade else None,
|
||
'signal_type': self.signal_type,
|
||
'confidence': self.confidence,
|
||
'trend': self.trend,
|
||
'entry_type': self.entry_type.value if self.entry_type else 'market',
|
||
'status': self.status.value if self.status else None,
|
||
'pnl_amount': self.pnl_amount,
|
||
'pnl_percent': self.pnl_percent,
|
||
'max_drawdown': self.max_drawdown,
|
||
'max_profit': self.max_profit,
|
||
'breakeven_triggered': self.breakeven_triggered,
|
||
'trailing_stop_triggered': getattr(self, 'trailing_stop_triggered', 0),
|
||
'trailing_stop_base_profit': getattr(self, 'trailing_stop_base_profit', 0),
|
||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||
'opened_at': self.opened_at.isoformat() if self.opened_at else None,
|
||
'closed_at': self.closed_at.isoformat() if self.closed_at else None,
|
||
'entry_reasons': self.entry_reasons,
|
||
}
|