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 sqlalchemy import Column, Integer, String, Float, DateTime, JSON, Text, Enum as SQLEnum
from app.models.database import Base
from app.utils.datetime_utils import get_beijing_time
class OrderStatus(str, Enum):
@ -88,7 +89,7 @@ class PaperOrder(Base):
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) # 开仓时间
closed_at = Column(DateTime, nullable=True) # 平仓时间
@ -99,6 +100,12 @@ class PaperOrder(Base):
def to_dict(self):
"""转换为字典"""
# 格式化时间字段(北京时间字符串)
def format_time(dt):
if dt is None:
return None
return dt.strftime('%Y-%m-%d %H:%M:%S')
return {
'id': self.id,
'order_id': self.order_id,
@ -125,8 +132,8 @@ class PaperOrder(Base):
'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,
'created_at': format_time(self.created_at),
'opened_at': format_time(self.opened_at),
'closed_at': format_time(self.closed_at),
'entry_reasons': self.entry_reasons,
}

View File

@ -2,25 +2,14 @@
模拟交易服务 - 订单管理和盈亏统计
"""
import uuid
from datetime import datetime, timedelta, timezone
from datetime import datetime, timedelta
from typing import Dict, Any, List, Optional
from app.models.paper_trading import PaperOrder, OrderStatus, OrderSide, SignalGrade, EntryType
from app.services.db_service import db_service
from app.config import get_settings
from app.utils.logger import logger
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)
from app.utils.datetime_utils import get_beijing_time
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) {
if (!timeStr) return '-';
const date = new Date(timeStr);
return date.toLocaleString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
// 后端已返回格式化的北京时间字符串,直接显示
// 格式: "2026-03-13 18:30:00" -> 显示为 "03-13 18:30"
const parts = timeStr.split(' ');
if (parts.length >= 2) {
const datePart = parts[0].substring(5); // "03-13"
const timePart = parts[1].substring(0, 5); // "18:30"
return `${datePart} ${timePart}`;
}
return timeStr;
},
formatStatus(status) {
@ -2507,21 +2509,16 @@
return (this.totalUnrealizedPnl / this.totalPosition) * 100;
},
// 累计收益率
// 累计收益率(使用 account.initial_balance 作为基准,与顶部收益率一致)
totalReturn() {
if (this.dailyReturns.length === 0) return 0;
const firstDay = this.dailyReturns[0];
const lastDay = this.dailyReturns[this.dailyReturns.length - 1];
if (firstDay.balance === 0) return 0;
return ((lastDay.balance - firstDay.balance) / firstDay.balance) * 100;
if (!this.account.initial_balance || this.account.initial_balance === 0) return 0;
return ((this.account.current_balance - this.account.initial_balance) / this.account.initial_balance) * 100;
},
// 累计收益额
totalReturnAmount() {
if (this.dailyReturns.length === 0) return 0;
const lastDay = this.dailyReturns[this.dailyReturns.length - 1];
const firstDay = this.dailyReturns[0];
return lastDay.balance - firstDay.balance;
if (!this.account.initial_balance) return 0;
return this.account.current_balance - this.account.initial_balance;
},
// 盈利天数