feat(market_cache): 引入市场数据缓存机制以优化API调用

在 `backend/database/models.py` 中新增 `MarketCache` 类,支持从数据库缓存交易对信息和资金费率,减少对币安API的调用频率。更新 `binance_client` 和 `market_scanner` 以优先从缓存读取数据,添加超时处理和重试机制,提升系统稳定性。同时,增强了资金费率和主动买卖量的过滤逻辑,确保在开仓前进行有效的风险控制。
This commit is contained in:
薇薇安 2026-02-16 18:05:11 +08:00
parent 43e993034f
commit 0fb42a5f24
9 changed files with 1388 additions and 34 deletions

View File

@ -0,0 +1,13 @@
-- 市场缓存表:存放较固定的交易所数据(交易对信息、资金费率规则等),减少 API 调用
-- 执行: mysql -u root -p auto_trade_sys < add_market_cache.sql
USE `auto_trade_sys`;
CREATE TABLE IF NOT EXISTS `market_cache` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`cache_key` VARCHAR(128) NOT NULL COMMENT '如 exchange_info, funding_info',
`cache_value` LONGTEXT NOT NULL COMMENT 'JSON 内容',
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY `uk_cache_key` (`cache_key`),
INDEX `idx_updated_at` (`updated_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='市场数据缓存(交易对/费率规则等)';

View File

@ -360,6 +360,128 @@ class TradingConfig:
return str(value)
# 缓存键常量(与 market_cache 表配合使用)
MARKET_CACHE_KEY_EXCHANGE_INFO = "exchange_info"
MARKET_CACHE_KEY_FUNDING_INFO = "funding_info"
class MarketCache:
"""
市场数据缓存交易对信息(exchange_info)资金费率规则(funding_info) 等较固定内容入库
减少对币安 API 的调用trading_system 可优先从 DB 读取过期或缺失时再拉 API 并回写
"""
@staticmethod
def _table_exists():
try:
db.execute_one("SELECT 1 FROM market_cache LIMIT 1")
return True
except Exception:
return False
@staticmethod
def get(cache_key: str):
"""获取缓存:返回 dict { cache_value: 解析后的对象, updated_at } 或 None。"""
if not MarketCache._table_exists():
return None
try:
row = db.execute_one(
"SELECT cache_value, updated_at FROM market_cache WHERE cache_key = %s",
(cache_key,),
)
if not row:
return None
raw = row.get("cache_value")
updated_at = row.get("updated_at")
if raw is None:
return None
try:
value = json.loads(raw) if isinstance(raw, str) else raw
except Exception:
value = raw
return {"cache_value": value, "updated_at": updated_at}
except Exception as e:
logger.debug("MarketCache.get %s: %s", cache_key, e)
return None
@staticmethod
def set(cache_key: str, value) -> bool:
"""写入缓存value 可为 dict/list将序列化为 JSON"""
if not MarketCache._table_exists():
return False
try:
payload = json.dumps(value, ensure_ascii=False) if not isinstance(value, str) else value
db.execute_update(
"""INSERT INTO market_cache (cache_key, cache_value)
VALUES (%s, %s)
ON DUPLICATE KEY UPDATE cache_value = VALUES(cache_value), updated_at = CURRENT_TIMESTAMP""",
(cache_key, payload),
)
return True
except Exception as e:
logger.warning("MarketCache.set %s: %s", cache_key, e)
return False
@staticmethod
def get_exchange_info(max_age_seconds: int = 86400):
"""
获取缓存的 exchange_info币安 GET /fapi/v1/exchangeInfo
若缓存存在且 updated_at max_age_seconds 返回解析后的 exchange_info 字典
否则返回 None调用方应拉 API 并调用 set_exchange_info 回写
"""
out = MarketCache.get(MARKET_CACHE_KEY_EXCHANGE_INFO)
if not out:
return None
updated_at = out.get("updated_at")
if max_age_seconds is not None and updated_at:
try:
from datetime import datetime, timezone, timedelta
if isinstance(updated_at, (int, float)):
utc_ts = float(updated_at)
else:
# datetime 转 UTC 时间戳
utc_ts = updated_at.timestamp() if hasattr(updated_at, "timestamp") else 0
age = datetime.now(timezone.utc).timestamp() - utc_ts
if age > max_age_seconds:
return None
except Exception:
pass
return out.get("cache_value")
@staticmethod
def set_exchange_info(data: dict) -> bool:
"""写入 exchange_info 到 market_cache。"""
return MarketCache.set(MARKET_CACHE_KEY_EXCHANGE_INFO, data)
@staticmethod
def get_funding_info(max_age_seconds: int = 86400):
"""
获取缓存的 funding_info币安 GET /fapi/v1/fundingInfo
若缓存存在且在 max_age_seconds 返回解析后的列表/字典否则返回 None
"""
out = MarketCache.get(MARKET_CACHE_KEY_FUNDING_INFO)
if not out:
return None
updated_at = out.get("updated_at")
if max_age_seconds is not None and updated_at:
try:
from datetime import datetime, timezone
if isinstance(updated_at, (int, float)):
utc_ts = float(updated_at)
else:
utc_ts = updated_at.timestamp() if hasattr(updated_at, "timestamp") else 0
age = datetime.now(timezone.utc).timestamp() - utc_ts
if age > max_age_seconds:
return None
except Exception:
pass
return out.get("cache_value")
@staticmethod
def set_funding_info(data) -> bool:
"""写入 funding_info 到 market_cache。"""
return MarketCache.set(MARKET_CACHE_KEY_FUNDING_INFO, data)
class GlobalStrategyConfig:
"""全局策略配置模型(独立于账户,管理员专用)"""

View File

@ -0,0 +1,58 @@
# 后续方向实施说明
本文档说明「策略与信号评估及改进建议」中未实现或部分实现的方向,以及固定内容入库的用法。
---
## 一、已完成的改动
### 1. 固定内容入库(交易对 / 费率规则)
- **表**`market_cache`(见 `backend/database/add_market_cache.sql`
- 字段:`cache_key`(如 `exchange_info`、`funding_info`)、`cache_value`JSON、`updated_at`
- **模型**`backend/database/models.py` 中的 `MarketCache`
- `get_exchange_info(max_age_seconds=86400)` / `set_exchange_info(data)`
- `get_funding_info(max_age_seconds=86400)` / `set_funding_info(data)`
- **使用**`trading_system/binance_client.py`
- `get_all_usdt_pairs()`:优先从 DB 读 `exchange_info`24 小时内有效),命中则直接解析返回;否则请求 API 并回写 DB。
- `get_symbol_info(symbol)`:在内存/Redis 未命中时,尝试从 DB 的 `exchange_info` 解析该 symbolAPI 拉取后也会回写 DB。
**部署**:执行 `mysql -u root -p auto_trade_sys < backend/database/add_market_cache.sql` 创建表。无需改配置即可生效。
### 2. 扫描阶段资金费率过滤
- **配置**`trading_system/config.py` 与 DB `trading_config`
- `SCAN_FUNDING_RATE_FILTER_ENABLED`:是否启用(默认 True
- `SCAN_FUNDING_RATE_MAX_ABS`:阈值,默认 0.001(即 \|lastFundingRate\| > 0.1% 的标的从扫描候选剔除)
- **逻辑**:在 `market_scanner.scan_market()` 中,在得到「初步筛选后」的 `pre_filtered_symbols` 后,若启用则调用 `get_premium_index(None)` 取全量费率,剔除 \|lastFundingRate\| > 阈值的 symbol再进入详细分析K 线/技术指标)。
---
## 二、建议实现顺序(未实现部分)
| 顺序 | 方向 | 说明 |
|------|----------------|------|
| 1 | 跟踪止损 | 浮盈达到 X% 后,用 `TRAILING_STOP_MARKET` 替代或补充固定止盈;配置:`TRAILING_STOP_ENABLED`、`TRAILING_ACTIVATION_PCT`、`TRAILING_CALLBACK_RATE`;在 position_manager 挂完固定 SL/TP 后,达标则下跟踪止损单。 |
| 2 | 时间/无盈利止损 | 持仓时长 > N 小时且浮盈 < 某阈值或浮亏)→ 市价平仓或提高止损优先级配置`MAX_HOLD_HOURS`、`MIN_PNL_PCT_TO_HOLD` position_manager 的检查循环里按 `entry_time` 与当前 pnl% 判断 |
| 3 | WS 订单/成交优化 | 下单后根据 ORDER_TRADE_UPDATE/TRADE 做快速反馈、部分成交后的加仓/减仓逻辑;实现成本较高,可排在跟踪止损和时间止损之后。 |
---
## 三、跟踪止损(待实现)要点
- **币安**:条件单类型 `TRAILING_STOP_MARKET`,需 `callbackRate`、`activationPrice`(或由系统按比例计算)。
- **配置建议**:与现有 `USE_TRAILING_STOP`、`TRAILING_STOP_ACTIVATION`、`TRAILING_STOP_PROTECT` 对齐,或新增 `TRAILING_ACTIVATION_PCT`、`TRAILING_CALLBACK_RATE` 从 config/DB 读取。
- **位置**`trading_system/position_manager.py` 中,在挂完固定 SL/TP 后(或定时检查持仓时),若启用且当前浮盈% ≥ 激活比例,则下 `TRAILING_STOP_MARKET`;若已有跟踪止损单则跳过。
---
## 四、时间/无盈利止损(待实现)要点
- **逻辑**:在 position_manager 的持仓检查循环中,读取每仓的 `entry_time` 和当前 pnl%;若 `持仓时长 > MAX_HOLD_HOURS``pnl_percent < MIN_PNL_PCT_TO_HOLD`(或为负),则触发市价平仓或标记为「建议平仓」。
- **配置**`MAX_HOLD_HOURS`、`MIN_PNL_PCT_TO_HOLD`,从 config/DB 读。
---
## 五、手续费等固定费率(可选)
若后续要做「预估盈亏」或「最小盈利目标」,可将 maker/taker 费率存入 config 或单独表(如 `symbol_fee`),在算目标价/预期收益时读取。当前未实现。

View File

@ -0,0 +1,82 @@
# 策略与信号评估及改进建议
基于持仓记录2026-02-16、当前策略逻辑与币安接口文档对「长时间持仓、盈利不明显」做简要诊断并给出可落地的改进方向。
---
## 一、持仓与信号现状简要诊断
### 1. 持仓样本(来自 持仓记录_2026-02-16
- **数量**:约 12 个持仓多空混合BUY 少、SELL 多)。
- **盈亏分布**:部分有 +15%、+13%、+9% 等浮盈,多数在 -5%+2% 区间横盘,整体「拿得久、盈利不突出」。
- **共性**
- 入场依赖 1H/4H 技术信号MACD + EMA + 4H 趋势),**无资金费率、多空结构、订单流等过滤**,容易在「结构不利」时开仓。
- 止损/止盈为固定比例或 ATR**无跟踪止损、无时间止损**,盈利单容易回吐,亏损单容易长期挂单。
- 选币主要按 24h 涨跌幅 + 成交量 + signal_strength 排序,**未用资金费率、大户多空比、主动买卖量等** 做过滤或排序。
### 2. 当前信号与选单逻辑(简要)
- **扫描**24h 涨跌幅 ≥ 阈值 + 成交量 ≥ MIN_VOLUME_24H_STRICT → 对每个 symbol 拉 1H/4H K 线 → 算 RSI、MACD、EMA、4H 趋势 → 算 **signal_strength**010按强度排序取 TOP_N。
- **开仓条件**signal_strength ≥ MIN_SIGNAL_STRENGTH8、4H 趋势不逆势、RSI/24h 涨跌幅过滤、短周期方向过滤、大盘 Beta 过滤等。
- **问题点**
- 信号完全来自 K 线技术指标,**没有合约特有的「成本与结构」信息**(资金费率、多空比、主动买卖)→ 容易在费率极高或多空极度拥挤时进场。
- 入场时机只看扫描周期(如 15 分钟一次),**没有用 WS 订单/成交推送做更细的时机优化**。
- 出场只有固定 SL/TP**没有跟踪止损、没有「持仓时间 + 无盈利」类时间止损**。
---
## 二、基于现有接口的改进方向(可落地)
### 1. 资金费率过滤(已接入 REST建议**优先上**
- **接口**`GET /fapi/v1/premiumIndex`(已封装为 `get_premium_index(symbol)`),含 `lastFundingRate`
- **思路**
- **做多**:若 `lastFundingRate > 某上限`(例如 0.001,即 0.1%),说明多头付费高,开多成本高、易被费率侵蚀 → **跳过开多**
- **做空**:若 `lastFundingRate < 某下限`(例如 -0.001),说明空头付费高 → **跳过开空**
- **效果**:减少在「费率极端不利于己方」时开仓,提高单笔期望收益;配合现有技术信号,不改变主逻辑,只做过滤。
- **实现**:在策略层开仓前增加「资金费率过滤」步骤(见下节代码)。
### 2. 大户多空比 / 主动买卖量(已接入 REST可选增强
- **接口**`get_top_long_short_position_ratio`、`get_taker_long_short_ratio`(或账户数多空比)。
- **思路**
- **做多**:优先选择「近期主动买入占比高」或「大户多空比」偏多的标的(顺势);或极端多头拥挤时做「逆势空」需谨慎、可提高信号门槛。
- **做空**:优先选择「主动卖出占比高」或大户偏空的标的。
- **实现**:可在扫描后、或策略决定开仓前,对候选 symbol 取最近 1 条 taker 或大户多空比,与方向一致则通过、相反则降权或跳过(可选,先上资金费率再考虑)。
### 3. 订单/成交推送WS对「下单时机」的改进
- **文档**订单交易更新推送ORDER_TRADE_UPDATE、条件单更新ALGO_UPDATE、账户信息流等。
- **思路**
- 已有 User Data Stream 时,**订单状态、成交回报**已经实时;可在此基础上做「下单后快速反馈、部分成交/全部成交的后续动作」。
- 若引入 **aggTrade / depth** 等行情 WS可在「信号已满足」时结合最近几笔大单或盘口变化再点一次火减少「信号刚满足就追在尖顶/尖底」的情况(实现成本较高,可作为二期)。
### 4. 止盈/止损与持仓管理
- **跟踪止损**:文档支持 `TRAILING_STOP_MARKET`(激活价、回调比例)。可在「浮盈达到 X% 后」改为跟踪止损,锁定利润、让利润奔跑。
- **时间止损**:若持仓超过 N 小时且浮盈 < 某阈值或浮亏可考虑平仓释放保证金避免长期无效挂单需在 position_manager 或策略层加持仓时长判断)。
- **分批止盈**:当前有 take_profit_1/2 等设计;可结合 ATR 或固定比例,先平一部分、留一部分用跟踪止损(逻辑已有基础,可逐步细化)。
### 5. 扫描与选币
- **资金费率**:扫描阶段可排除「费率极端」的标的(例如 |lastFundingRate| > 0.001),避免进入 TOP_N 的 symbol 本身就不适合当前方向。
- **成交量/流动性**:继续用 24h 成交量 + 深度(若有)保证可执行性;深度接口已接入,可用于大单前的冲击成本估算。
- **多空比/主动买卖**:作为排序加分项(与方向一致则排前),而不是硬过滤,避免过度减少开仓次数。
---
## 三、建议实施顺序
1. **立即**:在开仓前增加**资金费率过滤**(做多/做空各一个阈值),并加配置开关与日志。
2. **短期**:在配置中提供**跟踪止损**开关与参数(如激活盈利率、回调比例),对已有持仓在达到条件时切到跟踪止损(或先对部分新开仓做实验)。
3. **中期**:在扫描或策略中引入**大户多空比 / taker 买卖比**,作为过滤或排序加分,并观察胜率与频率变化。
4. **可选**:结合 WS 订单/成交与行情aggTrade/depth做更细的入场时机优化以及「持仓时间 + 无盈利」的时间止损。
---
## 四、小结
- **现状**:信号与选单主要依赖 K 线技术指标和 4H 趋势,缺少「资金费率、多空结构、订单流」等维度,且出场方式单一,容易导致持仓时间长、盈利不明显。
- **改进**:在**不推翻现有技术信号**的前提下,优先用**资金费率**做开仓过滤,再用**多空比/主动买卖**做过滤或排序,并逐步加上**跟踪止损**和**时间/无盈利止损**,有望在控制回撤的同时提高单笔质量与资金效率。
- 上述改进均可基于你已接入的 REST 接口premiumIndex、fundingRate、topLongShort、takerlongshortRatio 等)和现有 WS 文档实现;具体参数(费率阈值、跟踪止损比例等)可在实盘或回测中微调。

View File

@ -5,7 +5,9 @@ import asyncio
import json
import logging
import random
import sys
import time
from pathlib import Path
from typing import Dict, List, Optional, Any
from binance import AsyncClient, BinanceSocketManager
from binance.exceptions import BinanceAPIException
@ -19,6 +21,92 @@ except ImportError:
logger = logging.getLogger(__name__)
# 交易对/费率等固定内容优先从 DB 读取market_cache 表),减少 API 调用
def _get_market_cache():
"""延迟导入 MarketCache需 backend 在 sys.path。失败返回 None。"""
try:
backend_path = Path(__file__).resolve().parent.parent / "backend"
if backend_path.exists() and str(backend_path) not in sys.path:
sys.path.insert(0, str(backend_path))
from database.models import MarketCache
return MarketCache
except Exception as e:
logger.debug("MarketCache 不可用: %s", e)
return None
def _load_exchange_info_from_db(max_age_seconds: int = 86400):
"""同步从 DB 读取 exchange_info供 run_in_executor 调用)。"""
mc = _get_market_cache()
if not mc:
return None
return mc.get_exchange_info(max_age_seconds=max_age_seconds)
def _save_exchange_info_to_db(data: dict) -> bool:
"""同步将 exchange_info 写入 DB。"""
mc = _get_market_cache()
if not mc:
return False
return mc.set_exchange_info(data)
def _parse_usdt_pairs_from_exchange_info(exchange_info: dict) -> tuple:
"""从 exchange_info 解析 USDT 永续列表和 display_to_api 映射。返回 (usdt_pairs, display_to_api_symbol)."""
if not exchange_info or not isinstance(exchange_info.get("symbols"), list):
return [], {}
usdt_pairs = []
display_to_api = {}
for s in exchange_info["symbols"]:
if not (s.get("symbol", "").endswith("USDT") and s.get("status") == "TRADING" and s.get("contractType") == "PERPETUAL"):
continue
sym = s["symbol"]
if sym.isascii():
usdt_pairs.append(sym)
continue
api_sym = (s.get("baseAsset") or "") + (s.get("quoteAsset") or "")
if api_sym and api_sym.isascii():
usdt_pairs.append(api_sym)
display_to_api[sym] = api_sym
else:
usdt_pairs.append(sym)
return usdt_pairs, display_to_api
def _parse_symbol_info_from_exchange_info(exchange_info: dict, symbol: str) -> Optional[Dict]:
"""从 exchange_info 中解析单个 symbol 的精度/限制信息,与 get_symbol_info 返回格式一致。"""
if not exchange_info or not isinstance(exchange_info.get("symbols"), list):
return None
for s in exchange_info["symbols"]:
if s.get("symbol") != symbol:
continue
quantity_precision = s.get("quantityPrecision", 8)
price_precision = s.get("pricePrecision", 8)
min_qty, step_size, min_notional, tick_size = None, None, None, None
for f in s.get("filters", []):
if f.get("filterType") == "LOT_SIZE":
min_qty = float(f.get("minQty", 0))
step_size = float(f.get("stepSize", 0))
elif f.get("filterType") == "PRICE_FILTER":
tick_size = float(f.get("tickSize", 0) or 0)
elif f.get("filterType") == "MIN_NOTIONAL":
min_notional = float(f.get("notional", 0))
if min_notional is None or min_notional == 0:
min_notional = 5.0
max_leverage_supported = 125
if s.get("leverageBracket") and len(s.get("leverageBracket", [])) > 0:
max_leverage_supported = s["leverageBracket"][0].get("maxLeverage", 125)
return {
"quantityPrecision": quantity_precision,
"pricePrecision": price_precision,
"minQty": min_qty or 0,
"stepSize": step_size or 0,
"tickSize": tick_size or 0,
"minNotional": min_notional,
"maxLeverage": int(max_leverage_supported),
}
return None
def _format_exception(e: Exception) -> str:
"""格式化异常用于日志,避免空 message 导致日志无内容"""
@ -458,7 +546,8 @@ class BinanceClient:
async def get_all_usdt_pairs(self, max_retries: int = 3, timeout: int = 30) -> List[str]:
"""
获取所有USDT交易对
添加超时处理和重试机制避免推荐系统因网络超时中断
优先从 DB market_cache 读取 exchange_info24 小时内有效减少 API 调用
添加超时处理和重试机制避免推荐系统因网络超时中断
Args:
max_retries: 最大重试次数默认3次
@ -467,10 +556,24 @@ class BinanceClient:
Returns:
USDT交易对列表失败时返回空列表
"""
loop = asyncio.get_event_loop()
# 优先从 DB 读取(同步调用放至线程池)
try:
exchange_info = await loop.run_in_executor(None, lambda: _load_exchange_info_from_db(86400))
if exchange_info and isinstance(exchange_info.get("symbols"), list):
usdt_pairs, display_to_api = _parse_usdt_pairs_from_exchange_info(exchange_info)
if usdt_pairs:
self._display_to_api_symbol.clear()
self._display_to_api_symbol.update(display_to_api)
if display_to_api:
logger.info(f"已映射 {len(display_to_api)} 个中文/非ASCII交易对到英文 symbol")
logger.info(f"从 DB 缓存获取到 {len(usdt_pairs)} 个USDT永续合约交易对")
return usdt_pairs
except Exception as e:
logger.debug("从 DB 读取 exchange_info 失败,将请求 API: %s", e)
for attempt in range(1, max_retries + 1):
try:
# 使用 _rate_limited_request 包装请求,添加速率限制
# 同时使用 asyncio.wait_for 添加超时处理
exchange_info = await asyncio.wait_for(
self._rate_limited_request(
'futures_exchange_info',
@ -478,35 +581,17 @@ class BinanceClient:
),
timeout=timeout
)
# 支持中文名交易对:若 symbol 含非 ASCII尝试用 baseAsset+quoteAsset 作为 API 下单用 symbol
# 如果 baseAsset+quoteAsset 也是非 ASCII则直接使用原始 symbol
usdt_pairs, display_to_api = _parse_usdt_pairs_from_exchange_info(exchange_info)
self._display_to_api_symbol.clear()
usdt_pairs = []
for s in exchange_info['symbols']:
if not (s['symbol'].endswith('USDT') and s['status'] == 'TRADING' and s.get('contractType') == 'PERPETUAL'):
continue
sym = s['symbol']
# 1. 如果是 ASCII直接使用
if sym.isascii():
usdt_pairs.append(sym)
continue
# 2. 如果非 ASCII尝试构建英文 symbol (base+quote)
api_sym = (s.get('baseAsset') or '') + (s.get('quoteAsset') or '')
if api_sym and api_sym.isascii():
usdt_pairs.append(api_sym)
self._display_to_api_symbol[sym] = api_sym
logger.debug(f"交易对显示名 -> API symbol: '{sym}' -> '{api_sym}'")
else:
# 3. 如果 base+quote 也是非 ASCII如 '币安人生USDT'),则直接使用原始 symbol
# 用户反馈希望交易这些特殊币种
usdt_pairs.append(sym)
logger.info(f"添加非ASCII交易对: {sym!r} (baseAsset={s.get('baseAsset')!r})")
if self._display_to_api_symbol:
logger.info(f"已映射 {len(self._display_to_api_symbol)} 个中文/非ASCII交易对到英文 symbol均可正常下单")
self._display_to_api_symbol.update(display_to_api)
if display_to_api:
logger.info(f"已映射 {len(display_to_api)} 个中文/非ASCII交易对到英文 symbol均可正常下单")
logger.info(f"获取到 {len(usdt_pairs)} 个USDT永续合约交易对")
# 回写 DB 供下次使用
try:
await loop.run_in_executor(None, lambda: _save_exchange_info_to_db(exchange_info))
except Exception as e:
logger.debug("exchange_info 写入 DB 失败: %s", e)
return usdt_pairs
except asyncio.TimeoutError:
@ -1112,6 +1197,7 @@ class BinanceClient:
交易对信息字典包含 quantityPrecision, minQty, stepSize
"""
symbol = self._resolve_api_symbol(symbol)
loop = asyncio.get_event_loop()
# 1. 先检查内存缓存
if symbol in self._symbol_info_cache:
cached_mem = self._symbol_info_cache[symbol]
@ -1133,11 +1219,23 @@ class BinanceClient:
if isinstance(cached, dict) and ("tickSize" not in cached or "pricePrecision" not in cached):
logger.info(f"{symbol} symbol_info 缓存缺少 tickSize/pricePrecision自动刷新一次")
else:
# 同时更新内存缓存
self._symbol_info_cache[symbol] = cached
return cached
# 3. 缓存未命中,调用 API加超时与重试避免网络抖动导致开仓前获取交易对信息失败
# 3. 尝试从 DB market_cache 读取 exchange_info 并解析本 symbol
try:
exchange_info_db = await loop.run_in_executor(None, lambda: _load_exchange_info_from_db(86400))
info = _parse_symbol_info_from_exchange_info(exchange_info_db, symbol) if exchange_info_db else None
if info:
self._symbol_info_cache[symbol] = info
if self.redis_cache:
await self.redis_cache.set(cache_key, info, ttl=3600)
logger.debug(f"从 DB 缓存解析 {symbol} 交易对信息")
return info
except Exception as e:
logger.debug("从 DB 读取 exchange_info 解析 %s 失败: %s", symbol, e)
# 4. 缓存未命中,调用 API加超时与重试
read_timeout = getattr(config, 'READ_ONLY_REQUEST_TIMEOUT', 60)
last_err = None
for attempt in range(3):
@ -1146,6 +1244,11 @@ class BinanceClient:
self.client.futures_exchange_info(),
timeout=read_timeout
)
# 回写 DB 供后续请求使用
try:
await loop.run_in_executor(None, lambda: _save_exchange_info_to_db(exchange_info))
except Exception as e:
logger.debug("exchange_info 写入 DB 失败: %s", e)
break
except (asyncio.TimeoutError, BinanceAPIException, OSError) as e:
last_err = e

View File

@ -231,6 +231,17 @@ DEFAULT_TRADING_CONFIG = {
'MIN_VOLUME_24H_STRICT': 50000000, # 严格过滤≥5000万美元
'MIN_VOLATILITY': 0.03, # 最小波动率3%,过滤死币
'MIN_SIGNAL_STRENGTH': 8, # 信号强度≥82026-01-29优化从7提高到8减少低质量信号提升胜率
# ===== 资金费率过滤(开仓前)=====
'FUNDING_RATE_FILTER_ENABLED': True, # 开仓前检查资金费率,避免在费率极端不利于己方时进场
'MAX_FUNDING_RATE_FOR_LONG': 0.001, # 做多时 lastFundingRate 超过此值则跳过0.1%=多头付费高)
'MIN_FUNDING_RATE_FOR_SHORT': -0.001, # 做空时 lastFundingRate 低于此值则跳过(-0.1%=空头付费高)
# 扫描阶段资金费率过滤:排除 |lastFundingRate| 过大的标的,避免进入 TOP_N
'SCAN_FUNDING_RATE_FILTER_ENABLED': True,
'SCAN_FUNDING_RATE_MAX_ABS': 0.001, # 绝对值超过 0.1% 则从扫描候选剔除
# 主动买卖量过滤(可选):与方向一致时通过,极端反向时跳过
'TAKER_RATIO_FILTER_ENABLED': False, # 默认关闭,开启后做多要求 buySellRatio≥下限、做空要求≤上限
'MIN_TAKER_BUY_RATIO_FOR_LONG': 0.85, # 做多时最近1h buySellRatio 低于此值则跳过(主动买不足)
'MAX_TAKER_BUY_RATIO_FOR_SHORT': 1.15, # 做空时最近1h buySellRatio 高于此值则跳过(主动卖不足)
# ===== 动态过滤优化 =====
'BETA_FILTER_ENABLED': True, # 大盘共振过滤BTC/ETH下跌时屏蔽多单

View File

@ -119,7 +119,35 @@ class MarketScanner:
pre_filtered_symbols.append(symbol)
logger.info(f"初步筛选后,需要详细分析的交易对: {len(pre_filtered_symbols)}")
# 扫描阶段资金费率过滤:排除 |lastFundingRate| 过大的标的(文档建议的后续方向)
scan_funding_enabled = cfg.get('SCAN_FUNDING_RATE_FILTER_ENABLED', False)
scan_funding_max_abs = float(cfg.get('SCAN_FUNDING_RATE_MAX_ABS', 0.001))
if scan_funding_enabled and pre_filtered_symbols:
try:
premium_all = await self.client.get_premium_index(None) # 全量,一次请求
rate_map = {}
if isinstance(premium_all, list):
for item in premium_all:
if isinstance(item, dict) and 'symbol' in item:
try:
rate_map[item['symbol']] = float(item.get('lastFundingRate', 0))
except (TypeError, ValueError):
pass
elif isinstance(premium_all, dict) and premium_all.get('symbol'):
rate_map[premium_all['symbol']] = float(premium_all.get('lastFundingRate', 0))
if rate_map:
before = len(pre_filtered_symbols)
pre_filtered_symbols = [
s for s in pre_filtered_symbols
if abs(rate_map.get(s, 0)) <= scan_funding_max_abs
]
excluded = before - len(pre_filtered_symbols)
if excluded > 0:
logger.info(f"扫描阶段资金费率过滤: 排除 {excluded} 个标的 (|lastFundingRate| > {scan_funding_max_abs}),剩余 {len(pre_filtered_symbols)}")
except Exception as e:
logger.debug("扫描阶段资金费率过滤失败,继续使用原列表: %s", e)
# 只对符合条件的交易对进行详细分析获取K线和技术指标
# ⚠️ 并发数说明:
# - 这是单个账户扫描时,同时分析多少个交易对(不是用户进程数)

View File

@ -178,6 +178,14 @@ class TradingStrategy:
# 确定交易方向(基于技术指标)
trade_direction = trade_signal['direction']
# 开仓前资金费率过滤:避免在费率极端不利于己方时进场
if not await self._check_funding_rate_filter(symbol, trade_direction):
logger.info(f"{symbol} 资金费率过滤未通过,跳过开仓")
continue
# 开仓前主动买卖量过滤(可选):与方向一致时通过
if not await self._check_taker_ratio_filter(symbol, trade_direction):
logger.info(f"{symbol} 主动买卖量过滤未通过,跳过开仓")
continue
entry_reason = trade_signal['reason']
signal_strength = trade_signal.get('strength', 0)
@ -353,6 +361,65 @@ class TradingStrategy:
await asyncio.sleep(check_interval)
waited += check_interval
async def _check_funding_rate_filter(self, symbol: str, direction: str) -> bool:
"""
开仓前资金费率过滤避免在费率极端不利于己方时进场做多时费率过高做空时费率过负则跳过
依赖 REST get_premium_index(symbol) lastFundingRate
"""
enabled = bool(config.TRADING_CONFIG.get('FUNDING_RATE_FILTER_ENABLED', True))
if not enabled:
return True
try:
data = await self.client.get_premium_index(symbol)
if not data or not isinstance(data, dict):
return True
rate_raw = data.get('lastFundingRate')
if rate_raw is None:
return True
rate = float(rate_raw)
max_long = float(config.TRADING_CONFIG.get('MAX_FUNDING_RATE_FOR_LONG', 0.001))
min_short = float(config.TRADING_CONFIG.get('MIN_FUNDING_RATE_FOR_SHORT', -0.001))
if direction == 'BUY' and rate > max_long:
logger.info(f"{symbol} 资金费率过滤做多跳过lastFundingRate={rate:.6f} > {max_long}")
return False
if direction == 'SELL' and rate < min_short:
logger.info(f"{symbol} 资金费率过滤做空跳过lastFundingRate={rate:.6f} < {min_short}")
return False
return True
except Exception as e:
logger.debug(f"{symbol} 资金费率检查失败(放行): {e}")
return True
async def _check_taker_ratio_filter(self, symbol: str, direction: str) -> bool:
"""
开仓前主动买卖量过滤可选做多要求近期主动买占比不低于下限做空要求主动卖占比不低于下限
依赖 get_taker_long_short_ratio(symbol, period='1h', limit=1)取最近一条的 buySellRatio
"""
enabled = bool(config.TRADING_CONFIG.get('TAKER_RATIO_FILTER_ENABLED', False))
if not enabled:
return True
try:
arr = await self.client.get_taker_long_short_ratio(symbol, period='1h', limit=3)
if not arr or not isinstance(arr, list):
return True
latest = arr[0] if arr else {}
ratio_raw = latest.get('buySellRatio')
if ratio_raw is None:
return True
ratio = float(ratio_raw)
min_long = float(config.TRADING_CONFIG.get('MIN_TAKER_BUY_RATIO_FOR_LONG', 0.85))
max_short = float(config.TRADING_CONFIG.get('MAX_TAKER_BUY_RATIO_FOR_SHORT', 1.15))
if direction == 'BUY' and ratio < min_long:
logger.info(f"{symbol} 主动买卖量过滤做多跳过buySellRatio={ratio:.3f} < {min_long}")
return False
if direction == 'SELL' and ratio > max_short:
logger.info(f"{symbol} 主动买卖量过滤做空跳过buySellRatio={ratio:.3f} > {max_short}")
return False
return True
except Exception as e:
logger.debug(f"{symbol} 主动买卖量检查失败(放行): {e}")
return True
async def _check_volume_confirmation(self, symbol_info: Dict) -> bool:
"""
成交量确认 - 确保有足够的成交量支撑

View File

@ -0,0 +1,870 @@
[
{
"id": 5110,
"symbol": "1000BONKUSDT",
"side": "BUY",
"quantity": 3531,
"entry_price": 0.006565,
"entry_value_usdt": 23.181015,
"notional_usdt": 23.459964,
"margin_usdt": 5.864991,
"original_notional_usdt": 23.181015,
"original_margin_usdt": 5.79525375,
"mark_price": 0.006644,
"pnl": 0.278949,
"pnl_percent": 4.7561709813365445,
"leverage": 4,
"entry_time": 1771214454,
"stop_loss_price": 0.00636805,
"take_profit_price": 0.00697531,
"take_profit_1": null,
"take_profit_2": null,
"atr": null,
"entry_order_id": null,
"entry_order_type": null,
"open_orders": [
{
"orderId": 2000000460584754,
"type": "TAKE_PROFIT_MARKET",
"side": "SELL",
"stopPrice": 0.006975,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460584735,
"type": "STOP_MARKET",
"side": "SELL",
"stopPrice": 0.006369,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
],
"active_sl_orders": "STOP_MARKET @ 0.006369 (NEW)",
"active_tp_orders": "TAKE_PROFIT_MARKET @ 0.006975 (NEW)",
"binance_open_orders_raw": [
{
"orderId": 2000000460584754,
"type": "TAKE_PROFIT_MARKET",
"side": "SELL",
"stopPrice": 0.006975,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460584735,
"type": "STOP_MARKET",
"side": "SELL",
"stopPrice": 0.006369,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
]
},
{
"id": 5157,
"symbol": "ZECUSDT",
"side": "SELL",
"quantity": 0.077,
"entry_price": 284.54,
"entry_value_usdt": 21.909580000000002,
"notional_usdt": 22.23683,
"margin_usdt": 5.5592075,
"original_notional_usdt": 21.92881912,
"original_margin_usdt": 5.48220478,
"mark_price": 288.79,
"pnl": -0.32725,
"pnl_percent": -5.886630423491117,
"leverage": 4,
"entry_time": 1771226616,
"stop_loss_price": null,
"take_profit_price": null,
"take_profit_1": null,
"take_profit_2": null,
"atr": 14.62428571,
"entry_order_id": 797717260491,
"entry_order_type": "LIMIT",
"open_orders": [
{
"orderId": 2000000460584806,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 266.76,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460584795,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 293.07,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
],
"active_sl_orders": "STOP_MARKET @ 293.07 (NEW)",
"active_tp_orders": "TAKE_PROFIT_MARKET @ 266.76 (NEW)",
"binance_open_orders_raw": [
{
"orderId": 2000000460584806,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 266.76,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460584795,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 293.07,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
]
},
{
"id": 5109,
"symbol": "HYPEUSDT",
"side": "SELL",
"quantity": 0.7,
"entry_price": 30.158,
"entry_value_usdt": 21.110599999999998,
"notional_usdt": 20.984715002999998,
"margin_usdt": 5.2461787507499995,
"original_notional_usdt": 21.1106,
"original_margin_usdt": 5.27765,
"mark_price": 29.97816429,
"pnl": 0.12588499,
"pnl_percent": 2.3995558668679244,
"leverage": 4,
"entry_time": 1771214453,
"stop_loss_price": null,
"take_profit_price": null,
"take_profit_1": null,
"take_profit_2": null,
"atr": null,
"entry_order_id": 5837357832,
"entry_order_type": "LIMIT",
"open_orders": [
{
"orderId": 2000000460584837,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 28.274,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460584833,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 31.062,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
],
"active_sl_orders": "STOP_MARKET @ 31.062 (NEW)",
"active_tp_orders": "TAKE_PROFIT_MARKET @ 28.274 (NEW)",
"binance_open_orders_raw": [
{
"orderId": 2000000460584837,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 28.274,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460584833,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 31.062,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
]
},
{
"id": 5112,
"symbol": "FILUSDT",
"side": "SELL",
"quantity": 22,
"entry_price": 0.963,
"entry_value_usdt": 21.186,
"notional_usdt": 21.37234946,
"margin_usdt": 5.343087365,
"original_notional_usdt": 21.186,
"original_margin_usdt": 5.2965,
"mark_price": 0.97147043,
"pnl": -0.18634946,
"pnl_percent": -3.4876738348073033,
"leverage": 4,
"entry_time": 1771214455,
"stop_loss_price": 0.99189,
"take_profit_price": 0.9028125,
"take_profit_1": null,
"take_profit_2": null,
"atr": null,
"entry_order_id": null,
"entry_order_type": null,
"open_orders": [
{
"orderId": 2000000460584866,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 0.903,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460584857,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 0.991,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
],
"active_sl_orders": "STOP_MARKET @ 0.991 (NEW)",
"active_tp_orders": "TAKE_PROFIT_MARKET @ 0.903 (NEW)",
"binance_open_orders_raw": [
{
"orderId": 2000000460584866,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 0.903,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460584857,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 0.991,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
]
},
{
"id": 5113,
"symbol": "TAKEUSDT",
"side": "SELL",
"quantity": 172,
"entry_price": 0.03629,
"entry_value_usdt": 6.24188,
"notional_usdt": 5.387599,
"margin_usdt": 5.387599,
"original_notional_usdt": 6.24188,
"original_margin_usdt": 6.24188,
"mark_price": 0.03132325,
"pnl": 0.854281,
"pnl_percent": 15.856432522168037,
"leverage": 1,
"entry_time": 1771214456,
"stop_loss_price": 0.0373787,
"take_profit_price": 0.0272175,
"take_profit_1": null,
"take_profit_2": null,
"atr": null,
"entry_order_id": null,
"entry_order_type": null,
"open_orders": [
{
"orderId": 2000000460584904,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 0.02722,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460584895,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 0.03737,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
],
"active_sl_orders": "STOP_MARKET @ 0.03737 (NEW)",
"active_tp_orders": "TAKE_PROFIT_MARKET @ 0.02722 (NEW)",
"binance_open_orders_raw": [
{
"orderId": 2000000460584904,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 0.02722,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460584895,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 0.03737,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
]
},
{
"id": 5135,
"symbol": "ZROUSDT",
"side": "SELL",
"quantity": 13.5,
"entry_price": 1.6807,
"entry_value_usdt": 22.68945,
"notional_usdt": 22.76581086,
"margin_usdt": 5.691452715,
"original_notional_usdt": 22.68945,
"original_margin_usdt": 5.6723625,
"mark_price": 1.68635636,
"pnl": -0.07636086,
"pnl_percent": -1.3416760855932017,
"leverage": 4,
"entry_time": 1771223082,
"stop_loss_price": 1.731121,
"take_profit_price": 1.57565625,
"take_profit_1": 1.57565625,
"take_profit_2": 1.49288286,
"atr": 0.07825714,
"entry_order_id": 6021914660,
"entry_order_type": "LIMIT",
"open_orders": [
{
"orderId": 2000000460584948,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 1.4929,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460584947,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 1.7311,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
],
"active_sl_orders": "STOP_MARKET @ 1.7311 (NEW)",
"active_tp_orders": "TAKE_PROFIT_MARKET @ 1.4929 (NEW)",
"binance_open_orders_raw": [
{
"orderId": 2000000460584948,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 1.4929,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460584947,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 1.7311,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
]
},
{
"id": 5115,
"symbol": "AXSUSDT",
"side": "SELL",
"quantity": 18,
"entry_price": 1.388,
"entry_value_usdt": 24.983999999999998,
"notional_usdt": 24.42318858,
"margin_usdt": 6.105797145,
"original_notional_usdt": 24.984,
"original_margin_usdt": 6.246,
"mark_price": 1.35684381,
"pnl": 0.56081142,
"pnl_percent": 9.184900950390155,
"leverage": 4,
"entry_time": 1771214457,
"stop_loss_price": 1.42964,
"take_profit_price": 1.30125,
"take_profit_1": null,
"take_profit_2": null,
"atr": null,
"entry_order_id": null,
"entry_order_type": null,
"open_orders": [
{
"orderId": 2000000460584986,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 1.302,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460584975,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 1.429,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
],
"active_sl_orders": "STOP_MARKET @ 1.429 (NEW)",
"active_tp_orders": "TAKE_PROFIT_MARKET @ 1.302 (NEW)",
"binance_open_orders_raw": [
{
"orderId": 2000000460584986,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 1.302,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460584975,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 1.429,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
]
},
{
"id": 5137,
"symbol": "XLMUSDT",
"side": "SELL",
"quantity": 127,
"entry_price": 0.16858,
"entry_value_usdt": 21.409660000000002,
"notional_usdt": 21.68884283,
"margin_usdt": 5.4222107075,
"original_notional_usdt": 21.40966,
"original_margin_usdt": 5.352415,
"mark_price": 0.17077829,
"pnl": -0.27918283,
"pnl_percent": -5.1488746022694105,
"leverage": 4,
"entry_time": 1771223321,
"stop_loss_price": 0.1736374,
"take_profit_price": 0.15804375,
"take_profit_1": 0.15804375,
"take_profit_2": 0.15550343,
"atr": 0.00544857,
"entry_order_id": 20994894391,
"entry_order_type": "LIMIT",
"open_orders": [
{
"orderId": 2000000460585021,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 0.15551,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460585020,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 0.17363,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
],
"active_sl_orders": "STOP_MARKET @ 0.17363 (NEW)",
"active_tp_orders": "TAKE_PROFIT_MARKET @ 0.15551 (NEW)",
"binance_open_orders_raw": [
{
"orderId": 2000000460585021,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 0.15551,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460585020,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 0.17363,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
]
},
{
"id": 5116,
"symbol": "DASHUSDT",
"side": "SELL",
"quantity": 0.571,
"entry_price": 37.25,
"entry_value_usdt": 21.26975,
"notional_usdt": 21.304009999999998,
"margin_usdt": 5.3260024999999995,
"original_notional_usdt": 21.26975,
"original_margin_usdt": 5.3174375,
"mark_price": 37.31,
"pnl": -0.03426,
"pnl_percent": -0.6432591798445457,
"leverage": 4,
"entry_time": 1771214458,
"stop_loss_price": 38.3675,
"take_profit_price": 34.921875,
"take_profit_1": null,
"take_profit_2": null,
"atr": null,
"entry_order_id": null,
"entry_order_type": null,
"open_orders": [
{
"orderId": 2000000460585100,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 34.93,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460585093,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 38.36,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
],
"active_sl_orders": "STOP_MARKET @ 38.36 (NEW)",
"active_tp_orders": "TAKE_PROFIT_MARKET @ 34.93 (NEW)",
"binance_open_orders_raw": [
{
"orderId": 2000000460585100,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 34.93,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460585093,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 38.36,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
]
},
{
"id": 5117,
"symbol": "USELESSUSDT",
"side": "BUY",
"quantity": 150,
"entry_price": 0.04083,
"entry_value_usdt": 6.124499999999999,
"notional_usdt": 7.085999999999999,
"margin_usdt": 7.085999999999999,
"original_notional_usdt": 6.1245,
"original_margin_usdt": 6.1245,
"mark_price": 0.04724,
"pnl": 0.9615,
"pnl_percent": 13.56900931414056,
"leverage": 1,
"entry_time": 1771214459,
"stop_loss_price": 0.0396051,
"take_profit_price": 0.0510375,
"take_profit_1": null,
"take_profit_2": null,
"atr": null,
"entry_order_id": null,
"entry_order_type": null,
"open_orders": [
{
"orderId": 2000000460585145,
"type": "TAKE_PROFIT_MARKET",
"side": "SELL",
"stopPrice": 0.05103,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460585138,
"type": "STOP_MARKET",
"side": "SELL",
"stopPrice": 0.03961,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
],
"active_sl_orders": "STOP_MARKET @ 0.03961 (NEW)",
"active_tp_orders": "TAKE_PROFIT_MARKET @ 0.05103 (NEW)",
"binance_open_orders_raw": [
{
"orderId": 2000000460585145,
"type": "TAKE_PROFIT_MARKET",
"side": "SELL",
"stopPrice": 0.05103,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460585138,
"type": "STOP_MARKET",
"side": "SELL",
"stopPrice": 0.03961,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
]
},
{
"id": 5118,
"symbol": "ENAUSDT",
"side": "SELL",
"quantity": 194,
"entry_price": 0.1196,
"entry_value_usdt": 23.2024,
"notional_usdt": 23.53230864,
"margin_usdt": 5.88307716,
"original_notional_usdt": 23.2024,
"original_margin_usdt": 5.8006,
"mark_price": 0.12130056,
"pnl": -0.32990864,
"pnl_percent": -5.607756468725288,
"leverage": 4,
"entry_time": 1771214460,
"stop_loss_price": 0.123188,
"take_profit_price": 0.112125,
"take_profit_1": null,
"take_profit_2": null,
"atr": null,
"entry_order_id": null,
"entry_order_type": null,
"open_orders": [
{
"orderId": 2000000460585199,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 0.1122,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460585168,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 0.1231,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
],
"active_sl_orders": "STOP_MARKET @ 0.1231 (NEW)",
"active_tp_orders": "TAKE_PROFIT_MARKET @ 0.1122 (NEW)",
"binance_open_orders_raw": [
{
"orderId": 2000000460585199,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 0.1122,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460585168,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 0.1231,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
]
},
{
"id": 5151,
"symbol": "WLFIUSDT",
"side": "SELL",
"quantity": 219,
"entry_price": 0.1011,
"entry_value_usdt": 22.1409,
"notional_usdt": 22.23684171,
"margin_usdt": 5.5592104275,
"original_notional_usdt": 22.1409,
"original_margin_usdt": 22.1409,
"mark_price": 0.10153809,
"pnl": -0.09594171,
"pnl_percent": -1.7258154058245532,
"leverage": 4,
"entry_time": 1771226264,
"stop_loss_price": null,
"take_profit_price": null,
"take_profit_1": null,
"take_profit_2": null,
"atr": null,
"entry_order_id": 2846730648,
"entry_order_type": "LIMIT",
"open_orders": [],
"active_sl_orders": "",
"active_tp_orders": "",
"binance_open_orders_raw": []
},
{
"id": 5123,
"symbol": "WLDUSDT",
"side": "SELL",
"quantity": 56,
"entry_price": 0.4028,
"entry_value_usdt": 22.5568,
"notional_usdt": 22.65973696,
"margin_usdt": 5.66493424,
"original_notional_usdt": 22.5568,
"original_margin_usdt": 5.6392,
"mark_price": 0.40463816,
"pnl": -0.10293696,
"pnl_percent": -1.8170901133002384,
"leverage": 4,
"entry_time": 1771214479,
"stop_loss_price": 0.414884,
"take_profit_price": 0.377625,
"take_profit_1": null,
"take_profit_2": null,
"atr": null,
"entry_order_id": null,
"entry_order_type": null,
"open_orders": [
{
"orderId": 2000000460523250,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 0.3777,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460523242,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 0.4148,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
],
"active_sl_orders": "STOP_MARKET @ 0.4148 (NEW)",
"active_tp_orders": "TAKE_PROFIT_MARKET @ 0.3777 (NEW)",
"binance_open_orders_raw": [
{
"orderId": 2000000460523250,
"type": "TAKE_PROFIT_MARKET",
"side": "BUY",
"stopPrice": 0.3777,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
},
{
"orderId": 2000000460523242,
"type": "STOP_MARKET",
"side": "BUY",
"stopPrice": 0.4148,
"price": 0,
"origType": "CONDITIONAL",
"reduceOnly": true,
"status": "NEW"
}
]
}
]