a
This commit is contained in:
parent
0e9a0977e9
commit
5e963ecc01
146
MULTI_USER_ARCHITECTURE.md
Normal file
146
MULTI_USER_ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
## 多用户(多账号)架构评估与落地方案(草案)
|
||||
|
||||
本文档基于你提出的多用户需求,给出数据隔离边界、并发模型、Redis/DB 设计、以及 2核4G 服务器负载评估与分阶段落地路线。
|
||||
|
||||
---
|
||||
|
||||
## 目标与约束
|
||||
|
||||
### 目标(对应你的 6 点)
|
||||
|
||||
1. **每个用户使用自己的 API KEY**
|
||||
2. **每个用户独立自动交易、独立持仓**
|
||||
3. **每个用户独立配置**
|
||||
4. **交易推荐全局共用**
|
||||
5. **订单/交易记录独立**
|
||||
6. **错误日志全局共用**
|
||||
|
||||
### 关键约束
|
||||
|
||||
- **币安限频是关键瓶颈**:公共行情接口通常按 IP 限频,单纯“多 key”无法线性扩容;必须通过共享行情缓存降低请求。
|
||||
- **隔离优先级**:下单/持仓/配置/订单记录必须强隔离;推荐/行情/错误日志可以共享。
|
||||
|
||||
---
|
||||
|
||||
## 数据归属与隔离(Data Ownership)
|
||||
|
||||
### A. 全局共享(所有用户共用)
|
||||
|
||||
- **推荐(Recommendations)**:一份全局 Redis Snapshot/List
|
||||
- 建议 Key:`ats:recommendations:snapshot`
|
||||
- 后端/前端只读(不在请求中触发扫描/生成)
|
||||
- **行情热数据(Market Data)**:全局缓存(Redis)
|
||||
- `ats:md:mark_price:all`(短 TTL,比如 2~5s)
|
||||
- `ats:md:ticker_24h:all`(TTL 10~30s)
|
||||
- `ats:md:klines:{symbol}:{interval}:{limit}`(TTL 30~120s)
|
||||
- **错误日志(Logs)**:全局流
|
||||
- 继续使用 `ats:logs:{error|warning|info}`
|
||||
- 每条日志建议增加字段:`account_id`(便于筛选定位)
|
||||
|
||||
### B. 用户私有(必须隔离)
|
||||
|
||||
- **API KEY/SECRET(敏感)**:按账号独立存储(必须加密)
|
||||
- **交易配置**:按账号独立
|
||||
- DB:`(account_id, config_key)` 唯一
|
||||
- Redis:`ats:cfg:{account_id}`(hash)
|
||||
- **自动交易运行态/热数据(可选但推荐)**
|
||||
- `ats:positions:{account_id}`(更快的 UI 展示/减少后端压力)
|
||||
- `ats:orders:pending:{account_id}`(挂单/入场状态)
|
||||
- `ats:stats:{account_id}:*`(每日下单数、成交数、撤单数等)
|
||||
- **交易/订单记录**:按账号独立
|
||||
- DB 表:`trades` 增加 `account_id`;统计/查询均按账号过滤
|
||||
|
||||
---
|
||||
|
||||
## 并发模型:是否需要多线程/多进程?
|
||||
|
||||
结论:**不推荐多线程**。更推荐以下两种模型之一:
|
||||
|
||||
### 方案 1(推荐先落地):每个账号一个 `trading_system` 进程(Supervisor 管理)
|
||||
|
||||
- **优点**
|
||||
- 隔离强:某个账号异常/限频/崩溃不影响其他账号
|
||||
- 上线快:对现有单账号 trading_system 改动最少
|
||||
- 运维简单:Supervisor 一账号一进程
|
||||
- **缺点**
|
||||
- 进程数随账号增长(内存占用会上升)
|
||||
- 若每进程都重复拉行情,会触发 IP 限频(必须配合“全局行情缓存”)
|
||||
|
||||
### 方案 2(后期优化):单进程 + asyncio 多账号 worker
|
||||
|
||||
- **优点**
|
||||
- 资源更省,便于共享行情缓存/HTTP Session
|
||||
- **缺点**
|
||||
- 隔离弱:某个账号阻塞/异常可能影响整体
|
||||
- 实现复杂:需要更严格的超时/限频/异常隔离与任务监控
|
||||
|
||||
---
|
||||
|
||||
## Redis 用法:减轻后端与交易系统压力的关键
|
||||
|
||||
建议将 Redis 分为四层:
|
||||
|
||||
1. **全局行情缓存层**(降低公共接口请求)
|
||||
2. **全局推荐层**(只读 snapshot,避免“刷新=触发扫描”)
|
||||
3. **账号配置层**(`ats:cfg:{account_id}`)
|
||||
4. **账号运行态层**(positions/pending/stats)
|
||||
|
||||
额外建议:
|
||||
- 使用短 TTL + 分布式锁(你已有 lock 设计),避免并发刷新造成浪涌。
|
||||
|
||||
---
|
||||
|
||||
## 数据库改造(最小必要变更)
|
||||
|
||||
1. 新增 `accounts` 表:
|
||||
- `id, name, api_key_enc, api_secret_enc, status, created_at ...`
|
||||
- API Key 必须加密存储(服务端用 master key 解密)
|
||||
2. `trading_config` 增加 `account_id`(或新表 `trading_config_accounts`)
|
||||
3. `trades` 增加 `account_id`
|
||||
4. `account_snapshots` 增加 `account_id`
|
||||
5. 其余按需:positions、signals(signals 可全局,positions 必须私有)
|
||||
|
||||
---
|
||||
|
||||
## 2核4G 负载评估(粗略)
|
||||
|
||||
负载主要来自两块:
|
||||
|
||||
1. **公共行情拉取/指标计算(全局)**
|
||||
2. **每账号的下单/持仓同步/风控计算(私有)**
|
||||
|
||||
### 不做共享行情(不推荐)
|
||||
|
||||
- 每账号都拉 klines/mark price/ticker:会先撞到 **币安 IP 限频**,2c4g 也撑不住。
|
||||
|
||||
### 做共享行情 + 推荐全局(推荐)
|
||||
|
||||
- 公共行情只拉 1 份(Redis 分发),账号 worker 只消费缓存 + 下单/持仓同步。
|
||||
- 2c4g 在“波段低频”(如 15min 扫描、每账号持仓 0~5)场景下,通常可支撑 **10~30 个账号**(视持仓监控方式与限频策略而定)。
|
||||
- 若每个账号都对每个持仓开 WebSocket 监控,上限会明显降低(更建议用全局 mark price + 定时校验替代/降频)。
|
||||
|
||||
---
|
||||
|
||||
## 分阶段落地路线(推荐)
|
||||
|
||||
### Phase 1:最快上线(强隔离、成本最低)
|
||||
|
||||
- accounts + account_id 改造(DB/API/前端)
|
||||
- 每账号一个 trading_system 进程(Supervisor)
|
||||
- 推荐/行情继续全局缓存(Redis)
|
||||
- 日志全局流(带 account_id)
|
||||
|
||||
### Phase 2:提升承载与稳定性
|
||||
|
||||
- 抽全局 MarketDataService(行情/klines 统一刷新一次)
|
||||
- 每账号 worker 只做决策/下单,减少公共 API 调用
|
||||
- 降低 per-position websocket 数量(用 mark price 缓存替代)
|
||||
|
||||
---
|
||||
|
||||
## 风险提示与建议
|
||||
|
||||
- **安全**:API Key 必须加密存储;前端永远不返回明文 secret。
|
||||
- **限频**:公共接口一定要共享缓存;多账号并发拉行情会让整体不可用。
|
||||
- **隔离**:交易执行建议多进程优先;后期再做单进程多 worker 的资源优化。
|
||||
|
||||
|
|
@ -21,10 +21,10 @@ router = APIRouter()
|
|||
# 智能入场(方案C)配置:为了“配置页可见”,即使数据库尚未创建,也在 GET /api/config 返回默认项
|
||||
SMART_ENTRY_CONFIG_DEFAULTS = {
|
||||
"SMART_ENTRY_ENABLED": {
|
||||
"value": True,
|
||||
"value": False,
|
||||
"type": "boolean",
|
||||
"category": "strategy",
|
||||
"description": "智能入场开关(方案C):趋势时减少错过,震荡时避免追价打损。",
|
||||
"description": "智能入场开关。关闭后回归“纯限价单模式”(不追价/不市价兜底/未成交则撤单跳过),更适合低频波段。",
|
||||
},
|
||||
"SMART_ENTRY_STRONG_SIGNAL": {
|
||||
"value": 8,
|
||||
|
|
|
|||
|
|
@ -219,6 +219,13 @@ async def get_trade_stats(
|
|||
# 只统计有意义的交易(排除0盈亏)的胜率
|
||||
win_trades = [t for t in meaningful_trades if float(t['pnl']) > 0]
|
||||
loss_trades = [t for t in meaningful_trades if float(t['pnl']) < 0]
|
||||
|
||||
# 盈利/亏损均值(用于观察是否接近 3:1)
|
||||
avg_win_pnl = sum(float(t["pnl"]) for t in win_trades) / len(win_trades) if win_trades else 0.0
|
||||
avg_loss_pnl_abs = (
|
||||
sum(abs(float(t["pnl"])) for t in loss_trades) / len(loss_trades) if loss_trades else 0.0
|
||||
)
|
||||
win_loss_ratio = (avg_win_pnl / avg_loss_pnl_abs) if avg_loss_pnl_abs > 0 else None
|
||||
|
||||
stats = {
|
||||
"total_trades": len(trades),
|
||||
|
|
@ -231,6 +238,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,
|
||||
# 额外统计:盈利单均值 vs 亏损单均值(绝对值)以及比值(目标 3:1)
|
||||
"avg_win_pnl": avg_win_pnl,
|
||||
"avg_loss_pnl_abs": avg_loss_pnl_abs,
|
||||
"avg_win_loss_ratio": win_loss_ratio,
|
||||
"avg_win_loss_ratio_target": 3.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))))
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ const ConfigPanel = () => {
|
|||
const presets = {
|
||||
swing: {
|
||||
name: '波段回归(推荐)',
|
||||
desc: '回归“15分钟节奏 + 精选机会 + 小保证金占用”的波段交易。建议先跑20-30单再评估。',
|
||||
desc: '根治高频与追价:关闭智能入场,回归“纯限价 + 30分钟扫描 + 更高信号门槛”的低频波段。建议先跑20-30单再评估。',
|
||||
configs: {
|
||||
// 操作频率
|
||||
SCAN_INTERVAL: 900, // 15分钟
|
||||
SCAN_INTERVAL: 1800, // 30分钟
|
||||
TOP_N_SYMBOLS: 8,
|
||||
|
||||
// 仓位管理(重要语义:这些百分比均按“保证金占用比例”理解)
|
||||
|
|
@ -37,8 +37,11 @@ const ConfigPanel = () => {
|
|||
MIN_POSITION_PERCENT: 0.0, // 0%(等价于关闭最小仓位占比)
|
||||
|
||||
// 风控
|
||||
MIN_SIGNAL_STRENGTH: 7,
|
||||
MIN_SIGNAL_STRENGTH: 8,
|
||||
USE_TRAILING_STOP: false,
|
||||
|
||||
// 根治:关闭智能入场(回归纯限价,不追价/不市价兜底)
|
||||
SMART_ENTRY_ENABLED: false,
|
||||
},
|
||||
},
|
||||
conservative: {
|
||||
|
|
@ -434,6 +437,10 @@ const ConfigPanel = () => {
|
|||
// 根据key判断类型和分类
|
||||
let type = 'number'
|
||||
let category = 'risk'
|
||||
if (typeof value === 'boolean') {
|
||||
type = 'boolean'
|
||||
category = 'strategy'
|
||||
}
|
||||
if (key.includes('PERCENT') || key.includes('PCT')) {
|
||||
type = 'number'
|
||||
if (key.includes('STOP_LOSS') || key.includes('TAKE_PROFIT')) {
|
||||
|
|
@ -1150,9 +1157,9 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => {
|
|||
const getConfigDetail = (key) => {
|
||||
const details = {
|
||||
// 市场扫描参数
|
||||
'SCAN_INTERVAL': '扫描间隔(秒)。系统每隔多长时间扫描一次市场寻找交易机会。值越小扫描越频繁,能更快捕捉波动,但会增加API请求和系统负载。建议:保守策略3600秒(1小时),平衡策略600秒(10分钟),激进策略300秒(5分钟)。晚间波动大时可降低到300-600秒。',
|
||||
'SCAN_INTERVAL': '扫描间隔(秒)。系统每隔多长时间扫描一次市场寻找交易机会。值越小扫描越频繁,能更快捕捉波动,但更容易产生噪音与过度交易。建议:低频波段1800秒(30分钟)或更长;稳健/保守可3600秒(1小时);高频不建议长期使用。',
|
||||
'MIN_CHANGE_PERCENT': '最小涨跌幅阈值(%)。只有24小时涨跌幅达到此值的交易对才会被考虑交易。值越小捕捉机会越多,但可能包含噪音和假信号。值越大只捕捉大幅波动,信号质量更高但机会更少。建议:保守策略2.0-3.0%,平衡策略1.5-2.0%,激进策略1.0-1.5%。',
|
||||
'MIN_SIGNAL_STRENGTH': '最小信号强度(0-10)。技术指标综合评分,只有达到此强度的信号才会执行交易。值越小交易机会越多,但信号质量可能下降,胜率降低。值越大只执行高质量信号,胜率更高但机会更少。建议:保守策略5-7,平衡策略4-5,激进策略3-4。',
|
||||
'MIN_SIGNAL_STRENGTH': '最小信号强度(0-10)。技术指标综合评分,只有达到此强度的信号才会执行交易。值越小机会越多但噪音更大;值越大更偏“精选高质量”。建议:低频波段建议≥8;一般保守5-7;平衡4-5;高频3-4(不推荐长期)。',
|
||||
'TOP_N_SYMBOLS': '每次扫描后处理的交易对数量。从符合条件的交易对中选择涨跌幅最大的前N个进行详细分析。值越大机会越多,但计算量增加,API请求增多。建议:保守策略8-10个,平衡策略12-15个,激进策略15-20个。',
|
||||
'MAX_SCAN_SYMBOLS': '扫描的最大交易对数量(0表示扫描所有)。限制每次扫描时处理的交易对总数,减少API请求和计算量。值越小扫描越快,但可能错过一些机会。值越大覆盖更全面,但API请求和计算量增加。建议:保守策略100-200个,平衡策略200-300个,激进策略300-500个。设置为0会扫描所有交易对(约500+个)。',
|
||||
'MIN_VOLATILITY': '最小波动率(小数形式,如0.02表示2%)。过滤掉波动率低于此值的交易对,确保只交易有足够波动的币种。值越小允许更多交易对,但可能包含波动不足的币种。值越大只交易高波动币种,但可能错过一些机会。建议:0.015-0.025(1.5%-2.5%)。',
|
||||
|
|
@ -1176,6 +1183,7 @@ const getConfigDetail = (key) => {
|
|||
// 策略参数
|
||||
'LEVERAGE': '交易杠杆倍数。放大资金利用率,同时放大收益和风险。杠杆越高,相同仓位下需要的保证金越少,但风险越大。建议:保守策略5-10倍,平衡策略10倍,激进策略10-15倍。注意:高杠杆会增加爆仓风险,请谨慎使用。',
|
||||
'USE_TRAILING_STOP': '是否启用移动止损(true/false)。启用后,当盈利达到激活阈值时,止损会自动跟踪价格,保护利润。适合趋势行情,可以捕捉更大的利润空间。建议:平衡和激进策略启用,保守策略可关闭。',
|
||||
'SMART_ENTRY_ENABLED': '智能入场开关(true/false)。开启时会进行“限价回调 + 追价 +(趋势强时)市价兜底”,以减少错过;关闭时回归“纯限价单模式”:只下一次限价单,未在确认时间内成交则撤单跳过,更适合低频波段与控频。',
|
||||
'TRAILING_STOP_ACTIVATION': '移动止损激活阈值(百分比,如0.01表示1%)。当盈利达到此百分比时,移动止损开始跟踪价格,将止损移至成本价(保本)。值越小激活越早,更早保护利润但可能过早退出。值越大激活越晚,给价格更多波动空间。建议:1-2%。',
|
||||
'TRAILING_STOP_PROTECT': '移动止损保护利润(百分比,如0.01表示1%)。当价格从最高点回撤达到此百分比时,触发止损平仓,锁定利润。值越小保护更严格,能锁定更多利润但可能过早退出。值越大允许更大回撤,可能捕捉更大趋势但利润可能回吐。建议:1-2%。',
|
||||
|
||||
|
|
|
|||
|
|
@ -266,6 +266,23 @@ const TradeList = () => {
|
|||
{stats.avg_pnl.toFixed(2)} USDT
|
||||
</div>
|
||||
</div>
|
||||
{"avg_win_pnl" in stats && "avg_loss_pnl_abs" in stats && (
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">平均盈利 / 平均亏损(期望 3:1)</div>
|
||||
<div
|
||||
className={`stat-value ${
|
||||
typeof stats.avg_win_loss_ratio === 'number' && stats.avg_win_loss_ratio >= 3 ? 'positive' : ''
|
||||
}`}
|
||||
>
|
||||
{typeof stats.avg_win_loss_ratio === 'number'
|
||||
? `${stats.avg_win_loss_ratio.toFixed(2)} : 1`
|
||||
: '—'}
|
||||
</div>
|
||||
<div style={{ fontSize: '12px', color: '#999', marginTop: '4px' }}>
|
||||
+{Number(stats.avg_win_pnl || 0).toFixed(2)} / -{Number(stats.avg_loss_pnl_abs || 0).toFixed(2)} USDT
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{"total_notional_usdt" in stats && (
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">总交易量(名义)</div>
|
||||
|
|
|
|||
|
|
@ -192,14 +192,14 @@ def _get_trading_config():
|
|||
'USE_DYNAMIC_ATR_MULTIPLIER': False, # 是否根据波动率动态调整ATR倍数
|
||||
'ATR_MULTIPLIER_MIN': 1.5, # 动态ATR倍数最小值
|
||||
'ATR_MULTIPLIER_MAX': 2.5, # 动态ATR倍数最大值
|
||||
'SCAN_INTERVAL': 3600,
|
||||
'SCAN_INTERVAL': 1800,
|
||||
'KLINE_INTERVAL': '1h',
|
||||
'PRIMARY_INTERVAL': '1h',
|
||||
'CONFIRM_INTERVAL': '4h',
|
||||
'ENTRY_INTERVAL': '15m',
|
||||
'MIN_VOLUME_24H': 5000000, # 降低到500万以获取更多推荐(推荐系统可以更宽松)
|
||||
'MIN_VOLATILITY': 0.02,
|
||||
'MIN_SIGNAL_STRENGTH': 7, # 提高至7,只交易高质量信号(简化策略后)
|
||||
'MIN_SIGNAL_STRENGTH': 8, # 提高至8,只交易高质量信号(低频波段)
|
||||
'LEVERAGE': 10, # 基础杠杆倍数
|
||||
'USE_DYNAMIC_LEVERAGE': True, # 是否启用动态杠杆(根据信号强度调整)
|
||||
'MAX_LEVERAGE': 15, # 最大杠杆倍数(降低到15,更保守,配合更大的保证金)
|
||||
|
|
@ -208,8 +208,9 @@ def _get_trading_config():
|
|||
'TRAILING_STOP_PROTECT': 0.05, # 保护利润提高到5%(保护5%利润,更合理)
|
||||
'POSITION_SYNC_INTERVAL': 60, # 持仓状态同步间隔(秒),缩短到1分钟,确保状态及时同步
|
||||
|
||||
# ===== 智能入场(方案C:趋势少错过,震荡不追价)=====
|
||||
'SMART_ENTRY_ENABLED': True,
|
||||
# ===== 智能入场(方案C)=====
|
||||
# 根治方案:默认关闭。关闭后回归“纯限价单模式”(不追价/不市价兜底/未成交撤单跳过)
|
||||
'SMART_ENTRY_ENABLED': False,
|
||||
'SMART_ENTRY_STRONG_SIGNAL': 8, # 强信号阈值:≥8 更倾向趋势模式(允许市价兜底)
|
||||
'ENTRY_SYMBOL_COOLDOWN_SEC': 120, # 同一symbol两次入场尝试的冷却时间(避免反复挂单/重入)
|
||||
'ENTRY_TIMEOUT_SEC': 180, # 智能入场总预算(秒)(限价/追价逻辑内部使用)
|
||||
|
|
@ -233,14 +234,14 @@ TRADING_CONFIG = _get_trading_config()
|
|||
|
||||
# 确保包含所有必要的默认值
|
||||
defaults = {
|
||||
'SCAN_INTERVAL': 3600,
|
||||
'SCAN_INTERVAL': 1800,
|
||||
'KLINE_INTERVAL': '1h',
|
||||
'PRIMARY_INTERVAL': '1h',
|
||||
'CONFIRM_INTERVAL': '4h',
|
||||
'ENTRY_INTERVAL': '15m',
|
||||
'LIMIT_ORDER_OFFSET_PCT': 0.5, # 限价单偏移百分比(默认0.5%)
|
||||
# 智能入场默认值(即使DB里没配置,也能用)
|
||||
'SMART_ENTRY_ENABLED': True,
|
||||
'SMART_ENTRY_ENABLED': False,
|
||||
'ENTRY_SYMBOL_COOLDOWN_SEC': 120,
|
||||
'ENTRY_TIMEOUT_SEC': 180,
|
||||
'ENTRY_STEP_WAIT_SEC': 15,
|
||||
|
|
|
|||
|
|
@ -298,10 +298,26 @@ class PositionManager:
|
|||
order_status = None
|
||||
actual_entry_price = None
|
||||
filled_quantity = 0.0
|
||||
entry_mode_used = "market" if not smart_entry_enabled else ("limit+fallback" if allow_market_fallback else "limit-chase")
|
||||
entry_mode_used = "limit-only" if not smart_entry_enabled else ("limit+fallback" if allow_market_fallback else "limit-chase")
|
||||
|
||||
if not smart_entry_enabled:
|
||||
order = await self.client.place_order(symbol=symbol, side=side, quantity=quantity, order_type="MARKET")
|
||||
# 根治方案:关闭智能入场后,回归“纯限价单模式”
|
||||
# - 不追价
|
||||
# - 不市价兜底
|
||||
# - 未在确认时间内成交则撤单并跳过(属于策略未触发入场,不是系统错误)
|
||||
confirm_timeout = int(config.TRADING_CONFIG.get("ENTRY_CONFIRM_TIMEOUT_SEC", 30) or 30)
|
||||
logger.info(
|
||||
f"{symbol} [纯限价入场] side={side} | 限价={initial_limit:.6f} (offset={limit_offset_ratio*100:.2f}%) | "
|
||||
f"确认超时={confirm_timeout}s(未成交将撤单跳过)"
|
||||
)
|
||||
order = await self.client.place_order(
|
||||
symbol=symbol, side=side, quantity=quantity, order_type="LIMIT", price=initial_limit
|
||||
)
|
||||
if not order:
|
||||
return None
|
||||
entry_order_id = order.get("orderId")
|
||||
if entry_order_id:
|
||||
self._pending_entry_orders[symbol] = {"order_id": entry_order_id, "created_at_ms": int(time.time() * 1000)}
|
||||
else:
|
||||
# 1) 先挂限价单
|
||||
logger.info(
|
||||
|
|
@ -418,11 +434,10 @@ class PositionManager:
|
|||
filled_quantity = float(res.get("executed_qty") or 0)
|
||||
else:
|
||||
# 未成交(NEW/超时/CANCELED 等)属于“策略未触发入场”或“挂单没成交”
|
||||
# 这不应当当作系统错误;同时需要撤单,避免留下悬挂委托造成后续混乱。
|
||||
# 这不应当当作系统错误;同时需要撤单(best-effort),避免留下悬挂委托造成后续混乱。
|
||||
logger.warning(f"{symbol} [开仓] 未成交,状态: {order_status},跳过本次开仓并撤销挂单")
|
||||
try:
|
||||
if str(order_status).upper() in {"NEW", "PARTIALLY_FILLED", "PENDING_NEW", "TIMEOUT"}:
|
||||
await self.client.cancel_order(symbol, int(entry_order_id))
|
||||
await self.client.cancel_order(symbol, int(entry_order_id))
|
||||
except Exception:
|
||||
pass
|
||||
self._pending_entry_orders.pop(symbol, None)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user