This commit is contained in:
aaron 2026-03-01 01:06:28 +08:00
parent 1a9bf9f74b
commit 3079430bbc
3 changed files with 87 additions and 27 deletions

View File

@ -1,5 +1,5 @@
""" """
模拟交易 API 交易 API
""" """
from fastapi import APIRouter, HTTPException, Query from fastapi import APIRouter, HTTPException, Query
from typing import Optional from typing import Optional
@ -13,7 +13,7 @@ from app.services.db_service import db_service
from app.utils.logger import logger from app.utils.logger import logger
router = APIRouter(prefix="/api/paper-trading", tags=["交易"]) router = APIRouter(prefix="/api/trading", tags=["交易"])
class CloseOrderRequest(BaseModel): class CloseOrderRequest(BaseModel):

View File

@ -684,10 +684,10 @@ async def health_check():
"""健康检查""" """健康检查"""
return {"status": "healthy"} return {"status": "healthy"}
@app.get("/paper-trading") @app.get("/trading")
async def paper_trading_page(): async def trading_page():
"""模拟交易页面""" """交易页面"""
page_path = os.path.join(frontend_path, "paper-trading.html") page_path = os.path.join(frontend_path, "trading.html")
if os.path.exists(page_path): if os.path.exists(page_path):
return FileResponse(page_path) return FileResponse(page_path)
return {"message": "页面不存在"} return {"message": "页面不存在"}

View File

