a
This commit is contained in:
parent
186b2f2424
commit
e5cea19b6a
|
|
@ -63,9 +63,40 @@ npm run dev
|
|||
|
||||
访问前端:http://localhost:3000
|
||||
|
||||
## 5. 启动交易系统
|
||||
## 5. 安装交易系统依赖
|
||||
|
||||
### 方式1:使用安装脚本(推荐)
|
||||
|
||||
```bash
|
||||
cd trading_system
|
||||
./setup.sh
|
||||
```
|
||||
|
||||
### 方式2:使用项目根目录虚拟环境
|
||||
|
||||
```bash
|
||||
# 如果项目根目录已有虚拟环境
|
||||
source .venv/bin/activate # 从项目根目录
|
||||
cd trading_system
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 方式3:手动创建虚拟环境
|
||||
|
||||
```bash
|
||||
cd trading_system
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 6. 启动交易系统
|
||||
|
||||
```bash
|
||||
# 确保虚拟环境已激活
|
||||
source .venv/bin/activate # 从项目根目录
|
||||
# 或 source ../.venv/bin/activate # 从trading_system目录
|
||||
|
||||
# 进入trading_system目录
|
||||
cd trading_system
|
||||
python main.py
|
||||
|
|
|
|||
31
README.md
31
README.md
|
|
@ -41,7 +41,16 @@ cd backend
|
|||
python init_config.py
|
||||
```
|
||||
|
||||
### 2. 启动后端服务
|
||||
### 2. 创建虚拟环境(推荐)
|
||||
|
||||
```bash
|
||||
# 在项目根目录创建虚拟环境(供backend和trading_system共享)
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate # Linux/Mac
|
||||
# 或 .venv\Scripts\activate # Windows
|
||||
```
|
||||
|
||||
### 3. 启动后端服务
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
|
|
@ -50,7 +59,7 @@ export DB_HOST=localhost DB_USER=root DB_PASSWORD=xxx DB_NAME=auto_trade_sys
|
|||
uvicorn api.main:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
### 3. 启动前端
|
||||
### 4. 启动前端
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
|
|
@ -58,17 +67,26 @@ npm install
|
|||
npm run dev
|
||||
```
|
||||
|
||||
### 4. 启动交易系统
|
||||
### 5. 安装并启动交易系统
|
||||
|
||||
```bash
|
||||
# 方式1:从trading_system目录运行(推荐)
|
||||
# 方式1:使用安装脚本(推荐)
|
||||
cd trading_system
|
||||
./setup.sh
|
||||
|
||||
# 然后运行(确保虚拟环境已激活)
|
||||
source ../.venv/bin/activate # 或 source .venv/bin/activate
|
||||
python main.py
|
||||
|
||||
# 方式2:从项目根目录运行
|
||||
python -m trading_system.main
|
||||
# 方式2:手动安装
|
||||
cd trading_system
|
||||
source ../.venv/bin/activate # 激活虚拟环境
|
||||
pip install -r requirements.txt
|
||||
python main.py
|
||||
```
|
||||
|
||||
**注意**:现代 Linux 系统(如 Ubuntu 22.04+)不允许直接在系统 Python 中安装包,必须使用虚拟环境。详见 `INSTALL.md`。
|
||||
|
||||
## 功能特性
|
||||
|
||||
1. **自动市场扫描**:每1小时扫描所有USDT永续合约,发现涨跌幅最大的前10个货币对
|
||||
|
|
@ -119,6 +137,7 @@ python -m trading_system.main
|
|||
## 文档
|
||||
|
||||
- `QUICK_START.md` - 快速开始指南
|
||||
- `INSTALL.md` - 安装指南(虚拟环境设置)
|
||||
- `DEPLOYMENT.md` - 部署指南
|
||||
- `README_ARCHITECTURE.md` - 架构说明
|
||||
- `PROJECT_SUMMARY.md` - 项目总结
|
||||
|
|
|
|||
173
backend/api/routes/account.py
Normal file
173
backend/api/routes/account.py
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
"""
|
||||
账户实时数据API - 从币安API获取实时账户和订单数据
|
||||
"""
|
||||
from fastapi import APIRouter, HTTPException
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
project_root = Path(__file__).parent.parent.parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
sys.path.insert(0, str(project_root / 'backend'))
|
||||
sys.path.insert(0, str(project_root / 'trading_system'))
|
||||
|
||||
from database.models import TradingConfig
|
||||
from fastapi import HTTPException
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
async def get_realtime_account_data():
|
||||
"""从币安API实时获取账户数据"""
|
||||
try:
|
||||
# 从数据库读取API密钥
|
||||
api_key = TradingConfig.get_value('BINANCE_API_KEY')
|
||||
api_secret = TradingConfig.get_value('BINANCE_API_SECRET')
|
||||
use_testnet = TradingConfig.get_value('USE_TESTNET', False)
|
||||
|
||||
if not api_key or not api_secret:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="API密钥未配置,请在配置界面设置BINANCE_API_KEY和BINANCE_API_SECRET"
|
||||
)
|
||||
|
||||
# 导入交易系统的BinanceClient
|
||||
try:
|
||||
from binance_client import BinanceClient
|
||||
except ImportError:
|
||||
# 如果直接导入失败,尝试从trading_system导入
|
||||
trading_system_path = project_root / 'trading_system'
|
||||
sys.path.insert(0, str(trading_system_path))
|
||||
from binance_client import BinanceClient
|
||||
|
||||
# 创建客户端
|
||||
client = BinanceClient(
|
||||
api_key=api_key,
|
||||
api_secret=api_secret,
|
||||
testnet=use_testnet
|
||||
)
|
||||
|
||||
await client.connect()
|
||||
|
||||
# 获取账户余额
|
||||
balance = await client.get_account_balance()
|
||||
|
||||
# 获取持仓
|
||||
positions = await client.get_open_positions()
|
||||
|
||||
# 计算总仓位价值和总盈亏
|
||||
total_position_value = 0
|
||||
total_pnl = 0
|
||||
open_positions_count = 0
|
||||
|
||||
for pos in positions:
|
||||
position_amt = float(pos.get('positionAmt', 0))
|
||||
if position_amt == 0:
|
||||
continue
|
||||
|
||||
entry_price = float(pos.get('entryPrice', 0))
|
||||
mark_price = float(pos.get('markPrice', 0))
|
||||
unrealized_pnl = float(pos.get('unRealizedProfit', 0))
|
||||
|
||||
if mark_price == 0:
|
||||
# 如果没有标记价格,使用入场价
|
||||
mark_price = entry_price
|
||||
|
||||
position_value = abs(position_amt * mark_price)
|
||||
total_position_value += position_value
|
||||
total_pnl += unrealized_pnl
|
||||
open_positions_count += 1
|
||||
|
||||
await client.disconnect()
|
||||
|
||||
return {
|
||||
"total_balance": balance.get('total', 0),
|
||||
"available_balance": balance.get('available', 0),
|
||||
"total_position_value": total_position_value,
|
||||
"total_pnl": total_pnl,
|
||||
"open_positions": open_positions_count
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"获取账户数据失败: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"获取账户数据失败: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/realtime")
|
||||
async def get_realtime_account():
|
||||
"""获取实时账户数据"""
|
||||
return await get_realtime_account_data()
|
||||
|
||||
|
||||
@router.get("/positions")
|
||||
async def get_realtime_positions():
|
||||
"""获取实时持仓数据"""
|
||||
try:
|
||||
# 从数据库读取API密钥
|
||||
api_key = TradingConfig.get_value('BINANCE_API_KEY')
|
||||
api_secret = TradingConfig.get_value('BINANCE_API_SECRET')
|
||||
use_testnet = TradingConfig.get_value('USE_TESTNET', False)
|
||||
|
||||
if not api_key or not api_secret:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="API密钥未配置"
|
||||
)
|
||||
|
||||
# 导入BinanceClient
|
||||
try:
|
||||
from binance_client import BinanceClient
|
||||
except ImportError:
|
||||
trading_system_path = project_root / 'trading_system'
|
||||
sys.path.insert(0, str(trading_system_path))
|
||||
from binance_client import BinanceClient
|
||||
|
||||
client = BinanceClient(
|
||||
api_key=api_key,
|
||||
api_secret=api_secret,
|
||||
testnet=use_testnet
|
||||
)
|
||||
|
||||
await client.connect()
|
||||
positions = await client.get_open_positions()
|
||||
await client.disconnect()
|
||||
|
||||
# 格式化持仓数据
|
||||
formatted_positions = []
|
||||
for pos in positions:
|
||||
position_amt = float(pos.get('positionAmt', 0))
|
||||
if position_amt == 0:
|
||||
continue
|
||||
|
||||
entry_price = float(pos.get('entryPrice', 0))
|
||||
mark_price = float(pos.get('markPrice', 0))
|
||||
unrealized_pnl = float(pos.get('unRealizedProfit', 0))
|
||||
|
||||
if mark_price == 0:
|
||||
mark_price = entry_price
|
||||
|
||||
position_value = abs(position_amt * mark_price)
|
||||
pnl_percent = 0
|
||||
if entry_price > 0 and position_value > 0:
|
||||
pnl_percent = (unrealized_pnl / position_value) * 100
|
||||
|
||||
formatted_positions.append({
|
||||
"symbol": pos.get('symbol'),
|
||||
"side": "BUY" if position_amt > 0 else "SELL",
|
||||
"quantity": abs(position_amt),
|
||||
"entry_price": entry_price,
|
||||
"mark_price": mark_price,
|
||||
"pnl": unrealized_pnl,
|
||||
"pnl_percent": pnl_percent,
|
||||
"leverage": int(pos.get('leverage', 1))
|
||||
})
|
||||
|
||||
return formatted_positions
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"获取持仓数据失败: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"获取持仓数据失败: {str(e)}")
|
||||
|
|
@ -74,8 +74,37 @@ async def update_config(key: str, item: ConfigUpdate):
|
|||
category = item.category or existing['category']
|
||||
description = item.description or existing['description']
|
||||
|
||||
# 验证配置值
|
||||
if config_type == 'number':
|
||||
try:
|
||||
float(item.value)
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(status_code=400, detail=f"Invalid number value for {key}")
|
||||
elif config_type == 'boolean':
|
||||
if not isinstance(item.value, bool):
|
||||
# 尝试转换
|
||||
if isinstance(item.value, str):
|
||||
item.value = item.value.lower() in ('true', '1', 'yes', 'on')
|
||||
else:
|
||||
item.value = bool(item.value)
|
||||
|
||||
# 特殊验证:百分比配置应该在0-1之间
|
||||
if 'PERCENT' in key and config_type == 'number':
|
||||
if not (0 <= float(item.value) <= 1):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"{key} must be between 0 and 1 (0% to 100%)"
|
||||
)
|
||||
|
||||
# 更新配置
|
||||
TradingConfig.set(key, item.value, config_type, category, description)
|
||||
return {"message": "Config updated", "key": key}
|
||||
|
||||
return {
|
||||
"message": "配置已更新",
|
||||
"key": key,
|
||||
"value": item.value,
|
||||
"note": "交易系统将在下次扫描时自动使用新配置"
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
|
|
@ -86,7 +115,25 @@ async def update_config(key: str, item: ConfigUpdate):
|
|||
async def update_configs_batch(configs: list[ConfigItem]):
|
||||
"""批量更新配置"""
|
||||
try:
|
||||
updated_count = 0
|
||||
errors = []
|
||||
|
||||
for item in configs:
|
||||
try:
|
||||
# 验证配置值
|
||||
if item.type == 'number':
|
||||
try:
|
||||
float(item.value)
|
||||
except (ValueError, TypeError):
|
||||
errors.append(f"{item.key}: Invalid number value")
|
||||
continue
|
||||
|
||||
# 特殊验证:百分比配置
|
||||
if 'PERCENT' in item.key and item.type == 'number':
|
||||
if not (0 <= float(item.value) <= 1):
|
||||
errors.append(f"{item.key}: Must be between 0 and 1")
|
||||
continue
|
||||
|
||||
TradingConfig.set(
|
||||
item.key,
|
||||
item.value,
|
||||
|
|
@ -94,6 +141,22 @@ async def update_configs_batch(configs: list[ConfigItem]):
|
|||
item.category,
|
||||
item.description
|
||||
)
|
||||
return {"message": f"{len(configs)} configs updated"}
|
||||
updated_count += 1
|
||||
except Exception as e:
|
||||
errors.append(f"{item.key}: {str(e)}")
|
||||
|
||||
if errors:
|
||||
return {
|
||||
"message": f"部分配置更新成功: {updated_count}/{len(configs)}",
|
||||
"updated": updated_count,
|
||||
"errors": errors,
|
||||
"note": "交易系统将在下次扫描时自动使用新配置"
|
||||
}
|
||||
|
||||
return {
|
||||
"message": f"成功更新 {updated_count} 个配置",
|
||||
"updated": updated_count,
|
||||
"note": "交易系统将在下次扫描时自动使用新配置"
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
|
|
|||
|
|
@ -5,15 +5,44 @@
|
|||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.config-panel h2 {
|
||||
.config-header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.config-panel h2 {
|
||||
margin-bottom: 0.5rem;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.config-info {
|
||||
background: #e7f3ff;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #2196F3;
|
||||
}
|
||||
|
||||
.config-info p {
|
||||
margin: 0;
|
||||
color: #1976D2;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.message {
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 4px;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.message.success {
|
||||
|
|
@ -67,6 +96,19 @@
|
|||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.config-item input:focus,
|
||||
.config-item select:focus {
|
||||
outline: none;
|
||||
border-color: #2196F3;
|
||||
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);
|
||||
}
|
||||
|
||||
.config-item input.editing {
|
||||
border-color: #FF9800;
|
||||
background-color: #FFF8E1;
|
||||
}
|
||||
|
||||
.config-item input:disabled,
|
||||
|
|
@ -75,6 +117,13 @@
|
|||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.edit-hint {
|
||||
font-size: 0.75rem;
|
||||
color: #FF9800;
|
||||
margin-left: 0.5rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.config-item .description {
|
||||
font-size: 0.85rem;
|
||||
color: #888;
|
||||
|
|
|
|||
|
|
@ -28,16 +28,23 @@ const ConfigPanel = () => {
|
|||
setSaving(true)
|
||||
setMessage('')
|
||||
try {
|
||||
await api.updateConfig(key, {
|
||||
const response = await api.updateConfig(key, {
|
||||
value,
|
||||
type,
|
||||
category
|
||||
})
|
||||
setMessage('配置已更新')
|
||||
setMessage(response.message || '配置已更新')
|
||||
if (response.note) {
|
||||
setTimeout(() => {
|
||||
setMessage(response.note)
|
||||
}, 2000)
|
||||
}
|
||||
// 重新加载配置
|
||||
await loadConfigs()
|
||||
} catch (error) {
|
||||
setMessage('更新失败: ' + error.message)
|
||||
const errorMsg = error.message || '更新失败'
|
||||
setMessage('更新失败: ' + errorMsg)
|
||||
console.error('Config update error:', error)
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
|
|
@ -55,8 +62,17 @@ const ConfigPanel = () => {
|
|||
|
||||
return (
|
||||
<div className="config-panel">
|
||||
<div className="config-header">
|
||||
<h2>交易配置</h2>
|
||||
{message && <div className={`message ${message.includes('失败') ? 'error' : 'success'}`}>{message}</div>}
|
||||
<div className="config-info">
|
||||
<p>修改配置后,交易系统将在下次扫描时自动使用新配置</p>
|
||||
</div>
|
||||
</div>
|
||||
{message && (
|
||||
<div className={`message ${message.includes('失败') || message.includes('错误') ? 'error' : 'success'}`}>
|
||||
{message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{Object.entries(configCategories).map(([category, label]) => (
|
||||
<section key={category} className="config-section">
|
||||
|
|
@ -82,33 +98,58 @@ const ConfigPanel = () => {
|
|||
|
||||
const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
||||
const [value, setValue] = useState(config.value)
|
||||
const [localValue, setLocalValue] = useState(config.value)
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setValue(config.value)
|
||||
setLocalValue(config.value)
|
||||
}, [config.value])
|
||||
|
||||
const handleChange = (newValue) => {
|
||||
setValue(newValue)
|
||||
setLocalValue(newValue)
|
||||
setIsEditing(true)
|
||||
}
|
||||
|
||||
const handleBlur = () => {
|
||||
setIsEditing(false)
|
||||
if (localValue !== value) {
|
||||
// 值发生变化,保存
|
||||
let finalValue = localValue
|
||||
if (config.type === 'number') {
|
||||
onUpdate(parseFloat(newValue) || 0)
|
||||
finalValue = parseFloat(localValue) || 0
|
||||
// 百分比配置需要转换
|
||||
if (label.includes('PERCENT')) {
|
||||
finalValue = finalValue / 100
|
||||
}
|
||||
} else if (config.type === 'boolean') {
|
||||
onUpdate(newValue === 'true' || newValue === true)
|
||||
} else {
|
||||
onUpdate(newValue)
|
||||
finalValue = localValue === 'true' || localValue === true
|
||||
}
|
||||
onUpdate(finalValue)
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyPress = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleBlur()
|
||||
}
|
||||
}
|
||||
|
||||
const displayValue = config.type === 'number' && label.includes('PERCENT')
|
||||
? (value * 100).toFixed(2)
|
||||
: value
|
||||
? (localValue * 100).toFixed(2)
|
||||
: localValue
|
||||
|
||||
if (config.type === 'boolean') {
|
||||
return (
|
||||
<div className="config-item">
|
||||
<label>{label}</label>
|
||||
<select
|
||||
value={value ? 'true' : 'false'}
|
||||
onChange={(e) => handleChange(e.target.value)}
|
||||
value={localValue ? 'true' : 'false'}
|
||||
onChange={(e) => {
|
||||
handleChange(e.target.value)
|
||||
// 布尔值立即保存
|
||||
onUpdate(e.target.value === 'true')
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<option value="true">是</option>
|
||||
|
|
@ -125,8 +166,12 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
|||
<div className="config-item">
|
||||
<label>{label}</label>
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) => handleChange(e.target.value)}
|
||||
value={localValue}
|
||||
onChange={(e) => {
|
||||
handleChange(e.target.value)
|
||||
// 下拉框立即保存
|
||||
onUpdate(e.target.value)
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
{options.map(opt => (
|
||||
|
|
@ -143,16 +188,20 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
|||
<label>{label}</label>
|
||||
<input
|
||||
type={config.type === 'number' ? 'number' : 'text'}
|
||||
value={label.includes('PERCENT') ? displayValue : value}
|
||||
value={label.includes('PERCENT') ? displayValue : localValue}
|
||||
onChange={(e) => {
|
||||
const newValue = config.type === 'number' && label.includes('PERCENT')
|
||||
? parseFloat(e.target.value) / 100
|
||||
: e.target.value
|
||||
handleChange(newValue)
|
||||
}}
|
||||
onBlur={handleBlur}
|
||||
onKeyPress={handleKeyPress}
|
||||
disabled={disabled}
|
||||
step={config.type === 'number' ? '0.01' : undefined}
|
||||
className={isEditing ? 'editing' : ''}
|
||||
/>
|
||||
{isEditing && <span className="edit-hint">按Enter保存</span>}
|
||||
{config.description && <span className="description">{config.description}</span>}
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ export const api = {
|
|||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || '更新配置失败');
|
||||
}
|
||||
return response.json();
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
# 交易系统依赖(已移至 trading_system/requirements.txt)
|
||||
# 如需运行交易系统,请安装:
|
||||
# pip install -r trading_system/requirements.txt
|
||||
|
||||
# 后端API依赖(已移至 backend/requirements.txt)
|
||||
# 如需运行后端API,请安装:
|
||||
# pip install -r backend/requirements.txt
|
||||
|
|
@ -49,8 +49,42 @@ python -m trading_system.main
|
|||
|
||||
配置文件:`config.py`
|
||||
|
||||
## 依赖
|
||||
## 安装依赖
|
||||
|
||||
### 方式1:使用安装脚本(推荐)
|
||||
|
||||
```bash
|
||||
cd trading_system
|
||||
./setup.sh
|
||||
```
|
||||
|
||||
### 方式2:手动创建虚拟环境
|
||||
|
||||
```bash
|
||||
# 在项目根目录创建虚拟环境
|
||||
cd ..
|
||||
python3 -m venv .venv
|
||||
|
||||
# 激活虚拟环境
|
||||
source .venv/bin/activate # Linux/Mac
|
||||
# 或 .venv\Scripts\activate # Windows
|
||||
|
||||
# 安装依赖
|
||||
cd trading_system
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 方式3:使用项目根目录的虚拟环境
|
||||
|
||||
如果项目根目录已有虚拟环境(与backend共享):
|
||||
|
||||
```bash
|
||||
# 激活虚拟环境
|
||||
source ../.venv/bin/activate # 从trading_system目录
|
||||
# 或 source .venv/bin/activate # 从项目根目录
|
||||
|
||||
# 安装依赖
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
**注意**:现代 Linux 系统(如 Ubuntu 22.04+)不允许直接在系统 Python 中安装包,必须使用虚拟环境。
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@ def _get_trading_config():
|
|||
}
|
||||
|
||||
# 币安API配置(优先从数据库,回退到环境变量和默认值)
|
||||
BINANCE_API_KEY: Optional[str] = _get_config_value('BINANCE_API_KEY', 'pMEXSgISMgpUIpGjyhikMXWQ7K7cCs1FFATyIvNIwWrUIQegoipVBskPUoUuvaVN')
|
||||
BINANCE_API_SECRET: Optional[str] = _get_config_value('BINANCE_API_SECRET', 'RklItVtBCjGV40mIquoSj78xlTGkdUxz0AFyTnsnuzSBfx776VG0S2Vw5BRLRRg2')
|
||||
BINANCE_API_KEY: Optional[str] = _get_config_value('BINANCE_API_KEY', '')
|
||||
BINANCE_API_SECRET: Optional[str] = _get_config_value('BINANCE_API_SECRET', '')
|
||||
USE_TESTNET: bool = _get_config_value('USE_TESTNET', False) if _get_config_value('USE_TESTNET') is not None else os.getenv('USE_TESTNET', 'False').lower() == 'true'
|
||||
|
||||
# 交易参数配置(优先从数据库读取,支持动态重载)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user