This commit is contained in:
aaron 2026-03-13 21:50:16 +08:00
parent 9166e4a987
commit e3bfa597c9
4 changed files with 54 additions and 34 deletions

View File

@ -5,6 +5,7 @@ from enum import Enum
from datetime import datetime from datetime import datetime
from sqlalchemy import Column, Integer, String, Float, DateTime, JSON, Text, Enum as SQLEnum from sqlalchemy import Column, Integer, String, Float, DateTime, JSON, Text, Enum as SQLEnum
from app.models.database import Base from app.models.database import Base
from app.utils.datetime_utils import get_beijing_time
class OrderStatus(str, Enum): class OrderStatus(str, Enum):
@ -88,7 +89,7 @@ class PaperOrder(Base):
trailing_stop_base_profit = Column(Float, default=0) # 移动止损触发时的盈利百分比 trailing_stop_base_profit = Column(Float, default=0) # 移动止损触发时的盈利百分比
# 时间戳 # 时间戳
created_at = Column(DateTime, default=datetime.utcnow) created_at = Column(DateTime, default=lambda: get_beijing_time())
opened_at = Column(DateTime, nullable=True) # 开仓时间 opened_at = Column(DateTime, nullable=True) # 开仓时间
closed_at = Column(DateTime, nullable=True) # 平仓时间 closed_at = Column(DateTime, nullable=True) # 平仓时间
@ -99,6 +100,12 @@ class PaperOrder(Base):
def to_dict(self): def to_dict(self):
"""转换为字典""" """转换为字典"""
# 格式化时间字段(北京时间字符串)
def format_time(dt):
if dt is None:
return None
return dt.strftime('%Y-%m-%d %H:%M:%S')
return { return {
'id': self.id, 'id': self.id,
'order_id': self.order_id, 'order_id': self.order_id,
@ -125,8 +132,8 @@ class PaperOrder(Base):
'breakeven_triggered': self.breakeven_triggered, 'breakeven_triggered': self.breakeven_triggered,
'trailing_stop_triggered': getattr(self, 'trailing_stop_triggered', 0), 'trailing_stop_triggered': getattr(self, 'trailing_stop_triggered', 0),
'trailing_stop_base_profit': getattr(self, 'trailing_stop_base_profit', 0), 'trailing_stop_base_profit': getattr(self, 'trailing_stop_base_profit', 0),
'created_at': self.created_at.isoformat() if self.created_at else None, 'created_at': format_time(self.created_at),
'opened_at': self.opened_at.isoformat() if self.opened_at else None, 'opened_at': format_time(self.opened_at),
'closed_at': self.closed_at.isoformat() if self.closed_at else None, 'closed_at': format_time(self.closed_at),
'entry_reasons': self.entry_reasons, 'entry_reasons': self.entry_reasons,
} }

View File

@ -2,25 +2,14 @@
模拟交易服务 - 订单管理和盈亏统计 模拟交易服务 - 订单管理和盈亏统计
""" """
import uuid import uuid
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta
from typing import Dict, Any, List, Optional from typing import Dict, Any, List, Optional
from app.models.paper_trading import PaperOrder, OrderStatus, OrderSide, SignalGrade, EntryType from app.models.paper_trading import PaperOrder, OrderStatus, OrderSide, SignalGrade, EntryType
from app.services.db_service import db_service from app.services.db_service import db_service
from app.config import get_settings from app.config import get_settings
from app.utils.logger import logger from app.utils.logger import logger
from app.utils.datetime_utils import get_beijing_time
def get_beijing_time() -> datetime:
"""
获取东八区北京时间当前时间
Returns:
东八区的当前时间
"""
utc_time = datetime.utcnow().replace(tzinfo=timezone.utc)
beijing_tz = timezone(timedelta(hours=8))
return utc_time.astimezone(beijing_tz).replace(tzinfo=None)
class PaperTradingService: class PaperTradingService:

View File

@ -0,0 +1,27 @@
"""
时间工具模块
提供统一的时间处理函数
"""
from datetime import datetime, timezone, timedelta
def get_beijing_time() -> datetime:
"""
获取东八区北京时间当前时间
Returns:
东八区的当前时间无时区信息的naive datetime
"""
utc_time = datetime.utcnow().replace(tzinfo=timezone.utc)
beijing_tz = timezone(timedelta(hours=8))
return utc_time.astimezone(beijing_tz).replace(tzinfo=None)
def get_utc_time() -> datetime:
"""
获取UTC当前时间
Returns:
UTC当前时间无时区信息的naive datetime
"""
return datetime.utcnow()

View File

@ -2149,13 +2149,15 @@
formatTime(timeStr) { formatTime(timeStr) {
if (!timeStr) return '-'; if (!timeStr) return '-';
const date = new Date(timeStr); // 后端已返回格式化的北京时间字符串,直接显示
return date.toLocaleString('zh-CN', { // 格式: "2026-03-13 18:30:00" -> 显示为 "03-13 18:30"
month: '2-digit', const parts = timeStr.split(' ');
day: '2-digit', if (parts.length >= 2) {
hour: '2-digit', const datePart = parts[0].substring(5); // "03-13"
minute: '2-digit' const timePart = parts[1].substring(0, 5); // "18:30"
}); return `${datePart} ${timePart}`;
}
return timeStr;
}, },
formatStatus(status) { formatStatus(status) {
@ -2507,21 +2509,16 @@
return (this.totalUnrealizedPnl / this.totalPosition) * 100; return (this.totalUnrealizedPnl / this.totalPosition) * 100;
}, },
// 累计收益率 // 累计收益率(使用 account.initial_balance 作为基准,与顶部收益率一致)
totalReturn() { totalReturn() {
if (this.dailyReturns.length === 0) return 0; if (!this.account.initial_balance || this.account.initial_balance === 0) return 0;
const firstDay = this.dailyReturns[0]; return ((this.account.current_balance - this.account.initial_balance) / this.account.initial_balance) * 100;
const lastDay = this.dailyReturns[this.dailyReturns.length - 1];
if (firstDay.balance === 0) return 0;
return ((lastDay.balance - firstDay.balance) / firstDay.balance) * 100;
}, },
// 累计收益额 // 累计收益额
totalReturnAmount() { totalReturnAmount() {
if (this.dailyReturns.length === 0) return 0; if (!this.account.initial_balance) return 0;
const lastDay = this.dailyReturns[this.dailyReturns.length - 1]; return this.account.current_balance - this.account.initial_balance;
const firstDay = this.dailyReturns[0];
return lastDay.balance - firstDay.balance;
}, },
// 盈利天数 // 盈利天数