@ -57,6 +57,8 @@
font-size: 24px; font-size: 24px;
font-weight: 300; font-weight: 300;
color: var(--text-primary); color: var(--text-primary);
user-select: none;
cursor: default;
} }
.trading-title span { .trading-title span {
@ -571,6 +573,17 @@
background: rgba(255, 68, 68, 0.1); background: rgba(255, 68, 68, 0.1);
} }
/* 管理员模式指示器 */
.admin-indicator {
font-size: 16px;
opacity: 0.6;
transition: opacity 0.2s;
}
.admin-indicator:hover {
opacity: 1;
}
/* 操作按钮 */ /* 操作按钮 */
.action-btn { .action-btn {
padding: 4px 8px; padding: 4px 8px;
@ -1150,23 +1163,27 @@
<div class="sticky-header"> <div class="sticky-header">
<!-- 头部 --> <!-- 头部 -->
<div class="trading-header"> <div class="trading-header">
<h1 class="trading-title">AI 自动化交易 <span>| AI Agent Trading System</span></h1> <h1 class="trading-title" @click="handleTitleClick">AI 自动化交易 <span>| AI Agent Trading System</span></h1>
<div style="display: flex; align-items: center; gap: 12px;"> <div style="display: flex; align-items: center; gap: 12px;">
<div class="monitor-status"> <div class="monitor-status">
<div class="monitor-dot" :class="{ running: monitorRunning }"></div> <div class="monitor-dot" :class="{ running: monitorRunning }"></div>
<span>{{ monitorRunning ? '监控中' : '未启动' }}</span> <span>{{ monitorRunning ? '监控中' : '未启动' }}</span>
</div> </div>
<div class="admin-dropdown"> <!-- 隐藏的管理菜单,仅在管理员模式下显示 -->
<div class="admin-dropdown" v-if="adminMode">
<button class="admin-btn" @click="toggleAdminMenu">管理 ▾</button> <button class="admin-btn" @click="toggleAdminMenu">管理 ▾</button>
<div class="admin-menu" v-if="showAdminMenu"> <div class="admin-menu" v-if="showAdminMenu">
<button @click="toggleAdminMode"> <button @click="toggleAdminMode">
<span v-if="adminMode">🔓 关闭管理员模式</span> <span>🔓 关闭管理员模式</span>
<span v-else>🔒 开启管理员模式</span>
</button> </button>
<button @click="adminSendReport">发送报告</button> <button @click="adminSendReport">发送报告</button>
<button @click="adminResetData" class="danger">重置数据</button> <button @click="adminResetData" class="danger">重置数据</button>
</div> </div>
</div> </div>
<!-- 管理员模式指示器(仅在开启后显示) -->
<div class="admin-indicator" v-if="adminMode">
<span>⚙️</span>
</div>
</div> </div>
</div> </div>
@ -1672,8 +1689,10 @@
isFirstLoad: true, isFirstLoad: true,
sendingReport: false, sendingReport: false,
showAdminMenu: false, showAdminMenu: false,
adminPassword: '223388', adminPassword: '223388', // 管理员密码
adminMode: localStorage.getItem('paperTradingAdminMode') === 'true', adminMode: false, // 不再持久化,关闭页面后自动退出
titleClickCount: 0, // 标题点击计数
titleClickTimer: null, // 点击计时器
showShareModal: false, showShareModal: false,
shareOrderData: { shareOrderData: {
symbol: '', symbol: '',
@ -1707,6 +1726,46 @@
document.removeEventListener('click', this.closeAdminMenu); document.removeEventListener('click', this.closeAdminMenu);
}, },
methods: { methods: {
// 标题点击处理 - 隐藏的管理员入口连续点击3次
handleTitleClick() {
this.titleClickCount++;
// 清除之前的计时器
if (this.titleClickTimer) {
clearTimeout(this.titleClickTimer);
}
// 设置新的计时器2秒后重置计数
this.titleClickTimer = setTimeout(() => {
this.titleClickCount = 0;
}, 2000);
// 连续点击3次触发管理员模式切换
if (this.titleClickCount === 3) {
this.titleClickCount = 0;
this.promptAdminMode();
}
},
// 提示输入管理员密码
promptAdminMode() {
if (this.adminMode) {
// 已开启,直接关闭
this.adminMode = false;
this.showAdminMenu = false;
alert('管理员模式已关闭');
} else {
// 未开启,提示输入密码
const password = prompt('请输入管理员密码:');
if (password === this.adminPassword) {
this.adminMode = true;
alert('管理员模式已开启\n\n功能\n• 删除订单\n• 重置数据\n• 发送报告');
} else if (password !== null) {
alert('密码错误');
}
}
},
// 切换管理菜单 // 切换管理菜单
toggleAdminMenu(event) { toggleAdminMenu(event) {
event.stopPropagation(); event.stopPropagation();
@ -1720,11 +1779,12 @@
} }
}, },
// 切换管理员模式 // 切换管理员模式(仅用于关闭)
toggleAdminMode() { toggleAdminMode() {
this.adminMode = !this.adminMode; if (this.adminMode) {
localStorage.setItem('paperTradingAdminMode', this.adminMode); this.adminMode = false;
this.showAdminMenu = false; this.showAdminMenu = false;
}
}, },
// 验证管理密码 // 验证管理密码
@ -1870,7 +1930,7 @@
} }
try { try {
const response = await fetch('/api/paper-trading/reset', { const response = await fetch('/api/trading/reset', {
method: 'POST' method: 'POST'
}); });
const data = await response.json(); const data = await response.json();
@ -1888,7 +1948,7 @@
async sendReport() { async sendReport() {
this.sendingReport = true; this.sendingReport = true;
try { try {
const response = await fetch('/api/paper-trading/report?hours=4&send_telegram=true', { const response = await fetch('/api/trading/report?hours=4&send_telegram=true', {
method: 'POST' method: 'POST'
}); });
const data = await response.json(); const data = await response.json();
@ -1909,7 +1969,7 @@
}, },
async fetchActiveOrders() { async fetchActiveOrders() {
const response = await fetch('/api/paper-trading/orders/active'); const response = await fetch('/api/trading/orders/active');
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
this.activeOrders = data.orders; this.activeOrders = data.orders;
@ -1917,7 +1977,7 @@
}, },
async fetchHistoryOrders() { async fetchHistoryOrders() {
const response = await fetch('/api/paper-trading/orders?status=closed&limit=50'); const response = await fetch('/api/trading/orders?status=closed&limit=50');
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
this.historyOrders = data.orders; this.historyOrders = data.orders;
@ -1925,7 +1985,7 @@
}, },
async fetchStatistics() { async fetchStatistics() {
const response = await fetch('/api/paper-trading/statistics'); const response = await fetch('/api/trading/statistics');
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
this.stats = data.statistics; this.stats = data.statistics;
@ -1933,7 +1993,7 @@
}, },
async fetchAccountStatus() { async fetchAccountStatus() {
const response = await fetch('/api/paper-trading/account'); const response = await fetch('/api/trading/account');
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
this.account = data.account; this.account = data.account;
@ -1941,7 +2001,7 @@
}, },
async fetchMonitorStatus() { async fetchMonitorStatus() {
const response = await fetch('/api/paper-trading/monitor/status'); const response = await fetch('/api/trading/monitor/status');
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
this.monitorRunning = data.running; this.monitorRunning = data.running;
@ -1961,7 +2021,7 @@
} }
try { try {
const response = await fetch(`/api/paper-trading/orders/${order.order_id}/close`, { const response = await fetch(`/api/trading/orders/${order.order_id}/close`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ exit_price: price }) body: JSON.stringify({ exit_price: price })
@ -1998,7 +2058,7 @@
} }
try { try {
const response = await fetch(`/api/paper-trading/orders/${order.order_id}`, { const response = await fetch(`/api/trading/orders/${order.order_id}`, {
method: 'DELETE' method: 'DELETE'
}); });
const data = await response.json(); const data = await response.json();
@ -2040,7 +2100,7 @@
} }
try { try {
const response = await fetch(`/api/paper-trading/orders/${order.order_id}`, { const response = await fetch(`/api/trading/orders/${order.order_id}`, {
method: 'DELETE' method: 'DELETE'
}); });
const data = await response.json(); const data = await response.json();
@ -2150,7 +2210,7 @@
async fetchDailyReturns() { async fetchDailyReturns() {
this.loadingReturns = true; this.loadingReturns = true;
try { try {
const response = await fetch('/api/paper-trading/daily-returns?days=30'); const response = await fetch('/api/trading/daily-returns?days=30');
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
this.dailyReturns = data.data; this.dailyReturns = data.data;