1
This commit is contained in:
parent
9166e4a987
commit
e3bfa597c9
@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
27
backend/app/utils/datetime_utils.py
Normal file
27
backend/app/utils/datetime_utils.py
Normal 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()
|
||||||
@ -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;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 盈利天数
|
// 盈利天数
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user