a
This commit is contained in:
parent
d59fa97036
commit
e3ecaf1232
|
|
@ -275,10 +275,15 @@ async def get_realtime_positions():
|
|||
if margin > 0:
|
||||
pnl_percent = (unrealized_pnl / margin) * 100
|
||||
|
||||
# 尝试从数据库获取开仓时间、止损止盈价格
|
||||
# 尝试从数据库获取开仓时间、止损止盈价格(以及交易规模字段)
|
||||
entry_time = None
|
||||
stop_loss_price = None
|
||||
take_profit_price = None
|
||||
take_profit_1 = None
|
||||
take_profit_2 = None
|
||||
atr_value = None
|
||||
db_margin_usdt = None
|
||||
db_notional_usdt = None
|
||||
try:
|
||||
from database.models import Trade
|
||||
db_trades = Trade.get_by_symbol(pos.get('symbol'), status='open')
|
||||
|
|
@ -290,6 +295,11 @@ async def get_realtime_positions():
|
|||
# 尝试从数据库获取止损止盈价格(如果存储了)
|
||||
stop_loss_price = db_trade.get('stop_loss_price')
|
||||
take_profit_price = db_trade.get('take_profit_price')
|
||||
take_profit_1 = db_trade.get('take_profit_1')
|
||||
take_profit_2 = db_trade.get('take_profit_2')
|
||||
atr_value = db_trade.get('atr')
|
||||
db_margin_usdt = db_trade.get('margin_usdt')
|
||||
db_notional_usdt = db_trade.get('notional_usdt')
|
||||
break
|
||||
except Exception as e:
|
||||
logger.debug(f"获取数据库信息失败: {e}")
|
||||
|
|
@ -302,14 +312,21 @@ async def get_realtime_positions():
|
|||
"side": "BUY" if position_amt > 0 else "SELL",
|
||||
"quantity": abs(position_amt),
|
||||
"entry_price": entry_price,
|
||||
"entry_value_usdt": entry_value_usdt, # 开仓时的USDT数量
|
||||
# 兼容旧字段:entry_value_usdt 仍保留(前端已有使用)
|
||||
"entry_value_usdt": entry_value_usdt,
|
||||
# 新字段:名义/保证金(若DB有则优先使用DB;否则使用实时计算)
|
||||
"notional_usdt": db_notional_usdt if db_notional_usdt is not None else entry_value_usdt,
|
||||
"margin_usdt": db_margin_usdt if db_margin_usdt is not None else margin,
|
||||
"mark_price": mark_price,
|
||||
"pnl": unrealized_pnl,
|
||||
"pnl_percent": pnl_percent, # 基于保证金的盈亏百分比
|
||||
"leverage": int(pos.get('leverage', 1)),
|
||||
"entry_time": entry_time, # 开仓时间
|
||||
"stop_loss_price": stop_loss_price, # 止损价格(如果可用)
|
||||
"take_profit_price": take_profit_price # 止盈价格(如果可用)
|
||||
"take_profit_price": take_profit_price, # 止盈价格(如果可用)
|
||||
"take_profit_1": take_profit_1,
|
||||
"take_profit_2": take_profit_2,
|
||||
"atr": atr_value,
|
||||
})
|
||||
|
||||
logger.info(f"格式化后 {len(formatted_positions)} 个有效持仓")
|
||||
|
|
|
|||
|
|
@ -231,6 +231,11 @@ async def get_trade_stats(
|
|||
"win_rate": len(win_trades) / len(meaningful_trades) * 100 if meaningful_trades else 0, # 基于有意义的交易计算胜率
|
||||
"total_pnl": sum(float(t['pnl']) for t in closed_trades),
|
||||
"avg_pnl": sum(float(t['pnl']) for t in closed_trades) / len(closed_trades) if closed_trades else 0,
|
||||
# 总交易量(名义下单量口径):优先使用 notional_usdt(新字段),否则回退 entry_price * quantity
|
||||
"total_notional_usdt": sum(
|
||||
float(t.get('notional_usdt') or (float(t.get('entry_price', 0)) * float(t.get('quantity', 0))))
|
||||
for t in trades
|
||||
),
|
||||
"filters": {
|
||||
"start_timestamp": start_timestamp,
|
||||
"end_timestamp": end_timestamp,
|
||||
|
|
|
|||
105
backend/database/add_trade_size_and_risk_fields.sql
Normal file
105
backend/database/add_trade_size_and_risk_fields.sql
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
-- 为 trades 表添加“交易规模/风险”字段:
|
||||
-- 1) 保证金(margin_usdt)
|
||||
-- 2) 名义下单量(notional_usdt)
|
||||
-- 3) 实际止损/止盈/分步止盈与ATR(用于仪表板展示真实值)
|
||||
--
|
||||
-- 说明:
|
||||
-- - 可重复执行:列已存在会自动跳过
|
||||
-- - 默认统计口径使用“入场时”的名义/保证金
|
||||
--
|
||||
|
||||
SET @dbname = DATABASE();
|
||||
SET @tablename = 'trades';
|
||||
|
||||
-- 1) notional_usdt(名义下单量)
|
||||
SET @columnname = 'notional_usdt';
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE (TABLE_SCHEMA = @dbname) AND (TABLE_NAME = @tablename) AND (COLUMN_NAME = @columnname)
|
||||
) > 0,
|
||||
"SELECT 'Column notional_usdt already exists.' AS result;",
|
||||
CONCAT(
|
||||
"ALTER TABLE `", @tablename, "` ",
|
||||
"ADD COLUMN `", @columnname, "` DECIMAL(20, 8) NULL ",
|
||||
"COMMENT '名义下单量(USDT):入场价×数量(用于统计总交易量)';"
|
||||
)
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
-- 2) margin_usdt(保证金)
|
||||
SET @columnname2 = 'margin_usdt';
|
||||
SET @preparedStatement2 = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE (TABLE_SCHEMA = @dbname) AND (TABLE_NAME = @tablename) AND (COLUMN_NAME = @columnname2)
|
||||
) > 0,
|
||||
"SELECT 'Column margin_usdt already exists.' AS result;",
|
||||
CONCAT(
|
||||
"ALTER TABLE `", @tablename, "` ",
|
||||
"ADD COLUMN `", @columnname2, "` DECIMAL(20, 8) NULL ",
|
||||
"COMMENT '保证金(USDT):名义下单量/杠杆(用于统计与盈亏口径统一)';"
|
||||
)
|
||||
));
|
||||
PREPARE alterIfNotExists2 FROM @preparedStatement2;
|
||||
EXECUTE alterIfNotExists2;
|
||||
DEALLOCATE PREPARE alterIfNotExists2;
|
||||
|
||||
-- 3) take_profit_1(分步止盈1)
|
||||
SET @columnname3 = 'take_profit_1';
|
||||
SET @preparedStatement3 = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE (TABLE_SCHEMA = @dbname) AND (TABLE_NAME = @tablename) AND (COLUMN_NAME = @columnname3)
|
||||
) > 0,
|
||||
"SELECT 'Column take_profit_1 already exists.' AS result;",
|
||||
CONCAT(
|
||||
"ALTER TABLE `", @tablename, "` ",
|
||||
"ADD COLUMN `", @columnname3, "` DECIMAL(20, 8) NULL ",
|
||||
"COMMENT '第一目标止盈价(用于展示与分步止盈)';"
|
||||
)
|
||||
));
|
||||
PREPARE alterIfNotExists3 FROM @preparedStatement3;
|
||||
EXECUTE alterIfNotExists3;
|
||||
DEALLOCATE PREPARE alterIfNotExists3;
|
||||
|
||||
-- 4) take_profit_2(分步止盈2)
|
||||
SET @columnname4 = 'take_profit_2';
|
||||
SET @preparedStatement4 = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE (TABLE_SCHEMA = @dbname) AND (TABLE_NAME = @tablename) AND (COLUMN_NAME = @columnname4)
|
||||
) > 0,
|
||||
"SELECT 'Column take_profit_2 already exists.' AS result;",
|
||||
CONCAT(
|
||||
"ALTER TABLE `", @tablename, "` ",
|
||||
"ADD COLUMN `", @columnname4, "` DECIMAL(20, 8) NULL ",
|
||||
"COMMENT '第二目标止盈价(用于展示与分步止盈)';"
|
||||
)
|
||||
));
|
||||
PREPARE alterIfNotExists4 FROM @preparedStatement4;
|
||||
EXECUTE alterIfNotExists4;
|
||||
DEALLOCATE PREPARE alterIfNotExists4;
|
||||
|
||||
-- 5) atr(开仓时使用的ATR值)
|
||||
SET @columnname5 = 'atr';
|
||||
SET @preparedStatement5 = (SELECT IF(
|
||||
(
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE (TABLE_SCHEMA = @dbname) AND (TABLE_NAME = @tablename) AND (COLUMN_NAME = @columnname5)
|
||||
) > 0,
|
||||
"SELECT 'Column atr already exists.' AS result;",
|
||||
CONCAT(
|
||||
"ALTER TABLE `", @tablename, "` ",
|
||||
"ADD COLUMN `", @columnname5, "` DECIMAL(20, 8) NULL ",
|
||||
"COMMENT '开仓时使用的ATR(用于展示动态止损止盈依据)';"
|
||||
)
|
||||
));
|
||||
PREPARE alterIfNotExists5 FROM @preparedStatement5;
|
||||
EXECUTE alterIfNotExists5;
|
||||
DEALLOCATE PREPARE alterIfNotExists5;
|
||||
|
||||
SELECT 'Migration completed: Added notional_usdt, margin_usdt, take_profit_1, take_profit_2, atr to trades table.' AS result;
|
||||
|
||||
|
|
@ -24,20 +24,34 @@ CREATE TABLE IF NOT EXISTS `trades` (
|
|||
`side` VARCHAR(10) NOT NULL COMMENT 'BUY, SELL',
|
||||
`quantity` DECIMAL(20, 8) NOT NULL,
|
||||
`entry_price` DECIMAL(20, 8) NOT NULL,
|
||||
`notional_usdt` DECIMAL(20, 8) NULL COMMENT '名义下单量(USDT):入场价×数量(用于统计总交易量)',
|
||||
`margin_usdt` DECIMAL(20, 8) NULL COMMENT '保证金(USDT):名义下单量/杠杆',
|
||||
`exit_price` DECIMAL(20, 8),
|
||||
`entry_time` INT UNSIGNED NOT NULL COMMENT '入场时间(Unix时间戳秒数)',
|
||||
`exit_time` INT UNSIGNED NULL COMMENT '平仓时间(Unix时间戳秒数)',
|
||||
`pnl` DECIMAL(20, 8) DEFAULT 0,
|
||||
`pnl_percent` DECIMAL(10, 4) DEFAULT 0,
|
||||
`leverage` INT DEFAULT 10,
|
||||
`entry_order_id` BIGINT NULL COMMENT '币安开仓订单号(用于对账)',
|
||||
`exit_order_id` BIGINT NULL COMMENT '币安平仓订单号(用于对账)',
|
||||
`entry_reason` TEXT,
|
||||
`exit_reason` VARCHAR(50) COMMENT 'stop_loss, take_profit, trailing_stop, manual',
|
||||
`strategy_type` VARCHAR(50) COMMENT '策略类型: trend_following, mean_reversion',
|
||||
`duration_minutes` INT COMMENT '持仓持续时间(分钟)',
|
||||
`atr` DECIMAL(20, 8) NULL COMMENT '开仓时使用的ATR(用于展示动态止损止盈依据)',
|
||||
`stop_loss_price` DECIMAL(20, 8) NULL COMMENT '实际使用的止损价格(考虑了ATR等动态计算)',
|
||||
`take_profit_price` DECIMAL(20, 8) NULL COMMENT '实际使用的止盈价格(考虑了ATR等动态计算)',
|
||||
`take_profit_1` DECIMAL(20, 8) NULL COMMENT '第一目标止盈价(用于展示与分步止盈)',
|
||||
`take_profit_2` DECIMAL(20, 8) NULL COMMENT '第二目标止盈价(用于展示与分步止盈)',
|
||||
`status` VARCHAR(20) DEFAULT 'open' COMMENT 'open, closed, cancelled',
|
||||
`created_at` INT UNSIGNED NOT NULL DEFAULT (UNIX_TIMESTAMP()) COMMENT '创建时间(Unix时间戳秒数)',
|
||||
INDEX `idx_symbol` (`symbol`),
|
||||
INDEX `idx_entry_time` (`entry_time`),
|
||||
INDEX `idx_status` (`status`),
|
||||
INDEX `idx_symbol_status` (`symbol`, `status`)
|
||||
INDEX `idx_symbol_status` (`symbol`, `status`),
|
||||
INDEX `idx_entry_order_id` (`entry_order_id`),
|
||||
INDEX `idx_exit_order_id` (`exit_order_id`),
|
||||
INDEX `idx_strategy_type` (`strategy_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交易记录表';
|
||||
|
||||
-- 账户快照表
|
||||
|
|
|
|||
|
|
@ -88,7 +88,22 @@ class Trade:
|
|||
"""交易记录模型"""
|
||||
|
||||
@staticmethod
|
||||
def create(symbol, side, quantity, entry_price, leverage=10, entry_reason=None, entry_order_id=None, stop_loss_price=None, take_profit_price=None):
|
||||
def create(
|
||||
symbol,
|
||||
side,
|
||||
quantity,
|
||||
entry_price,
|
||||
leverage=10,
|
||||
entry_reason=None,
|
||||
entry_order_id=None,
|
||||
stop_loss_price=None,
|
||||
take_profit_price=None,
|
||||
take_profit_1=None,
|
||||
take_profit_2=None,
|
||||
atr=None,
|
||||
notional_usdt=None,
|
||||
margin_usdt=None,
|
||||
):
|
||||
"""创建交易记录(使用北京时间)
|
||||
|
||||
Args:
|
||||
|
|
@ -101,31 +116,73 @@ class Trade:
|
|||
entry_order_id: 币安开仓订单号(可选,用于对账)
|
||||
stop_loss_price: 实际使用的止损价格(考虑了ATR等动态计算)
|
||||
take_profit_price: 实际使用的止盈价格(考虑了ATR等动态计算)
|
||||
take_profit_1: 第一目标止盈价(可选)
|
||||
take_profit_2: 第二目标止盈价(可选)
|
||||
atr: 开仓时使用的ATR值(可选)
|
||||
notional_usdt: 名义下单量(USDT,可选)
|
||||
margin_usdt: 保证金(USDT,可选)
|
||||
"""
|
||||
entry_time = get_beijing_time()
|
||||
|
||||
# 检查字段是否存在(兼容旧数据库schema)
|
||||
# 自动计算 notional/margin(若调用方没传)
|
||||
try:
|
||||
db.execute_one("SELECT stop_loss_price FROM trades LIMIT 1")
|
||||
has_stop_take_fields = True
|
||||
except:
|
||||
has_stop_take_fields = False
|
||||
|
||||
if has_stop_take_fields:
|
||||
db.execute_update(
|
||||
"""INSERT INTO trades
|
||||
(symbol, side, quantity, entry_price, leverage, entry_reason, status, entry_time, entry_order_id, stop_loss_price, take_profit_price)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, 'open', %s, %s, %s, %s)""",
|
||||
(symbol, side, quantity, entry_price, leverage, entry_reason, entry_time, entry_order_id, stop_loss_price, take_profit_price)
|
||||
)
|
||||
else:
|
||||
# 兼容旧schema
|
||||
db.execute_update(
|
||||
"""INSERT INTO trades
|
||||
(symbol, side, quantity, entry_price, leverage, entry_reason, status, entry_time, entry_order_id)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, 'open', %s, %s)""",
|
||||
(symbol, side, quantity, entry_price, leverage, entry_reason, entry_time, entry_order_id)
|
||||
)
|
||||
if notional_usdt is None and quantity is not None and entry_price is not None:
|
||||
notional_usdt = float(quantity) * float(entry_price)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if margin_usdt is None and notional_usdt is not None:
|
||||
lv = float(leverage) if leverage else 0
|
||||
margin_usdt = (float(notional_usdt) / lv) if lv and lv > 0 else float(notional_usdt)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _has_column(col: str) -> bool:
|
||||
try:
|
||||
db.execute_one(f"SELECT {col} FROM trades LIMIT 1")
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# 动态构建 INSERT(兼容不同schema)
|
||||
columns = ["symbol", "side", "quantity", "entry_price", "leverage", "entry_reason", "status", "entry_time"]
|
||||
values = [symbol, side, quantity, entry_price, leverage, entry_reason, "open", entry_time]
|
||||
|
||||
if _has_column("entry_order_id"):
|
||||
columns.append("entry_order_id")
|
||||
values.append(entry_order_id)
|
||||
|
||||
if _has_column("notional_usdt"):
|
||||
columns.append("notional_usdt")
|
||||
values.append(notional_usdt)
|
||||
|
||||
if _has_column("margin_usdt"):
|
||||
columns.append("margin_usdt")
|
||||
values.append(margin_usdt)
|
||||
|
||||
if _has_column("atr"):
|
||||
columns.append("atr")
|
||||
values.append(atr)
|
||||
|
||||
if _has_column("stop_loss_price"):
|
||||
columns.append("stop_loss_price")
|
||||
values.append(stop_loss_price)
|
||||
|
||||
if _has_column("take_profit_price"):
|
||||
columns.append("take_profit_price")
|
||||
values.append(take_profit_price)
|
||||
|
||||
if _has_column("take_profit_1"):
|
||||
columns.append("take_profit_1")
|
||||
values.append(take_profit_1)
|
||||
|
||||
if _has_column("take_profit_2"):
|
||||
columns.append("take_profit_2")
|
||||
values.append(take_profit_2)
|
||||
|
||||
placeholders = ", ".join(["%s"] * len(columns))
|
||||
sql = f"INSERT INTO trades ({', '.join(columns)}) VALUES ({placeholders})"
|
||||
db.execute_update(sql, tuple(values))
|
||||
return db.execute_one("SELECT LAST_INSERT_ID() as id")['id']
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -266,6 +266,15 @@ const TradeList = () => {
|
|||
{stats.avg_pnl.toFixed(2)} USDT
|
||||
</div>
|
||||
</div>
|
||||
{"total_notional_usdt" in stats && (
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">总交易量(名义)</div>
|
||||
<div className="stat-value">{Number(stats.total_notional_usdt || 0).toFixed(2)} USDT</div>
|
||||
<div style={{ fontSize: '12px', color: '#999', marginTop: '4px' }}>
|
||||
(口径:入场价×数量)
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -281,6 +290,7 @@ const TradeList = () => {
|
|||
<th>交易对</th>
|
||||
<th>方向</th>
|
||||
<th>数量</th>
|
||||
<th>名义</th>
|
||||
<th>保证金</th>
|
||||
<th>入场价</th>
|
||||
<th>出场价</th>
|
||||
|
|
@ -295,12 +305,18 @@ const TradeList = () => {
|
|||
</thead>
|
||||
<tbody>
|
||||
{trades.map(trade => {
|
||||
// 计算保证金:如果有entry_value_usdt和leverage,则计算;否则使用名义价值/杠杆
|
||||
const entryValue = trade.entry_value_usdt !== undefined
|
||||
? parseFloat(trade.entry_value_usdt)
|
||||
: (parseFloat(trade.quantity || 0) * parseFloat(trade.entry_price || 0))
|
||||
// 名义/保证金:优先使用后端返回字段(notional_usdt / margin_usdt),否则回退计算
|
||||
const notional = trade.notional_usdt !== undefined && trade.notional_usdt !== null
|
||||
? parseFloat(trade.notional_usdt)
|
||||
: (
|
||||
trade.entry_value_usdt !== undefined && trade.entry_value_usdt !== null
|
||||
? parseFloat(trade.entry_value_usdt)
|
||||
: (parseFloat(trade.quantity || 0) * parseFloat(trade.entry_price || 0))
|
||||
)
|
||||
const leverage = parseFloat(trade.leverage || 10)
|
||||
const margin = leverage > 0 ? entryValue / leverage : 0
|
||||
const margin = trade.margin_usdt !== undefined && trade.margin_usdt !== null
|
||||
? parseFloat(trade.margin_usdt)
|
||||
: (leverage > 0 ? notional / leverage : 0)
|
||||
|
||||
// 计算盈亏比例(盈亏/保证金)
|
||||
const pnl = parseFloat(trade.pnl || 0)
|
||||
|
|
@ -349,6 +365,7 @@ const TradeList = () => {
|
|||
<td>{trade.symbol}</td>
|
||||
<td className={trade.side === 'BUY' ? 'buy' : 'sell'}>{trade.side}</td>
|
||||
<td>{parseFloat(trade.quantity).toFixed(4)}</td>
|
||||
<td>{notional >= 0.01 ? notional.toFixed(2) : notional.toFixed(4)} USDT</td>
|
||||
<td>{margin >= 0.01 ? margin.toFixed(2) : margin.toFixed(4)} USDT</td>
|
||||
<td>{parseFloat(trade.entry_price).toFixed(4)}</td>
|
||||
<td>{trade.exit_price ? parseFloat(trade.exit_price).toFixed(4) : '-'}</td>
|
||||
|
|
@ -374,12 +391,18 @@ const TradeList = () => {
|
|||
{/* 移动端卡片 */}
|
||||
<div className="trades-cards">
|
||||
{trades.map(trade => {
|
||||
// 计算保证金和盈亏比例
|
||||
const entryValue = trade.entry_value_usdt !== undefined
|
||||
? parseFloat(trade.entry_value_usdt)
|
||||
: (parseFloat(trade.quantity || 0) * parseFloat(trade.entry_price || 0))
|
||||
// 名义/保证金:优先后端字段
|
||||
const notional = trade.notional_usdt !== undefined && trade.notional_usdt !== null
|
||||
? parseFloat(trade.notional_usdt)
|
||||
: (
|
||||
trade.entry_value_usdt !== undefined && trade.entry_value_usdt !== null
|
||||
? parseFloat(trade.entry_value_usdt)
|
||||
: (parseFloat(trade.quantity || 0) * parseFloat(trade.entry_price || 0))
|
||||
)
|
||||
const leverage = parseFloat(trade.leverage || 10)
|
||||
const margin = leverage > 0 ? entryValue / leverage : 0
|
||||
const margin = trade.margin_usdt !== undefined && trade.margin_usdt !== null
|
||||
? parseFloat(trade.margin_usdt)
|
||||
: (leverage > 0 ? notional / leverage : 0)
|
||||
const pnl = parseFloat(trade.pnl || 0)
|
||||
const pnlPercent = margin > 0 ? (pnl / margin) * 100 : 0
|
||||
|
||||
|
|
@ -422,6 +445,10 @@ const TradeList = () => {
|
|||
<span className="trade-card-label">数量</span>
|
||||
<span className="trade-card-value">{parseFloat(trade.quantity).toFixed(4)}</span>
|
||||
</div>
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">名义</span>
|
||||
<span className="trade-card-value">{notional >= 0.01 ? notional.toFixed(2) : notional.toFixed(4)} USDT</span>
|
||||
</div>
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">保证金</span>
|
||||
<span className="trade-card-value">{margin >= 0.01 ? margin.toFixed(2) : margin.toFixed(4)} USDT</span>
|
||||
|
|
|
|||
|
|
@ -316,6 +316,23 @@ class PositionManager:
|
|||
entry_price = actual_entry_price
|
||||
quantity = filled_quantity # 使用实际成交数量
|
||||
logger.info(f"{symbol} [开仓] ✓ 使用实际成交价格: {entry_price:.4f} USDT (下单时价格: {original_entry_price:.4f}), 成交数量: {quantity:.4f}")
|
||||
|
||||
# 分步止盈(基于“实际成交价 + 已计算的止损/止盈”)
|
||||
if side == 'BUY':
|
||||
take_profit_1 = entry_price + (entry_price - stop_loss_price) # 盈亏比1:1
|
||||
else:
|
||||
take_profit_1 = entry_price - (stop_loss_price - entry_price) # 盈亏比1:1
|
||||
take_profit_2 = take_profit_price
|
||||
|
||||
# 交易规模:名义/保证金(用于统计总交易量与UI展示)
|
||||
try:
|
||||
notional_usdt = float(entry_price) * float(quantity)
|
||||
except Exception:
|
||||
notional_usdt = None
|
||||
try:
|
||||
margin_usdt = (float(notional_usdt) / float(leverage)) if notional_usdt is not None and float(leverage) > 0 else notional_usdt
|
||||
except Exception:
|
||||
margin_usdt = None
|
||||
|
||||
# 记录到数据库(只有在订单真正成交后才保存)
|
||||
trade_id = None
|
||||
|
|
@ -329,7 +346,14 @@ class PositionManager:
|
|||
entry_price=entry_price, # 使用实际成交价格
|
||||
leverage=leverage,
|
||||
entry_reason=entry_reason,
|
||||
entry_order_id=entry_order_id # 保存币安订单号
|
||||
entry_order_id=entry_order_id, # 保存币安订单号
|
||||
stop_loss_price=stop_loss_price,
|
||||
take_profit_price=take_profit_price,
|
||||
take_profit_1=take_profit_1,
|
||||
take_profit_2=take_profit_2,
|
||||
atr=atr,
|
||||
notional_usdt=notional_usdt,
|
||||
margin_usdt=margin_usdt,
|
||||
)
|
||||
logger.info(f"✓ {symbol} 交易记录已保存到数据库 (ID: {trade_id}, 订单号: {entry_order_id}, 成交价: {entry_price:.4f}, 成交数量: {quantity:.4f})")
|
||||
except Exception as e:
|
||||
|
|
@ -343,16 +367,6 @@ class PositionManager:
|
|||
elif not Trade:
|
||||
logger.warning(f"Trade模型未导入,无法保存 {symbol} 交易记录")
|
||||
|
||||
# 计算分步止盈价格
|
||||
# 第一目标:盈亏比1:1(保守,了结50%仓位)
|
||||
if side == 'BUY':
|
||||
take_profit_1 = entry_price + (entry_price - stop_loss_price) # 盈亏比1:1
|
||||
else:
|
||||
take_profit_1 = entry_price - (stop_loss_price - entry_price) # 盈亏比1:1
|
||||
|
||||
# 第二目标:原始止盈价(激进,剩余50%仓位)
|
||||
take_profit_2 = take_profit_price
|
||||
|
||||
# 记录持仓信息(包含动态止损止盈和分步止盈)
|
||||
from datetime import datetime
|
||||
position_info = {
|
||||
|
|
@ -1483,7 +1497,10 @@ class PositionManager:
|
|||
quantity=quantity,
|
||||
entry_price=entry_price,
|
||||
leverage=binance_position.get('leverage', 10),
|
||||
entry_reason='manual_entry' # 标记为手动开仓
|
||||
entry_reason='manual_entry', # 标记为手动开仓
|
||||
# 手动开仓无法拿到策略侧ATR/分步止盈,这里尽量补齐“规模字段”
|
||||
notional_usdt=(float(entry_price) * float(quantity)) if entry_price and quantity else None,
|
||||
margin_usdt=((float(entry_price) * float(quantity)) / float(binance_position.get('leverage', 10))) if entry_price and quantity and float(binance_position.get('leverage', 10) or 0) > 0 else None,
|
||||
)
|
||||
|
||||
logger.info(f"{symbol} [状态同步] ✓ 数据库记录已创建 (ID: {trade_id})")
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user