a
This commit is contained in:
parent
bd0dd6c336
commit
90f3d019ed
|
|
@ -544,3 +544,145 @@ async def close_position(symbol: str):
|
||||||
logger.error(f"错误类型: {type(e).__name__}")
|
logger.error(f"错误类型: {type(e).__name__}")
|
||||||
logger.error("=" * 60, exc_info=True)
|
logger.error("=" * 60, exc_info=True)
|
||||||
raise HTTPException(status_code=500, detail=error_msg)
|
raise HTTPException(status_code=500, detail=error_msg)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/positions/sync")
|
||||||
|
async def sync_positions():
|
||||||
|
"""同步币安实际持仓状态与数据库状态"""
|
||||||
|
try:
|
||||||
|
logger.info("=" * 60)
|
||||||
|
logger.info("收到持仓状态同步请求")
|
||||||
|
logger.info("=" * 60)
|
||||||
|
|
||||||
|
# 从数据库读取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:
|
||||||
|
error_msg = "API密钥未配置"
|
||||||
|
logger.warning(error_msg)
|
||||||
|
raise HTTPException(status_code=400, detail=error_msg)
|
||||||
|
|
||||||
|
# 导入必要的模块
|
||||||
|
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
|
||||||
|
|
||||||
|
# 导入数据库模型
|
||||||
|
from database.models import Trade
|
||||||
|
|
||||||
|
# 创建客户端
|
||||||
|
client = BinanceClient(
|
||||||
|
api_key=api_key,
|
||||||
|
api_secret=api_secret,
|
||||||
|
testnet=use_testnet
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("连接币安API...")
|
||||||
|
await client.connect()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. 获取币安实际持仓
|
||||||
|
binance_positions = await client.get_open_positions()
|
||||||
|
binance_symbols = {p['symbol'] for p in binance_positions if float(p.get('positionAmt', 0)) != 0}
|
||||||
|
logger.info(f"币安实际持仓: {len(binance_symbols)} 个")
|
||||||
|
if binance_symbols:
|
||||||
|
logger.info(f" 持仓列表: {', '.join(binance_symbols)}")
|
||||||
|
|
||||||
|
# 2. 获取数据库中状态为open的交易记录
|
||||||
|
db_open_trades = Trade.get_all(status='open')
|
||||||
|
db_open_symbols = {t['symbol'] for t in db_open_trades}
|
||||||
|
logger.info(f"数据库open状态: {len(db_open_symbols)} 个")
|
||||||
|
if db_open_symbols:
|
||||||
|
logger.info(f" 持仓列表: {', '.join(db_open_symbols)}")
|
||||||
|
|
||||||
|
# 3. 找出在数据库中open但在币安已不存在的持仓(需要更新为closed)
|
||||||
|
missing_in_binance = db_open_symbols - binance_symbols
|
||||||
|
updated_count = 0
|
||||||
|
|
||||||
|
if missing_in_binance:
|
||||||
|
logger.info(f"发现 {len(missing_in_binance)} 个持仓在数据库中是open但币安已不存在: {', '.join(missing_in_binance)}")
|
||||||
|
|
||||||
|
for symbol in missing_in_binance:
|
||||||
|
try:
|
||||||
|
# 获取该交易对的所有open记录
|
||||||
|
open_trades = Trade.get_by_symbol(symbol, status='open')
|
||||||
|
|
||||||
|
for trade in open_trades:
|
||||||
|
trade_id = trade['id']
|
||||||
|
entry_price = float(trade['entry_price'])
|
||||||
|
quantity = float(trade['quantity'])
|
||||||
|
|
||||||
|
# 获取当前价格作为平仓价格
|
||||||
|
ticker = await client.get_ticker_24h(symbol)
|
||||||
|
exit_price = float(ticker['price']) if ticker else entry_price
|
||||||
|
|
||||||
|
# 计算盈亏
|
||||||
|
if trade['side'] == 'BUY':
|
||||||
|
pnl = (exit_price - entry_price) * quantity
|
||||||
|
else:
|
||||||
|
pnl = (entry_price - exit_price) * quantity
|
||||||
|
|
||||||
|
# 计算基于保证金的盈亏百分比
|
||||||
|
leverage = float(trade.get('leverage', 10))
|
||||||
|
entry_value = entry_price * quantity
|
||||||
|
margin = entry_value / leverage if leverage > 0 else entry_value
|
||||||
|
pnl_percent_margin = (pnl / margin * 100) if margin > 0 else 0
|
||||||
|
|
||||||
|
# 更新数据库记录
|
||||||
|
Trade.update_exit(
|
||||||
|
trade_id=trade_id,
|
||||||
|
exit_price=exit_price,
|
||||||
|
exit_reason='sync', # 标记为同步平仓
|
||||||
|
pnl=pnl,
|
||||||
|
pnl_percent=pnl_percent_margin, # 使用基于保证金的盈亏百分比
|
||||||
|
exit_order_id=None
|
||||||
|
)
|
||||||
|
updated_count += 1
|
||||||
|
logger.info(
|
||||||
|
f"✓ {symbol} 已更新为closed (ID: {trade_id}, "
|
||||||
|
f"盈亏: {pnl:.2f} USDT, {pnl_percent_margin:.2f}% of margin)"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ {symbol} 更新失败: {e}")
|
||||||
|
import traceback
|
||||||
|
logger.error(f" 错误详情:\n{traceback.format_exc()}")
|
||||||
|
else:
|
||||||
|
logger.info("✓ 数据库与币安状态一致,无需更新")
|
||||||
|
|
||||||
|
# 4. 检查币安有但数据库没有记录的持仓
|
||||||
|
missing_in_db = binance_symbols - db_open_symbols
|
||||||
|
if missing_in_db:
|
||||||
|
logger.info(f"发现 {len(missing_in_db)} 个持仓在币安存在但数据库中没有记录: {', '.join(missing_in_db)}")
|
||||||
|
logger.info(" 这些持仓可能是手动开仓的,建议手动处理")
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"message": "持仓状态同步完成",
|
||||||
|
"binance_positions": len(binance_symbols),
|
||||||
|
"db_open_positions": len(db_open_symbols),
|
||||||
|
"updated_to_closed": updated_count,
|
||||||
|
"missing_in_binance": list(missing_in_binance),
|
||||||
|
"missing_in_db": list(missing_in_db)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("=" * 60)
|
||||||
|
logger.info("持仓状态同步完成!")
|
||||||
|
logger.info(f"结果: {result}")
|
||||||
|
logger.info("=" * 60)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
finally:
|
||||||
|
await client.disconnect()
|
||||||
|
logger.info("✓ 已断开币安API连接")
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"同步持仓状态失败: {str(e)}"
|
||||||
|
logger.error(error_msg, exc_info=True)
|
||||||
|
raise HTTPException(status_code=500, detail=error_msg)
|
||||||
|
|
|
||||||
|
|
@ -119,8 +119,10 @@ class ConfigManager:
|
||||||
'TOP_N_SYMBOLS': self.get('TOP_N_SYMBOLS', 10),
|
'TOP_N_SYMBOLS': self.get('TOP_N_SYMBOLS', 10),
|
||||||
|
|
||||||
# 风险控制
|
# 风险控制
|
||||||
'STOP_LOSS_PERCENT': self.get('STOP_LOSS_PERCENT', 0.03),
|
'STOP_LOSS_PERCENT': self.get('STOP_LOSS_PERCENT', 0.08), # 默认8%(更宽松,避免被正常波动触发)
|
||||||
'TAKE_PROFIT_PERCENT': self.get('TAKE_PROFIT_PERCENT', 0.05),
|
'TAKE_PROFIT_PERCENT': self.get('TAKE_PROFIT_PERCENT', 0.15), # 默认15%(给趋势更多空间)
|
||||||
|
'MIN_STOP_LOSS_PRICE_PCT': self.get('MIN_STOP_LOSS_PRICE_PCT', 0.02), # 默认2%
|
||||||
|
'MIN_TAKE_PROFIT_PRICE_PCT': self.get('MIN_TAKE_PROFIT_PRICE_PCT', 0.03), # 默认3%
|
||||||
|
|
||||||
# 市场扫描(1小时主周期)
|
# 市场扫描(1小时主周期)
|
||||||
'SCAN_INTERVAL': self.get('SCAN_INTERVAL', 3600), # 1小时
|
'SCAN_INTERVAL': self.get('SCAN_INTERVAL', 3600), # 1小时
|
||||||
|
|
|
||||||
|
|
@ -141,8 +141,10 @@ INSERT INTO `trading_config` (`config_key`, `config_value`, `config_type`, `cate
|
||||||
('MAX_SCAN_SYMBOLS', '500', 'number', 'scan', '扫描的最大交易对数量(0表示扫描所有,建议100-500)'),
|
('MAX_SCAN_SYMBOLS', '500', 'number', 'scan', '扫描的最大交易对数量(0表示扫描所有,建议100-500)'),
|
||||||
|
|
||||||
-- 风险控制
|
-- 风险控制
|
||||||
('STOP_LOSS_PERCENT', '0.03', 'number', 'risk', '止损:3%'),
|
('STOP_LOSS_PERCENT', '0.08', 'number', 'risk', '止损:8%(相对于保证金,更宽松避免被正常波动触发)'),
|
||||||
('TAKE_PROFIT_PERCENT', '0.05', 'number', 'risk', '止盈:5%'),
|
('TAKE_PROFIT_PERCENT', '0.15', 'number', 'risk', '止盈:15%(相对于保证金,给趋势更多空间)'),
|
||||||
|
('MIN_STOP_LOSS_PRICE_PCT', '0.02', 'number', 'risk', '最小止损价格变动:2%(防止止损过紧)'),
|
||||||
|
('MIN_TAKE_PROFIT_PRICE_PCT', '0.03', 'number', 'risk', '最小止盈价格变动:3%(防止止盈过紧)'),
|
||||||
|
|
||||||
-- 市场扫描(1小时主周期)
|
-- 市场扫描(1小时主周期)
|
||||||
('SCAN_INTERVAL', '3600', 'number', 'scan', '扫描间隔:1小时(秒)'),
|
('SCAN_INTERVAL', '3600', 'number', 'scan', '扫描间隔:1小时(秒)'),
|
||||||
|
|
|
||||||
|
|
@ -15,53 +15,59 @@ const ConfigGuide = () => {
|
||||||
<h2>一、预设方案说明</h2>
|
<h2>一、预设方案说明</h2>
|
||||||
|
|
||||||
<div className="preset-card">
|
<div className="preset-card">
|
||||||
<h3>方案1:保守配置(默认)</h3>
|
<h3>方案1:保守配置</h3>
|
||||||
<p className="preset-desc">适合新手或稳健型交易者,风险较低,交易频率适中</p>
|
<p className="preset-desc">适合新手或稳健型交易者,风险较低,止损止盈较宽松,避免被正常波动触发</p>
|
||||||
<div className="preset-params">
|
<div className="preset-params">
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>扫描间隔</strong>: 3600秒(1小时)</li>
|
<li><strong>扫描间隔</strong>: 3600秒(1小时)</li>
|
||||||
<li><strong>最小涨跌幅</strong>: 2.0%</li>
|
<li><strong>最小涨跌幅</strong>: 2.0%</li>
|
||||||
<li><strong>信号强度</strong>: 5/10</li>
|
<li><strong>信号强度</strong>: 5/10</li>
|
||||||
<li><strong>处理交易对</strong>: 10个</li>
|
<li><strong>处理交易对</strong>: 10个</li>
|
||||||
|
<li><strong>止损</strong>: 10% of margin(最小2%价格变动)</li>
|
||||||
|
<li><strong>止盈</strong>: 20% of margin(最小3%价格变动)</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="preset-effect">
|
<div className="preset-effect">
|
||||||
<strong>效果:</strong>每小时扫描一次,只捕捉2%以上的波动,信号质量高,胜率较高但交易机会较少
|
<strong>效果:</strong>每小时扫描一次,只捕捉2%以上的波动,止损止盈宽松,避免被正常波动触发,适合稳健交易
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="preset-card">
|
<div className="preset-card">
|
||||||
<h3>方案2:平衡配置(推荐)</h3>
|
<h3>方案2:平衡配置(推荐)</h3>
|
||||||
<p className="preset-desc">平衡交易频率和信号质量,适合大多数交易者</p>
|
<p className="preset-desc">平衡交易频率和信号质量,止损止盈适中,适合大多数交易者</p>
|
||||||
<div className="preset-params">
|
<div className="preset-params">
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>扫描间隔</strong>: 600秒(10分钟)</li>
|
<li><strong>扫描间隔</strong>: 600秒(10分钟)</li>
|
||||||
<li><strong>最小涨跌幅</strong>: 1.5%</li>
|
<li><strong>最小涨跌幅</strong>: 1.5%</li>
|
||||||
<li><strong>信号强度</strong>: 4/10</li>
|
<li><strong>信号强度</strong>: 4/10</li>
|
||||||
<li><strong>处理交易对</strong>: 12个</li>
|
<li><strong>处理交易对</strong>: 12个</li>
|
||||||
|
<li><strong>止损</strong>: 8% of margin(最小2%价格变动)</li>
|
||||||
|
<li><strong>止盈</strong>: 15% of margin(最小3%价格变动)</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="preset-effect">
|
<div className="preset-effect">
|
||||||
<strong>效果:</strong>10分钟扫描一次,捕捉1.5%以上的波动,交易机会增加,信号质量仍然较高
|
<strong>效果:</strong>10分钟扫描一次,捕捉1.5%以上的波动,止损止盈适中,平衡风险与收益,推荐使用
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="preset-card">
|
<div className="preset-card">
|
||||||
<h3>方案3:激进高频配置</h3>
|
<h3>方案3:激进高频配置</h3>
|
||||||
<p className="preset-desc">适合晚间波动大时使用,交易频率高,需要密切监控</p>
|
<p className="preset-desc">适合晚间波动大时使用,交易频率高,止损止盈较紧,快速止盈止损</p>
|
||||||
<div className="preset-params">
|
<div className="preset-params">
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>扫描间隔</strong>: 300秒(5分钟)</li>
|
<li><strong>扫描间隔</strong>: 300秒(5分钟)</li>
|
||||||
<li><strong>最小涨跌幅</strong>: 1.0%</li>
|
<li><strong>最小涨跌幅</strong>: 1.0%</li>
|
||||||
<li><strong>信号强度</strong>: 3/10</li>
|
<li><strong>信号强度</strong>: 3/10</li>
|
||||||
<li><strong>处理交易对</strong>: 20个</li>
|
<li><strong>处理交易对</strong>: 18个</li>
|
||||||
|
<li><strong>止损</strong>: 5% of margin(最小1.5%价格变动)</li>
|
||||||
|
<li><strong>止盈</strong>: 10% of margin(最小2%价格变动)</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="preset-effect">
|
<div className="preset-effect">
|
||||||
<strong>效果:</strong>5分钟扫描一次,捕捉1%以上的波动,交易机会大幅增加,但需要监控胜率和手续费
|
<strong>效果:</strong>5分钟扫描一次,捕捉1%以上的波动,止损止盈较紧,快速锁定利润或止损,适合高频交易
|
||||||
</div>
|
</div>
|
||||||
<div className="preset-warning">
|
<div className="preset-warning">
|
||||||
⚠️ <strong>风险提示:</strong>高频交易会增加手续费成本,建议在波动大的时段使用,并密切监控胜率
|
⚠️ <strong>风险提示:</strong>高频交易会增加手续费成本,止损止盈较紧可能被正常波动触发,建议在波动大的时段使用,并密切监控胜率
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -10,42 +10,54 @@ const ConfigPanel = () => {
|
||||||
const [message, setMessage] = useState('')
|
const [message, setMessage] = useState('')
|
||||||
|
|
||||||
// 预设方案配置
|
// 预设方案配置
|
||||||
// 注意:百分比配置使用整数形式(如2.0表示2%),在应用时会转换为小数(0.02)
|
// 注意:百分比配置使用整数形式(如8.0表示8%),在应用时会转换为小数(0.08)
|
||||||
const presets = {
|
const presets = {
|
||||||
conservative: {
|
conservative: {
|
||||||
name: '保守配置',
|
name: '保守配置',
|
||||||
desc: '适合新手,风险较低,交易频率适中',
|
desc: '适合新手,风险较低,止损止盈较宽松,避免被正常波动触发',
|
||||||
configs: {
|
configs: {
|
||||||
SCAN_INTERVAL: 3600,
|
SCAN_INTERVAL: 3600,
|
||||||
MIN_CHANGE_PERCENT: 2.0, // 2%
|
MIN_CHANGE_PERCENT: 2.0, // 2%
|
||||||
MIN_SIGNAL_STRENGTH: 5,
|
MIN_SIGNAL_STRENGTH: 5,
|
||||||
TOP_N_SYMBOLS: 10,
|
TOP_N_SYMBOLS: 10,
|
||||||
MAX_SCAN_SYMBOLS: 150,
|
MAX_SCAN_SYMBOLS: 150,
|
||||||
MIN_VOLATILITY: 0.02 // 保持小数形式(波动率)
|
MIN_VOLATILITY: 0.02, // 保持小数形式(波动率)
|
||||||
|
STOP_LOSS_PERCENT: 10.0, // 10%(相对于保证金,更宽松)
|
||||||
|
TAKE_PROFIT_PERCENT: 20.0, // 20%(相对于保证金,给趋势更多空间)
|
||||||
|
MIN_STOP_LOSS_PRICE_PCT: 2.0, // 2%最小价格变动保护
|
||||||
|
MIN_TAKE_PROFIT_PRICE_PCT: 3.0 // 3%最小价格变动保护
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
balanced: {
|
balanced: {
|
||||||
name: '平衡配置',
|
name: '平衡配置',
|
||||||
desc: '推荐使用,平衡频率和质量',
|
desc: '推荐使用,平衡频率和质量,止损止盈适中',
|
||||||
configs: {
|
configs: {
|
||||||
SCAN_INTERVAL: 600,
|
SCAN_INTERVAL: 600,
|
||||||
MIN_CHANGE_PERCENT: 1.5, // 1.5%
|
MIN_CHANGE_PERCENT: 1.5, // 1.5%
|
||||||
MIN_SIGNAL_STRENGTH: 4,
|
MIN_SIGNAL_STRENGTH: 4,
|
||||||
TOP_N_SYMBOLS: 12,
|
TOP_N_SYMBOLS: 12,
|
||||||
MAX_SCAN_SYMBOLS: 250,
|
MAX_SCAN_SYMBOLS: 250,
|
||||||
MIN_VOLATILITY: 0.018 // 保持小数形式(波动率)
|
MIN_VOLATILITY: 0.018, // 保持小数形式(波动率)
|
||||||
|
STOP_LOSS_PERCENT: 8.0, // 8%(相对于保证金,默认值)
|
||||||
|
TAKE_PROFIT_PERCENT: 15.0, // 15%(相对于保证金,默认值)
|
||||||
|
MIN_STOP_LOSS_PRICE_PCT: 2.0, // 2%最小价格变动保护
|
||||||
|
MIN_TAKE_PROFIT_PRICE_PCT: 3.0 // 3%最小价格变动保护
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
aggressive: {
|
aggressive: {
|
||||||
name: '激进高频',
|
name: '激进高频',
|
||||||
desc: '晚间波动大时使用,交易频率高',
|
desc: '晚间波动大时使用,交易频率高,止损止盈较紧',
|
||||||
configs: {
|
configs: {
|
||||||
SCAN_INTERVAL: 300,
|
SCAN_INTERVAL: 300,
|
||||||
MIN_CHANGE_PERCENT: 1.0, // 1%
|
MIN_CHANGE_PERCENT: 1.0, // 1%
|
||||||
MIN_SIGNAL_STRENGTH: 3,
|
MIN_SIGNAL_STRENGTH: 3,
|
||||||
TOP_N_SYMBOLS: 18,
|
TOP_N_SYMBOLS: 18,
|
||||||
MAX_SCAN_SYMBOLS: 350,
|
MAX_SCAN_SYMBOLS: 350,
|
||||||
MIN_VOLATILITY: 0.015 // 保持小数形式(波动率)
|
MIN_VOLATILITY: 0.015, // 保持小数形式(波动率)
|
||||||
|
STOP_LOSS_PERCENT: 5.0, // 5%(相对于保证金,较紧)
|
||||||
|
TAKE_PROFIT_PERCENT: 10.0, // 10%(相对于保证金,快速止盈)
|
||||||
|
MIN_STOP_LOSS_PRICE_PCT: 1.5, // 1.5%最小价格变动保护
|
||||||
|
MIN_TAKE_PROFIT_PRICE_PCT: 2.0 // 2%最小价格变动保护
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +119,7 @@ const ConfigPanel = () => {
|
||||||
|
|
||||||
// 获取当前值(处理百分比转换)
|
// 获取当前值(处理百分比转换)
|
||||||
let currentValue = currentConfig.value
|
let currentValue = currentConfig.value
|
||||||
if (key.includes('PERCENT')) {
|
if (key.includes('PERCENT') || key.includes('PCT')) {
|
||||||
currentValue = currentValue * 100
|
currentValue = currentValue * 100
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,10 +152,37 @@ const ConfigPanel = () => {
|
||||||
try {
|
try {
|
||||||
const configItems = Object.entries(preset.configs).map(([key, value]) => {
|
const configItems = Object.entries(preset.configs).map(([key, value]) => {
|
||||||
const config = configs[key]
|
const config = configs[key]
|
||||||
if (!config) return null
|
if (!config) {
|
||||||
|
// 如果配置项不存在,尝试创建(用于新增的配置项)
|
||||||
|
// 根据key判断类型和分类
|
||||||
|
let type = 'number'
|
||||||
|
let category = 'risk'
|
||||||
|
if (key.includes('PERCENT') || key.includes('PCT')) {
|
||||||
|
type = 'number'
|
||||||
|
if (key.includes('STOP_LOSS') || key.includes('TAKE_PROFIT')) {
|
||||||
|
category = 'risk'
|
||||||
|
} else {
|
||||||
|
category = 'scan'
|
||||||
|
}
|
||||||
|
} else if (key === 'MIN_VOLATILITY') {
|
||||||
|
type = 'number'
|
||||||
|
category = 'scan'
|
||||||
|
} else if (typeof value === 'number') {
|
||||||
|
type = 'number'
|
||||||
|
category = 'scan'
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
value: (key.includes('PERCENT') || key.includes('PCT')) ? value / 100 : value,
|
||||||
|
type,
|
||||||
|
category,
|
||||||
|
description: `预设方案配置项:${key}`
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
value: key.includes('PERCENT') ? value / 100 : value,
|
value: (key.includes('PERCENT') || key.includes('PCT')) ? value / 100 : value,
|
||||||
type: config.type,
|
type: config.type,
|
||||||
category: config.category,
|
category: config.category,
|
||||||
description: config.description
|
description: config.description
|
||||||
|
|
@ -585,8 +624,10 @@ const getConfigDetail = (key) => {
|
||||||
'MIN_POSITION_PERCENT': '单笔最小仓位(账户余额的百分比,如0.01表示1%)。单笔交易允许的最小仓位大小,避免交易过小的仓位,减少手续费影响。建议:1-2%。',
|
'MIN_POSITION_PERCENT': '单笔最小仓位(账户余额的百分比,如0.01表示1%)。单笔交易允许的最小仓位大小,避免交易过小的仓位,减少手续费影响。建议:1-2%。',
|
||||||
|
|
||||||
// 风险控制参数
|
// 风险控制参数
|
||||||
'STOP_LOSS_PERCENT': '止损百分比(如0.03表示3%)。当亏损达到此百分比时自动平仓止损,限制单笔交易的最大亏损。值越小止损更严格,单笔损失更小但可能被正常波动触发。值越大允许更大的回撤,但单笔损失可能较大。建议:保守策略3-5%,平衡策略2-3%,激进策略2-3%。注意:止损应该小于止盈,建议盈亏比至少1:1.5。',
|
'STOP_LOSS_PERCENT': '止损百分比(如0.08表示8%,相对于保证金)。当亏损达到此百分比时自动平仓止损,限制单笔交易的最大亏损。值越小止损更严格,单笔损失更小但可能被正常波动触发。值越大允许更大的回撤,但单笔损失可能较大。建议:保守策略10-15%,平衡策略8-10%,激进策略5-8%。注意:止损应该小于止盈,建议盈亏比至少1:1.5。系统会结合最小价格变动保护,取更宽松的一个。',
|
||||||
'TAKE_PROFIT_PERCENT': '止盈百分比(如0.05表示5%)。当盈利达到此百分比时自动平仓止盈,锁定利润。值越大目标利润更高,但可能错过及时止盈的机会,持仓时间更长。值越小能更快锁定利润,但可能错过更大的趋势。建议:保守策略5-8%,平衡策略5-6%,激进策略3-5%。注意:应该大于止损,建议盈亏比至少1:1.5。',
|
'TAKE_PROFIT_PERCENT': '止盈百分比(如0.15表示15%,相对于保证金)。当盈利达到此百分比时自动平仓止盈,锁定利润。值越大目标利润更高,但可能错过及时止盈的机会,持仓时间更长。值越小能更快锁定利润,但可能错过更大的趋势。建议:保守策略20-30%,平衡策略15-20%,激进策略10-15%。注意:应该大于止损,建议盈亏比至少1:1.5。系统会结合最小价格变动保护,取更宽松的一个。',
|
||||||
|
'MIN_STOP_LOSS_PRICE_PCT': '最小止损价格变动百分比(如0.02表示2%)。防止止损过紧,即使基于保证金的止损更紧,也会使用至少此百分比的价格变动。建议:保守策略2-3%,平衡策略2%,激进策略1.5-2%。',
|
||||||
|
'MIN_TAKE_PROFIT_PRICE_PCT': '最小止盈价格变动百分比(如0.03表示3%)。防止止盈过紧,即使基于保证金的止盈更紧,也会使用至少此百分比的价格变动。建议:保守策略3-4%,平衡策略3%,激进策略2-3%。',
|
||||||
|
|
||||||
// 策略参数
|
// 策略参数
|
||||||
'LEVERAGE': '交易杠杆倍数。放大资金利用率,同时放大收益和风险。杠杆越高,相同仓位下需要的保证金越少,但风险越大。建议:保守策略5-10倍,平衡策略10倍,激进策略10-15倍。注意:高杠杆会增加爆仓风险,请谨慎使用。',
|
'LEVERAGE': '交易杠杆倍数。放大资金利用率,同时放大收益和风险。杠杆越高,相同仓位下需要的保证金越少,但风险越大。建议:保守策略5-10倍,平衡策略10倍,激进策略10-15倍。注意:高杠杆会增加爆仓风险,请谨慎使用。',
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,46 @@ const StatsDashboard = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSyncPositions = async () => {
|
||||||
|
if (!window.confirm('确定要同步持仓状态吗?这将检查币安实际持仓并更新数据库状态。')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setMessage('正在同步持仓状态...')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await api.syncPositions()
|
||||||
|
console.log('同步结果:', result)
|
||||||
|
|
||||||
|
let message = result.message || '同步完成'
|
||||||
|
if (result.updated_to_closed > 0) {
|
||||||
|
message += `,已更新 ${result.updated_to_closed} 条记录为已平仓`
|
||||||
|
}
|
||||||
|
if (result.missing_in_db && result.missing_in_db.length > 0) {
|
||||||
|
message += `,发现 ${result.missing_in_db.length} 个币安持仓在数据库中没有记录`
|
||||||
|
}
|
||||||
|
|
||||||
|
setMessage(message)
|
||||||
|
|
||||||
|
// 立即刷新数据
|
||||||
|
await loadDashboard()
|
||||||
|
|
||||||
|
// 5秒后清除消息
|
||||||
|
setTimeout(() => {
|
||||||
|
setMessage('')
|
||||||
|
}, 5000)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Sync positions error:', error)
|
||||||
|
const errorMessage = error.message || error.toString() || '同步失败,请检查网络连接或后端服务'
|
||||||
|
setMessage(`同步失败: ${errorMessage}`)
|
||||||
|
|
||||||
|
// 错误消息5秒后清除
|
||||||
|
setTimeout(() => {
|
||||||
|
setMessage('')
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (loading) return <div className="loading">加载中...</div>
|
if (loading) return <div className="loading">加载中...</div>
|
||||||
|
|
||||||
const account = dashboardData?.account
|
const account = dashboardData?.account
|
||||||
|
|
@ -86,7 +126,24 @@ const StatsDashboard = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard">
|
<div className="dashboard">
|
||||||
<h2>仪表板</h2>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
|
||||||
|
<h2>仪表板</h2>
|
||||||
|
<button
|
||||||
|
onClick={handleSyncPositions}
|
||||||
|
style={{
|
||||||
|
padding: '8px 16px',
|
||||||
|
backgroundColor: '#007bff',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}
|
||||||
|
title="同步币安实际持仓状态与数据库状态"
|
||||||
|
>
|
||||||
|
同步持仓状态
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{message && (
|
{message && (
|
||||||
<div className={`message ${message.includes('失败') || message.includes('错误') ? 'error' : 'success'}`}>
|
<div className={`message ${message.includes('失败') || message.includes('错误') ? 'error' : 'success'}`}>
|
||||||
|
|
@ -205,9 +262,9 @@ const StatsDashboard = () => {
|
||||||
const stopLossConfig = configSource?.STOP_LOSS_PERCENT
|
const stopLossConfig = configSource?.STOP_LOSS_PERCENT
|
||||||
const takeProfitConfig = configSource?.TAKE_PROFIT_PERCENT
|
const takeProfitConfig = configSource?.TAKE_PROFIT_PERCENT
|
||||||
|
|
||||||
// 配置值是小数形式(0.03表示3%),相对于保证金
|
// 配置值是小数形式(0.08表示8%),相对于保证金
|
||||||
let stopLossPercentMargin = 0.03 // 默认3%(相对于保证金)
|
let stopLossPercentMargin = 0.08 // 默认8%(相对于保证金,更宽松)
|
||||||
let takeProfitPercentMargin = 0.05 // 默认5%(相对于保证金)
|
let takeProfitPercentMargin = 0.15 // 默认15%(相对于保证金,给趋势更多空间)
|
||||||
|
|
||||||
if (stopLossConfig) {
|
if (stopLossConfig) {
|
||||||
const configValue = stopLossConfig.value
|
const configValue = stopLossConfig.value
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,21 @@ export const api = {
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 同步持仓状态
|
||||||
|
syncPositions: async () => {
|
||||||
|
const response = await fetch(buildUrl('/api/account/positions/sync'), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json().catch(() => ({ detail: '同步失败' }));
|
||||||
|
throw new Error(error.detail || '同步失败');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
|
||||||
// 交易推荐
|
// 交易推荐
|
||||||
getRecommendations: async (params = {}) => {
|
getRecommendations: async (params = {}) => {
|
||||||
// 默认使用实时推荐
|
// 默认使用实时推荐
|
||||||
|
|
|
||||||
|
|
@ -168,10 +168,10 @@ def _get_trading_config():
|
||||||
'MIN_CHANGE_PERCENT': 0.5, # 降低到0.5%以获取更多推荐(推荐系统可以更宽松)
|
'MIN_CHANGE_PERCENT': 0.5, # 降低到0.5%以获取更多推荐(推荐系统可以更宽松)
|
||||||
'TOP_N_SYMBOLS': 50, # 每次扫描后处理的交易对数量(增加到50以获取更多推荐)
|
'TOP_N_SYMBOLS': 50, # 每次扫描后处理的交易对数量(增加到50以获取更多推荐)
|
||||||
'MAX_SCAN_SYMBOLS': 500, # 扫描的最大交易对数量(0表示扫描所有)
|
'MAX_SCAN_SYMBOLS': 500, # 扫描的最大交易对数量(0表示扫描所有)
|
||||||
'STOP_LOSS_PERCENT': 0.03, # 止损百分比(相对于保证金),默认3%
|
'STOP_LOSS_PERCENT': 0.08, # 止损百分比(相对于保证金),默认8%(更宽松,避免被正常波动触发)
|
||||||
'TAKE_PROFIT_PERCENT': 0.05, # 止盈百分比(相对于保证金),默认5%
|
'TAKE_PROFIT_PERCENT': 0.15, # 止盈百分比(相对于保证金),默认15%(更宽松,给趋势更多空间)
|
||||||
'MIN_STOP_LOSS_PRICE_PCT': 0.01, # 最小止损价格变动百分比(如0.01表示1%),防止止损过紧,默认1%
|
'MIN_STOP_LOSS_PRICE_PCT': 0.02, # 最小止损价格变动百分比(如0.02表示2%),防止止损过紧,默认2%
|
||||||
'MIN_TAKE_PROFIT_PRICE_PCT': 0.015, # 最小止盈价格变动百分比(如0.015表示1.5%),防止止盈过紧,默认1.5%
|
'MIN_TAKE_PROFIT_PRICE_PCT': 0.03, # 最小止盈价格变动百分比(如0.03表示3%),防止止盈过紧,默认3%
|
||||||
'SCAN_INTERVAL': 3600,
|
'SCAN_INTERVAL': 3600,
|
||||||
'KLINE_INTERVAL': '1h',
|
'KLINE_INTERVAL': '1h',
|
||||||
'PRIMARY_INTERVAL': '1h',
|
'PRIMARY_INTERVAL': '1h',
|
||||||
|
|
|
||||||
|
|
@ -163,9 +163,12 @@ class PositionManager:
|
||||||
atr=atr
|
atr=atr
|
||||||
)
|
)
|
||||||
|
|
||||||
# 计算止盈(基于保证金,为止损的倍数)
|
# 计算止盈(基于保证金)
|
||||||
# 如果止损是保证金的3%,止盈可以是保证金的7.5%(2.5倍)
|
# 优先使用配置的止盈百分比,如果没有配置则使用止损的2倍
|
||||||
take_profit_pct_margin = stop_loss_pct_margin * 2.5
|
take_profit_pct_margin = self.risk_manager.config.get('TAKE_PROFIT_PERCENT', 0.15)
|
||||||
|
# 如果配置中没有设置止盈,则使用止损的2倍作为默认
|
||||||
|
if take_profit_pct_margin is None or take_profit_pct_margin == 0:
|
||||||
|
take_profit_pct_margin = stop_loss_pct_margin * 2.0
|
||||||
take_profit_price = self.risk_manager.get_take_profit_price(
|
take_profit_price = self.risk_manager.get_take_profit_price(
|
||||||
entry_price, side, quantity, leverage,
|
entry_price, side, quantity, leverage,
|
||||||
take_profit_pct=take_profit_pct_margin
|
take_profit_pct=take_profit_pct_margin
|
||||||
|
|
@ -185,61 +188,97 @@ class PositionManager:
|
||||||
if entry_order_id:
|
if entry_order_id:
|
||||||
logger.info(f"{symbol} [开仓] 币安订单号: {entry_order_id}")
|
logger.info(f"{symbol} [开仓] 币安订单号: {entry_order_id}")
|
||||||
|
|
||||||
# 等待订单成交,然后从币安获取实际成交价格
|
# 等待订单成交,检查订单状态并获取实际成交价格
|
||||||
|
# 只有在订单真正成交(FILLED)后才保存到数据库
|
||||||
actual_entry_price = None
|
actual_entry_price = None
|
||||||
try:
|
order_status = None
|
||||||
# 等待一小段时间让订单成交
|
filled_quantity = 0
|
||||||
await asyncio.sleep(1)
|
max_retries = 5 # 最多重试5次,每次等待1秒
|
||||||
|
retry_count = 0
|
||||||
# 从币安获取订单详情,获取实际成交价格
|
|
||||||
|
while retry_count < max_retries:
|
||||||
try:
|
try:
|
||||||
order_info = await self.client.client.futures_get_order(symbol=symbol, orderId=entry_order_id)
|
# 等待一小段时间让订单成交
|
||||||
if order_info:
|
await asyncio.sleep(1)
|
||||||
# 优先使用平均成交价格(avgPrice),如果没有则使用价格字段
|
|
||||||
actual_entry_price = float(order_info.get('avgPrice', 0)) or float(order_info.get('price', 0))
|
# 从币安获取订单详情,检查订单状态
|
||||||
if actual_entry_price > 0:
|
try:
|
||||||
logger.info(f"{symbol} [开仓] 从币安订单获取实际成交价格: {actual_entry_price:.4f} USDT")
|
order_info = await self.client.client.futures_get_order(symbol=symbol, orderId=entry_order_id)
|
||||||
else:
|
if order_info:
|
||||||
# 如果订单还没有完全成交,尝试从成交记录获取
|
order_status = order_info.get('status')
|
||||||
if order_info.get('status') == 'FILLED' and order_info.get('fills'):
|
logger.info(f"{symbol} [开仓] 订单状态: {order_status} (重试 {retry_count + 1}/{max_retries})")
|
||||||
# 计算加权平均成交价格
|
|
||||||
total_qty = 0
|
# 检查订单是否已成交
|
||||||
total_value = 0
|
if order_status == 'FILLED':
|
||||||
for fill in order_info.get('fills', []):
|
# 订单已完全成交,获取实际成交价格和数量
|
||||||
qty = float(fill.get('qty', 0))
|
actual_entry_price = float(order_info.get('avgPrice', 0)) or float(order_info.get('price', 0))
|
||||||
price = float(fill.get('price', 0))
|
filled_quantity = float(order_info.get('executedQty', 0))
|
||||||
total_qty += qty
|
|
||||||
total_value += qty * price
|
if actual_entry_price > 0 and filled_quantity > 0:
|
||||||
if total_qty > 0:
|
logger.info(f"{symbol} [开仓] ✓ 订单已成交,成交价格: {actual_entry_price:.4f} USDT, 成交数量: {filled_quantity:.4f}")
|
||||||
actual_entry_price = total_value / total_qty
|
break
|
||||||
logger.info(f"{symbol} [开仓] 从成交记录计算平均成交价格: {actual_entry_price:.4f} USDT")
|
elif order_info.get('fills'):
|
||||||
except Exception as order_error:
|
# 从成交记录计算加权平均成交价格和总成交数量
|
||||||
logger.warning(f"{symbol} [开仓] 获取订单详情失败: {order_error},使用备用方法")
|
total_qty = 0
|
||||||
|
total_value = 0
|
||||||
# 如果无法从订单获取价格,使用当前价格作为备用
|
for fill in order_info.get('fills', []):
|
||||||
if not actual_entry_price or actual_entry_price <= 0:
|
qty = float(fill.get('qty', 0))
|
||||||
ticker = await self.client.get_ticker_24h(symbol)
|
price = float(fill.get('price', 0))
|
||||||
if ticker:
|
total_qty += qty
|
||||||
actual_entry_price = float(ticker['price'])
|
total_value += qty * price
|
||||||
logger.warning(f"{symbol} [开仓] 使用当前价格作为入场价格: {actual_entry_price:.4f} USDT")
|
if total_qty > 0:
|
||||||
else:
|
actual_entry_price = total_value / total_qty
|
||||||
actual_entry_price = float(order.get('avgPrice', 0)) or float(order.get('price', 0))
|
filled_quantity = total_qty
|
||||||
if actual_entry_price <= 0:
|
logger.info(f"{symbol} [开仓] ✓ 订单已成交,从成交记录计算平均成交价格: {actual_entry_price:.4f} USDT, 成交数量: {filled_quantity:.4f}")
|
||||||
logger.error(f"{symbol} [开仓] 无法获取入场价格,使用订单价格字段")
|
break
|
||||||
actual_entry_price = float(order.get('price', 0)) or entry_price
|
elif order_status == 'PARTIALLY_FILLED':
|
||||||
except Exception as price_error:
|
# 部分成交,继续等待
|
||||||
logger.warning(f"{symbol} [开仓] 获取成交价格时出错: {price_error},使用当前价格")
|
filled_quantity = float(order_info.get('executedQty', 0))
|
||||||
ticker = await self.client.get_ticker_24h(symbol)
|
logger.info(f"{symbol} [开仓] ⏳ 订单部分成交 ({filled_quantity:.4f}/{quantity:.4f}),继续等待...")
|
||||||
actual_entry_price = float(ticker['price']) if ticker else entry_price
|
retry_count += 1
|
||||||
|
continue
|
||||||
|
elif order_status in ['NEW', 'PENDING_NEW']:
|
||||||
|
# 订单已提交但未成交,继续等待
|
||||||
|
logger.info(f"{symbol} [开仓] ⏳ 订单已提交但未成交,继续等待...")
|
||||||
|
retry_count += 1
|
||||||
|
continue
|
||||||
|
elif order_status in ['CANCELED', 'REJECTED', 'EXPIRED']:
|
||||||
|
# 订单被取消、拒绝或过期
|
||||||
|
logger.error(f"{symbol} [开仓] ❌ 订单状态异常: {order_status},订单未成交")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
logger.warning(f"{symbol} [开仓] ⚠️ 未知订单状态: {order_status},继续等待...")
|
||||||
|
retry_count += 1
|
||||||
|
continue
|
||||||
|
except Exception as order_error:
|
||||||
|
logger.warning(f"{symbol} [开仓] 获取订单详情失败: {order_error},重试中...")
|
||||||
|
retry_count += 1
|
||||||
|
continue
|
||||||
|
except Exception as price_error:
|
||||||
|
logger.warning(f"{symbol} [开仓] 检查订单状态时出错: {price_error},重试中...")
|
||||||
|
retry_count += 1
|
||||||
|
continue
|
||||||
|
|
||||||
# 使用实际成交价格(如果获取成功)
|
# 检查订单是否最终成交
|
||||||
if actual_entry_price and actual_entry_price > 0:
|
if order_status != 'FILLED':
|
||||||
# 记录下单时的价格(用于对比)
|
logger.error(f"{symbol} [开仓] ❌ 订单未成交,状态: {order_status},不保存到数据库")
|
||||||
original_entry_price = entry_price
|
return None
|
||||||
entry_price = actual_entry_price
|
|
||||||
logger.info(f"{symbol} [开仓] 使用实际成交价格: {entry_price:.4f} USDT (下单时价格: {original_entry_price:.4f})")
|
|
||||||
|
|
||||||
# 记录到数据库(使用实际成交价格)
|
if not actual_entry_price or actual_entry_price <= 0:
|
||||||
|
logger.error(f"{symbol} [开仓] ❌ 无法获取实际成交价格,不保存到数据库")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if filled_quantity <= 0:
|
||||||
|
logger.error(f"{symbol} [开仓] ❌ 成交数量为0,不保存到数据库")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 使用实际成交价格和数量
|
||||||
|
original_entry_price = entry_price
|
||||||
|
entry_price = actual_entry_price
|
||||||
|
quantity = filled_quantity # 使用实际成交数量
|
||||||
|
logger.info(f"{symbol} [开仓] ✓ 使用实际成交价格: {entry_price:.4f} USDT (下单时价格: {original_entry_price:.4f}), 成交数量: {quantity:.4f}")
|
||||||
|
|
||||||
|
# 记录到数据库(只有在订单真正成交后才保存)
|
||||||
trade_id = None
|
trade_id = None
|
||||||
if DB_AVAILABLE and Trade:
|
if DB_AVAILABLE and Trade:
|
||||||
try:
|
try:
|
||||||
|
|
@ -247,18 +286,19 @@ class PositionManager:
|
||||||
trade_id = Trade.create(
|
trade_id = Trade.create(
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
side=side,
|
side=side,
|
||||||
quantity=quantity,
|
quantity=quantity, # 使用实际成交数量
|
||||||
entry_price=entry_price, # 使用实际成交价格
|
entry_price=entry_price, # 使用实际成交价格
|
||||||
leverage=leverage,
|
leverage=leverage,
|
||||||
entry_reason=entry_reason,
|
entry_reason=entry_reason,
|
||||||
entry_order_id=entry_order_id # 保存币安订单号
|
entry_order_id=entry_order_id # 保存币安订单号
|
||||||
)
|
)
|
||||||
logger.info(f"✓ {symbol} 交易记录已保存到数据库 (ID: {trade_id}, 订单号: {entry_order_id}, 成交价: {entry_price:.4f})")
|
logger.info(f"✓ {symbol} 交易记录已保存到数据库 (ID: {trade_id}, 订单号: {entry_order_id}, 成交价: {entry_price:.4f}, 成交数量: {quantity:.4f})")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ 保存交易记录到数据库失败: {e}")
|
logger.error(f"❌ 保存交易记录到数据库失败: {e}")
|
||||||
logger.error(f" 错误类型: {type(e).__name__}")
|
logger.error(f" 错误类型: {type(e).__name__}")
|
||||||
import traceback
|
import traceback
|
||||||
logger.error(f" 错误详情:\n{traceback.format_exc()}")
|
logger.error(f" 错误详情:\n{traceback.format_exc()}")
|
||||||
|
return None
|
||||||
elif not DB_AVAILABLE:
|
elif not DB_AVAILABLE:
|
||||||
logger.debug(f"数据库不可用,跳过保存 {symbol} 交易记录")
|
logger.debug(f"数据库不可用,跳过保存 {symbol} 交易记录")
|
||||||
elif not Trade:
|
elif not Trade:
|
||||||
|
|
@ -1059,10 +1099,11 @@ class PositionManager:
|
||||||
|
|
||||||
# 计算止损止盈(基于保证金)
|
# 计算止损止盈(基于保证金)
|
||||||
leverage = binance_position.get('leverage', 10)
|
leverage = binance_position.get('leverage', 10)
|
||||||
stop_loss_pct_margin = self.risk_manager.config.get('STOP_LOSS_PERCENT', 0.03)
|
stop_loss_pct_margin = self.risk_manager.config.get('STOP_LOSS_PERCENT', 0.08)
|
||||||
take_profit_pct_margin = self.risk_manager.config.get('TAKE_PROFIT_PERCENT', 0.05)
|
take_profit_pct_margin = self.risk_manager.config.get('TAKE_PROFIT_PERCENT', 0.15)
|
||||||
# 止盈为止损的2.5倍
|
# 如果配置中没有设置止盈,则使用止损的2倍作为默认
|
||||||
take_profit_pct_margin = stop_loss_pct_margin * 2.5
|
if take_profit_pct_margin is None or take_profit_pct_margin == 0:
|
||||||
|
take_profit_pct_margin = stop_loss_pct_margin * 2.0
|
||||||
|
|
||||||
stop_loss_price = self.risk_manager.get_stop_loss_price(
|
stop_loss_price = self.risk_manager.get_stop_loss_price(
|
||||||
entry_price, side, quantity, leverage,
|
entry_price, side, quantity, leverage,
|
||||||
|
|
@ -1153,8 +1194,11 @@ class PositionManager:
|
||||||
|
|
||||||
# 计算止损止盈(基于保证金)
|
# 计算止损止盈(基于保证金)
|
||||||
leverage = position.get('leverage', 10)
|
leverage = position.get('leverage', 10)
|
||||||
stop_loss_pct_margin = self.risk_manager.config.get('STOP_LOSS_PERCENT', 0.03)
|
stop_loss_pct_margin = self.risk_manager.config.get('STOP_LOSS_PERCENT', 0.08)
|
||||||
take_profit_pct_margin = stop_loss_pct_margin * 2.5
|
take_profit_pct_margin = self.risk_manager.config.get('TAKE_PROFIT_PERCENT', 0.15)
|
||||||
|
# 如果配置中没有设置止盈,则使用止损的2倍作为默认
|
||||||
|
if take_profit_pct_margin is None or take_profit_pct_margin == 0:
|
||||||
|
take_profit_pct_margin = stop_loss_pct_margin * 2.0
|
||||||
|
|
||||||
stop_loss_price = self.risk_manager.get_stop_loss_price(
|
stop_loss_price = self.risk_manager.get_stop_loss_price(
|
||||||
entry_price, side, quantity, leverage,
|
entry_price, side, quantity, leverage,
|
||||||
|
|
|
||||||
|
|
@ -372,8 +372,8 @@ class TradeRecommender:
|
||||||
estimated_quantity = estimated_position_value / entry_price if entry_price > 0 else 0
|
estimated_quantity = estimated_position_value / entry_price if entry_price > 0 else 0
|
||||||
|
|
||||||
# 计算基于保证金的止损止盈
|
# 计算基于保证金的止损止盈
|
||||||
stop_loss_pct_margin = config.TRADING_CONFIG.get('STOP_LOSS_PERCENT', 0.03)
|
stop_loss_pct_margin = config.TRADING_CONFIG.get('STOP_LOSS_PERCENT', 0.08)
|
||||||
take_profit_pct_margin = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.05)
|
take_profit_pct_margin = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.15)
|
||||||
|
|
||||||
stop_loss_price = self.risk_manager.get_stop_loss_price(
|
stop_loss_price = self.risk_manager.get_stop_loss_price(
|
||||||
entry_price,
|
entry_price,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user