Deepseek介入OKX自动交易
本文最后更新于 2025-11-13,文章内容可能已经过时。
来源:https://github.com/huojichuanqi/ds/tree/main
新容器在 /root/btc_deepseek/plus 目录下运行,需要确保以下文件和目录结构是完整的。
以下是 /root/btc_deepseek/plus 目录下所有必须的文件内容:
📁 目录结构
/root/btc_deepseek/plus/
├── Dockerfile # Docker 镜像构建文件
├── requirements.txt # Python 依赖清单
├── .env # 环境变量和 API Key (敏感信息)
├── bot_web_app.py # 主 Python 应用 (已修复和增强)
├── templates/
│ └── index.html # Web UI 界面 (HTML)
└── static/
└── app.js # Web UI 动态脚本 (JavaScript)
1. 📄 Dockerfile
(用于构建镜像的指令)
Dockerfile
# 使用官方 Python 基础镜像
FROM python:3.11-slim
# 设置工作目录
WORKDIR /app
# 复制依赖文件并安装依赖 (先安装依赖可以利用Docker缓存)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制所有应用代码和前端文件
COPY . /app
# 暴露 Flask 应用的端口 (容器内部端口)
EXPOSE 5000
# 定义容器启动时执行的命令
CMD ["python", "bot_web_app.py"]
2. 📄 requirements.txt
(Python 依赖库列表)
Plaintext
flask
apscheduler
ccxt
openai
pandas
python-dotenv
requests
3. 📄 .env
(你的 API 密钥,请确保复制正确的密钥和密码)
Plaintext
DEEPSEEK_API_KEY=111111111111111111111111
# ... (其他不需要的密钥可以删除或留空)
OKX_API_KEY=1111111111111111111
OKX_SECRET=1111111111111111111111
OKX_PASSWORD=111111111111111111111114. 🐍 bot_web_app.py
import os
import time
import re
import json
import requests
import pandas as pd
import ccxt
from openai import OpenAI
from dotenv import load_dotenv
from datetime import datetime, timedelta
import traceback
# 核心导入
from flask import Flask, jsonify, request, render_template
from apscheduler.schedulers.background import BackgroundScheduler
import atexit
load_dotenv()
# --- 全局变量和初始化 ---
app = Flask(__name__, static_folder='static')
scheduler = BackgroundScheduler()
atexit.register(lambda: scheduler.shutdown())
# 核心状态存储 (新增日志和历史数据)
TRADE_LOGS = []
ORDER_HISTORY = [
{'id': '12345', 'symbol': 'BTC/USDT', 'side': 'buy', 'amount': 0.01, 'price': 60000.0, 'profit': 15.5, 'status': 'closed', 'timestamp': (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')},
{'id': '12346', 'symbol': 'BTC/USDT', 'side': 'sell', 'amount': 0.01, 'price': 62000.0, 'profit': -5.1, 'status': 'closed', 'timestamp': (datetime.now() - timedelta(hours=12)).strftime('%Y-%m-%d %H:%M:%S')}
]
ACCOUNT_BALANCE = {
'equity': 0,
'unrealized_pnl': 0,
'total_pnl': 0,
'usdt_balance': 0
}
BOT_STATUS = {
'running': False,
'last_run': 'N/A',
'status_message': '初始化中...',
'current_position': None,
'last_signal': None,
'config_ok': False
}
# 初始化DeepSeek客户端/OKX交易所
deepseek_client = OpenAI(
api_key=os.getenv('DEEPSEEK_API_KEY'),
base_url="https://api.deepseek.com"
)
exchange = ccxt.okx({
'options': {
'defaultType': 'swap',
},
'apiKey': os.getenv('OKX_API_KEY'),
'secret': os.getenv('OKX_SECRET'),
'password': os.getenv('OKX_PASSWORD'),
})
TRADE_CONFIG = {
'symbol': 'BTC/USDT:USDT', 'leverage': 10, 'timeframe': '15m', 'test_mode': False,
'data_points': 96, 'contract_size': 0.01, 'min_amount': 0.01,
'analysis_periods': {'short_term': 20, 'medium_term': 50, 'long_term': 96},
'position_management': {'enable_intelligent_position': True, 'base_usdt_amount': 100, 'high_confidence_multiplier': 1.5,
'medium_confidence_multiplier': 1.0, 'low_confidence_multiplier': 0.5, 'max_position_ratio': 10,
'trend_strength_multiplier': 1.2}
}
# --- 辅助函数 (已填充逻辑) ---
def log_trade_step(title, message, log_type='info'):
"""记录交易步骤日志,用于UI展示"""
global TRADE_LOGS
log_entry = {
'time': datetime.now().strftime('%H:%M:%S'),
'title': title,
'message': message,
'type': log_type
}
TRADE_LOGS.append(log_entry)
if len(TRADE_LOGS) > 50:
TRADE_LOGS.pop(0)
def fetch_account_summary():
"""获取账户余额和总览"""
global ACCOUNT_BALANCE
try:
balance = exchange.fetch_balance()
usdt_balance = balance['USDT']['free']
total_equity = balance['total'].get('USDT', 0)
positions = exchange.fetch_positions([TRADE_CONFIG['symbol']])
unrealized_pnl = sum(float(pos['unrealizedPnl']) for pos in positions if 'unrealizedPnl' in pos)
ACCOUNT_BALANCE.update({
'equity': total_equity,
'unrealized_pnl': unrealized_pnl,
'total_pnl': sum(float(order['profit']) for order in ORDER_HISTORY),
'usdt_balance': usdt_balance
})
except Exception as e:
log_trade_step("❌ 账户信息获取失败", f"错误: {e}", log_type='error')
def setup_exchange():
"""设置交易所参数 - 强制全仓模式"""
try:
print("🔍 获取BTC合约规格...")
markets = exchange.load_markets()
btc_market = markets[TRADE_CONFIG['symbol']]
TRADE_CONFIG['contract_size'] = float(btc_market['contractSize'])
TRADE_CONFIG['min_amount'] = btc_market['limits']['amount']['min']
# 检查并设置持仓模式和杠杆...
exchange.set_position_mode(False, TRADE_CONFIG['symbol'])
exchange.set_leverage(TRADE_CONFIG['leverage'], TRADE_CONFIG['symbol'], {'mgnMode': 'cross'})
print(f"✅ 已设置全仓模式,杠杆倍数: {TRADE_CONFIG['leverage']}x")
return True
except Exception as e:
print(f"❌ 交易所设置失败: {e}")
BOT_STATUS['status_message'] = f"交易所设置失败: {e}"
return False
def calculate_technical_indicators(df):
"""计算技术指标"""
try:
df['sma_5'] = df['close'].rolling(window=5, min_periods=1).mean()
df['sma_20'] = df['close'].rolling(window=20, min_periods=1).mean()
df['sma_50'] = df['close'].rolling(window=50, min_periods=1).mean()
delta = df['close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
rs = gain / loss
df['rsi'] = 100 - (100 / (1 + rs))
df = df.bfill().ffill()
return df
except Exception as e:
print(f"技术指标计算失败: {e}")
return df
def get_market_trend(df):
"""判断市场趋势"""
current_price = df['close'].iloc[-1]
trend_short = "上涨" if current_price > df['sma_20'].iloc[-1] else "下跌"
trend_medium = "上涨" if current_price > df['sma_50'].iloc[-1] else "下跌"
overall_trend = "强势上涨" if trend_short == "上涨" and trend_medium == "上涨" else \
"强势下跌" if trend_short == "下跌" and trend_medium == "下跌" else "震荡整理"
return {'overall': overall_trend}
def get_btc_ohlcv_enhanced():
"""增强版:获取BTC K线数据并计算技术指标"""
try:
ohlcv = exchange.fetch_ohlcv(TRADE_CONFIG['symbol'], TRADE_CONFIG['timeframe'], limit=TRADE_CONFIG['data_points'])
df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
df = calculate_technical_indicators(df)
current_data = df.iloc[-1]
previous_data = df.iloc[-2]
return {
'price': current_data['close'],
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'price_change': ((current_data['close'] - previous_data['close']) / previous_data['close']) * 100,
'trend_analysis': get_market_trend(df),
'full_data': df
}
except Exception as e:
print(f"获取增强K线数据失败: {e}")
return None
def generate_technical_analysis_text(price_data):
"""生成技术分析文本(简化)"""
trend = price_data.get('trend_analysis', {}).get('overall', 'N/A')
return f"当前价格: ${price_data['price']:,.2f}。整体趋势分析为: {trend}"
def get_current_position():
"""获取当前持仓情况 - OKX版本"""
try:
positions = exchange.fetch_positions([TRADE_CONFIG['symbol']])
for pos in positions:
if pos['symbol'] == TRADE_CONFIG['symbol'] and float(pos['contracts']) > 0:
return {
'side': pos['side'],
'size': float(pos['contracts']),
'entry_price': float(pos['entryPrice']),
'unrealized_pnl': float(pos['unrealizedPnl']),
'leverage': float(pos['leverage']),
'symbol': pos['symbol']
}
return None
except Exception:
return None
# --- 位于 bot_web_app.py 中间的辅助函数区域 ---
def safe_json_parse(json_str):
"""安全解析JSON,处理DeepSeek的Markdown代码块标记和格式不规范的情况"""
# 1. 尝试移除 Markdown 代码块标记(修复 DeepSeek 返回带 ```json 的问题)
# 检查是否以 ``` 开头和结尾
if json_str.strip().startswith("```") and json_str.strip().endswith("```"):
# 移除开头的 '```json' 或 '```',并移除结尾的 '```'
json_str = json_str.strip().lstrip('```json').lstrip('```').rstrip('```').strip()
# 2. 尝试标准解析
try:
return json.loads(json_str)
except json.JSONDecodeError:
# Fallback 尝试,处理其他不规范的格式(例如单引号)
try:
json_str = json_str.replace("'", '"')
# 修正没有引号的键名(如果存在)
json_str = re.sub(r'(\w+):', r'"\1":', json_str)
# 修正末尾多余的逗号
json_str = re.sub(r',\s*}', '}', json_str)
json_str = re.sub(r',\s*]', ']', json_str)
return json.loads(json_str)
except json.JSONDecodeError as e:
print(f"JSON解析最终失败,原始内容: {json_str}")
return None
def create_fallback_signal(price_data):
"""创建备用交易信号"""
return {
"signal": "HOLD", "reason": "AI分析失败,采取保守策略", "confidence": "LOW", "is_fallback": True
}
def analyze_with_deepseek(price_data):
"""使用DeepSeek分析市场并生成交易信号(已添加日志记录 Prompt 和 响应)"""
try:
technical_analysis = generate_technical_analysis_text(price_data)
# 构建发送给DeepSeek的Prompt
prompt = f"""
你是一位专业的加密货币量化交易员。请根据提供的市场信息和技术分析,以最高的准确率生成一个交易信号。
当前K线周期: {TRADE_CONFIG['timeframe']}。
市场信息: {technical_analysis}。
请严格以 JSON 格式返回结果,包含 'signal' (BUY/SELL/HOLD), 'reason' (中文解释), 'confidence' (HIGH/MEDIUM/LOW)。
"""
# 🆕 日志记录:发送的Prompt内容
log_trade_step(
"🧠 步骤 2.1: DeepSeek Prompt (已发送)",
prompt.strip().replace('\n', ' '),
log_type='info'
)
response = deepseek_client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "system", "content": "你是一位专业的交易员,请严格按照用户要求的JSON格式输出交易信号。"},
{"role": "user", "content": prompt}],
stream=False,
temperature=0.1
)
result = response.choices[0].message.content
# 🆕 日志记录:DeepSeek返回的原始JSON响应
log_trade_step(
"🧠 步骤 2.2: DeepSeek 原始响应 (已接收)",
result,
log_type='info'
)
signal_data = safe_json_parse(result)
if signal_data is None:
signal_data = create_fallback_signal(price_data)
signal_data['timestamp'] = price_data['timestamp']
return signal_data
except Exception:
# 如果API调用或处理失败,记录错误堆栈
log_trade_step("❌ 步骤 2.2: DeepSeek API 失败", traceback.format_exc(), log_type='error')
return create_fallback_signal(price_data)
def analyze_with_deepseek_with_retry(price_data, max_retries=2):
"""带重试的DeepSeek分析"""
for attempt in range(max_retries):
signal_data = analyze_with_deepseek(price_data)
if signal_data and not signal_data.get('is_fallback', False):
return signal_data
time.sleep(1)
return create_fallback_signal(price_data)
def calculate_intelligent_position(signal_data, price_data, current_position):
"""计算智能仓位大小(简化)"""
config = TRADE_CONFIG['position_management']
base_contracts = config['base_usdt_amount'] / (price_data['price'] * TRADE_CONFIG['contract_size'])
confidence_multiplier = {'HIGH': 1.5, 'MEDIUM': 1.0, 'LOW': 0.5}.get(signal_data['confidence'], 1.0)
final_contracts = round(base_contracts * confidence_multiplier, 2)
return max(final_contracts, TRADE_CONFIG['min_amount'])
def execute_intelligent_trade(signal_data, price_data):
"""执行智能交易(简化)"""
current_position = get_current_position()
position_size = calculate_intelligent_position(signal_data, price_data, current_position)
if signal_data['confidence'] == 'LOW' and not TRADE_CONFIG['test_mode']:
return log_trade_step("⚠️ 步骤 3: 跳过", "低信心信号,跳过执行。", log_type='info')
if TRADE_CONFIG['test_mode']:
return log_trade_step("📝 步骤 3: 模拟", f"测试模式,模拟信号: {signal_data['signal']},仓位: {position_size}", log_type='info')
try:
side = 'buy' if signal_data['signal'] == 'BUY' else 'sell'
if signal_data['signal'] == 'HOLD':
return
# 1. 尝试平仓
if current_position:
# 如果信号与持仓反向,先平仓
if (side == 'buy' and current_position['side'] == 'short') or \
(side == 'sell' and current_position['side'] == 'long'):
reduce_side = 'sell' if current_position['side'] == 'long' else 'buy'
exchange.create_market_order(TRADE_CONFIG['symbol'], reduce_side, current_position['size'], params={'reduceOnly': True})
log_trade_step("🔄 步骤 3: 平仓", f"已平仓: {current_position['side']} {current_position['size']}", log_type='info')
time.sleep(0.5)
# 2. 开仓 (如果不是HOLD)
if current_position is None or (side == 'buy' and current_position['side'] == 'short') or (side == 'sell' and current_position['side'] == 'long'):
exchange.create_market_order(TRADE_CONFIG['symbol'], side, position_size)
log_trade_step("🆕 步骤 3: 开仓", f"成功开仓: {side} {position_size}", log_type='success')
except Exception as e:
log_trade_step("❌ 交易执行失败", f"错误: {e}", log_type='error')
# --- 核心调度函数 ---
def trading_bot():
"""主交易机器人函数,由 APScheduler 调用"""
global BOT_STATUS
log_trade_step("🤖 任务开始", f"交易周期 {TRADE_CONFIG['timeframe']} 开始执行。")
if not BOT_STATUS['config_ok']:
log_trade_step("⚠️ 机器人未通过初始化检查,跳过本次运行。", log_type='error')
return
# 1. 获取增强版K线数据
log_trade_step("📊 步骤 1: 获取行情数据", "正在从 OKX 获取 K线数据...")
price_data = get_btc_ohlcv_enhanced()
if not price_data:
log_trade_step("❌ 步骤 1: 失败", "K线数据获取失败,跳过本次运行。", log_type='error')
return
log_trade_step("✅ 步骤 1: 成功", f"当前价格: ${price_data['price']:,.2f},数据就绪。")
# 2. 使用DeepSeek分析(带重试)
log_trade_step("🧠 步骤 2: AI分析", "正在调用 DeepSeek 进行智能分析...")
signal_data = analyze_with_deepseek_with_retry(price_data)
if signal_data.get('is_fallback', False):
log_trade_step("⚠️ 步骤 2: 完成", f"AI分析失败,使用备用信号: {signal_data['signal']}", log_type='error')
else:
log_trade_step("✅ 步骤 2: 完成", f"AI信号: {signal_data['signal']} (置信度: {signal_data['confidence']})", log_type='success')
# 3. 执行智能交易
log_trade_step("💸 步骤 3: 交易执行", f"执行信号: {signal_data['signal']}...")
execute_intelligent_trade(signal_data, price_data)
log_trade_step("✅ 步骤 3: 交易完成", "已执行或跳过交易操作。", log_type='success')
# 实时更新账户信息
fetch_account_summary()
# 状态更新
current_pos = get_current_position()
BOT_STATUS['last_run'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
BOT_STATUS['status_message'] = f"上次执行成功,信号: {signal_data['signal']},持仓: {current_pos['side'] if current_pos else '无'}。"
BOT_STATUS['current_position'] = current_pos
BOT_STATUS['last_signal'] = signal_data
log_trade_step("🏁 任务结束", "交易任务周期结束,等待下一次运行。", log_type='info')
def init_bot():
"""机器人初始化和设置"""
print("BTC/USDT OKX自动交易机器人启动成功!")
if not setup_exchange():
print("交易所初始化失败,程序退出")
return False
BOT_STATUS['status_message'] = "交易所设置成功,Web服务已就绪。请通过Web界面启动任务。"
BOT_STATUS['config_ok'] = True
fetch_account_summary()
return True
# --- Flask Web 路由 ---
@app.route('/')
def index():
"""首页 - 渲染 Web UI"""
return render_template('index.html')
@app.route('/api/data', methods=['GET'])
def get_dashboard_data():
"""获取动态数据 (状态、日志、持仓)"""
fetch_account_summary()
BOT_STATUS['current_position'] = get_current_position()
return jsonify({
'status': 'RUNNING' if BOT_STATUS['running'] else 'STOPPED',
'details': BOT_STATUS,
'logs': TRADE_LOGS,
'balance': ACCOUNT_BALANCE,
'config': {
'symbol': TRADE_CONFIG['symbol'],
'timeframe': TRADE_CONFIG['timeframe'],
'leverage': TRADE_CONFIG['leverage'],
'test_mode': TRADE_CONFIG['test_mode']
}
})
@app.route('/api/orders', methods=['GET'])
def get_historical_orders():
"""获取历史订单数据"""
return jsonify(sorted(ORDER_HISTORY, key=lambda x: x['timestamp'], reverse=True))
@app.route('/control/<action>', methods=['POST'])
def control_bot(action):
"""控制交易任务的启动、暂停、停止、重启 (已修复 SyntaxError)"""
global scheduler # <-- 修复点:确保 global 声明在函数体最前面
if not BOT_STATUS['config_ok'] and action != 'restart':
return jsonify({'message': '交易所配置失败,无法启动任务。', 'status': 'ERROR'}), 400
try:
if action == 'start':
if not BOT_STATUS['running']:
scheduler.add_job(func=trading_bot, trigger='interval', minutes=15, id='trade_job', next_run_time=datetime.now())
scheduler.start()
BOT_STATUS['running'] = True
BOT_STATUS['status_message'] = '交易任务已启动,等待第一次执行...'
log_trade_step("🟢 控制", "通过 Web API 启动任务。", log_type='success')
return jsonify({'message': '交易任务已启动', 'status': 'OK'})
elif action == 'stop' or action == 'pause':
if BOT_STATUS['running']:
scheduler.shutdown(wait=False)
BOT_STATUS['running'] = False
BOT_STATUS['status_message'] = '交易任务已停止/暂停。'
log_trade_step("🛑 控制", "通过 Web API 停止/暂停任务。", log_type='info')
return jsonify({'message': '交易任务已停止/暂停', 'status': 'OK'})
elif action == 'restart':
if BOT_STATUS['running']:
scheduler.shutdown(wait=False)
BOT_STATUS['running'] = False
log_trade_step("🔃 控制", "正在停止旧任务...", log_type='info')
# 重新初始化调度器
scheduler = BackgroundScheduler()
atexit.register(lambda: scheduler.shutdown())
# 启动
scheduler.add_job(func=trading_bot, trigger='interval', minutes=15, id='trade_job', next_run_time=datetime.now())
scheduler.start()
BOT_STATUS['running'] = True
BOT_STATUS['status_message'] = '交易任务已重启并启动,等待第一次执行...'
log_trade_step("🟢 控制", "任务已成功重启并启动。", log_type='success')
return jsonify({'message': '交易任务已重启', 'status': 'OK'})
else:
return jsonify({'message': f'未知操作: {action}'}), 400
except Exception as e:
log_trade_step("❌ 控制失败", f"控制操作失败: {e}", log_type='error')
traceback.print_exc()
return jsonify({'message': f'控制操作失败: {e}'}), 500
if __name__ == '__main__':
if init_bot():
print("\n🌐 Flask Web Server 正在启动...")
app.run(host='0.0.0.0', port=5000)
else:
print("Web应用启动失败,请解决交易所配置问题。")5. 🌐 templates/index.html
(前端界面 HTML 文件)
HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DeepSeek 量化交易机器人仪表板</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 20px; background-color: #f4f6f8; color: #333; }
.container { max-width: 1200px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); }
h1 { color: #007bff; border-bottom: 2px solid #e9ecef; padding-bottom: 10px; margin-bottom: 20px; }
.control-panel { display: flex; gap: 10px; margin-bottom: 20px; padding: 15px; background: #e9f7ff; border-radius: 6px; }
.control-panel button { padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; transition: background-color 0.3s; }
#start-btn { background-color: #28a745; color: white; }
#stop-btn { background-color: #dc3545; color: white; }
#restart-btn { background-color: #ffc107; color: #333; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.card { padding: 15px; border: 1px solid #dee2e6; border-radius: 6px; background: #fff; }
.status-badge { padding: 5px 10px; border-radius: 12px; font-weight: bold; display: inline-block; margin-left: 10px; }
.status-badge.RUNNING { background-color: #d4edda; color: #155724; }
.status-badge.STOPPED { background-color: #f8d7da; color: #721c24; }
.status-badge.ERROR { background-color: #fff3cd; color: #856404; }
.log-list { max-height: 400px; overflow-y: scroll; border: 1px solid #ccc; padding: 10px; border-radius: 4px; background: #fcfcfc; }
.log-item { margin-bottom: 5px; padding: 5px; border-bottom: 1px dotted #eee; font-size: 0.9em; }
.log-item span { display: block; }
.log-item .time { color: #6c757d; font-weight: bold; }
.log-item.error { background-color: #f8d7da; }
.log-item.success { background-color: #d4edda; }
.log-item.warning { background-color: #fff3cd; }
</style>
</head>
<body>
<div class="container">
<h1>DeepSeek BTC 交易机器人 (Test:8088)</h1>
<div class="control-panel">
<button id="start-btn">启动 Start</button>
<button id="stop-btn">暂停 Stop/Pause</button>
<button id="restart-btn">重启 Restart</button>
<p>任务状态: <span id="bot-status" class="status-badge">加载中...</span></p>
</div>
<div class="grid">
<div class="card">
<h2>📊 账户概览</h2>
<p><strong>总资产净值:</strong> <span id="equity">$...</span> USDT</p>
<p><strong>可用余额:</strong> <span id="usdt-balance">$...</span> USDT</p>
<p><strong>未实现盈亏 (PNL):</strong> <span id="unrealized-pnl">$...</span> USDT</p>
<p><strong>累计已实现PNL:</strong> <span id="total-pnl">$...</span> USDT</p>
</div>
<div class="card">
<h2>📈 任务与持仓</h2>
<p><strong>交易对:</strong> <span id="symbol">...</span></p>
<p><strong>K线周期:</strong> <span id="timeframe">...</span></p>
<p><strong>杠杆倍数:</strong> <span id="leverage">...</span>x</p>
<p><strong>当前持仓:</strong> <span id="position">...</span></p>
<p><strong>上次信号:</strong> <span id="last-signal">...</span></p>
<p><strong>上次运行时间:</strong> <span id="last-run">...</span></p>
</div>
</div>
<div class="card" style="margin-top: 20px;">
<h2>📝 交易流程实时日志</h2>
<div id="trade-logs" class="log-list">
<p style="text-align: center; color: #6c757d;">等待机器人初始化...</p>
</div>
</div>
<div class="card" style="margin-top: 20px;">
<h2>📜 历史订单 (示例)</h2>
<table style="width: 100%; border-collapse: collapse; font-size: 0.9em;">
<thead>
<tr style="background-color: #f1f1f1;">
<th style="padding: 8px; border: 1px solid #ddd;">时间</th>
<th style="padding: 8px; border: 1px solid #ddd;">方向</th>
<th style="padding: 8px; border: 1px solid #ddd;">数量</th>
<th style="padding: 8px; border: 1px solid #ddd;">成交价</th>
<th style="padding: 8px; border: 1px solid #ddd;">盈亏 (USDT)</th>
<th style="padding: 8px; border: 1px solid #ddd;">状态</th>
</tr>
</thead>
<tbody id="order-history">
</tbody>
</table>
</div>
</div>
<script src="/static/app.js"></script>
</body>
</html>
6. ☕ static/app.js
(前端 JavaScript 文件)
JavaScript
document.addEventListener('DOMContentLoaded', function() {
const STATUS_URL = '/api/data';
const CONTROL_URL = '/control/';
const statusMap = {
'RUNNING': '运行中',
'STOPPED': '已停止',
'ERROR': '错误',
'PENDING': '等待中'
};
const logTypeMap = {
'info': 'log-item',
'success': 'log-item success',
'error': 'log-item error',
'warning': 'log-item warning'
};
function updateDashboard() {
fetch(STATUS_URL)
.then(response => response.json())
.then(data => {
// 1. 更新控制面板状态
const statusElement = document.getElementById('bot-status');
statusElement.textContent = statusMap[data.status] || data.status;
statusElement.className = `status-badge ${data.status}`;
// 2. 更新账户概览
document.getElementById('equity').textContent = formatCurrency(data.balance.equity);
document.getElementById('usdt-balance').textContent = formatCurrency(data.balance.usdt_balance);
document.getElementById('unrealized-pnl').textContent = formatCurrency(data.balance.unrealized_pnl, true);
document.getElementById('total-pnl').textContent = formatCurrency(data.balance.total_pnl, true);
// 3. 更新任务与持仓信息
document.getElementById('symbol').textContent = data.config.symbol;
document.getElementById('timeframe').textContent = data.config.timeframe;
document.getElementById('leverage').textContent = data.config.leverage;
document.getElementById('last-run').textContent = data.details.last_run;
const position = data.details.current_position;
document.getElementById('position').textContent = position ? `${position.side.toUpperCase()} ${position.size} BTC @ ${formatCurrency(position.entry_price)}` : '无持仓';
const lastSignal = data.details.last_signal;
document.getElementById('last-signal').textContent = lastSignal ? `${lastSignal.signal} (${lastSignal.confidence})` : 'N/A';
// 4. 更新日志
const logContainer = document.getElementById('trade-logs');
// 只在日志发生变化时更新,并保持滚动到底部
const newLogHtml = data.logs.reverse().map(log => `
<div class="${logTypeMap[log.type] || 'log-item'}">
<span class="time">[${log.time}]</span>
<span class="title" style="font-weight: 500;">${log.title}:</span>
<span class="message">${log.message}</span>
</div>
`).join('');
if (logContainer.innerHTML !== newLogHtml) {
logContainer.innerHTML = newLogHtml;
logContainer.scrollTop = logContainer.scrollHeight; // 自动滚动到底部
}
})
.catch(error => console.error('Error fetching dashboard data:', error));
// 5. 更新历史订单 (简化:使用API返回的示例)
fetch('/api/orders')
.then(response => response.json())
.then(orders => {
const orderBody = document.getElementById('order-history');
orderBody.innerHTML = orders.map(order => `
<tr>
<td style="padding: 8px; border: 1px solid #ddd;">${order.timestamp.split(' ')[1]}</td>
<td style="padding: 8px; border: 1px solid #ddd; color: ${order.side === 'buy' ? 'green' : 'red'}; font-weight: bold;">${order.side.toUpperCase()}</td>
<td style="padding: 8px; border: 1px solid #ddd;">${order.amount}</td>
<td style="padding: 8px; border: 1px solid #ddd;">${formatCurrency(order.price)}</td>
<td style="padding: 8px; border: 1px solid #ddd; color: ${order.profit >= 0 ? 'green' : 'red'};">${formatCurrency(order.profit, true)}</td>
<td style="padding: 8px; border: 1px solid #ddd;">${order.status.toUpperCase()}</td>
</tr>
`).join('');
})
.catch(error => console.error('Error fetching order history:', error));
}
function formatCurrency(amount, isPnl = false) {
if (typeof amount !== 'number') return 'N/A';
const sign = isPnl && amount > 0 ? '+' : '';
return `${sign}$${amount.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`;
}
function controlBot(action) {
fetch(CONTROL_URL + action, { method: 'POST' })
.then(response => response.json())
.then(data => {
alert(data.message);
updateDashboard(); // 立即刷新状态
})
.catch(error => {
alert(`控制失败: ${error}`);
});
}
document.getElementById('start-btn').addEventListener('click', () => controlBot('start'));
document.getElementById('stop-btn').addEventListener('click', () => controlBot('stop'));
document.getElementById('restart-btn').addEventListener('click', () => controlBot('restart'));
// 初始加载和定时刷新
updateDashboard();
setInterval(updateDashboard, 3000);
});
🚀 最终部署命令
请在你的服务器终端执行以下命令,完成部署:
1. 创建目录和子目录
Bash
mkdir -p /root/btc_deepseek/plus/templates
mkdir -p /root/btc_deepseek/plus/static
2. 将上述所有文件内容粘贴到对应路径。
3. 构建新的 Docker 镜像
Bash
cd /root/btc_deepseek/plus
docker build --no-cache -t btc-web-app:v5 .
4. 运行新的容器(原容器不变)
Bash
docker run -d --name btc_web_test_plus_runner -p 8088:5000 btc-web-app:v5
5. 访问新的测试页面
在你的浏览器中访问:http://[你的服务器IP]:8088/
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 Dr.Chen
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果