Signed-off-by: TIANHE <TIANHE@GMAIL.COM>
This commit is contained in:
TIANHE
2026-02-27 23:34:13 +08:00
parent d92d4c53dc
commit cf58ff40fa
31 changed files with 138 additions and 64 deletions
+45 -9
View File
@@ -1656,6 +1656,12 @@ def _analyze_opportunities_crypto(opportunities: list):
crypto_data = _fetch_crypto_prices()
if crypto_data:
_set_cached("crypto_prices", crypto_data)
if not crypto_data:
logger.warning("_analyze_opportunities_crypto: No crypto data available")
return
logger.debug(f"_analyze_opportunities_crypto: Analyzing {len(crypto_data)} crypto coins")
for coin in (crypto_data or [])[:20]:
change = _safe_float(coin.get("change_24h", 0))
@@ -1669,12 +1675,13 @@ def _analyze_opportunities_crypto(opportunities: list):
reason = ""
impact = "neutral"
# Lower thresholds to show more opportunities
if change > 15:
signal = "overbought"
strength = "strong"
reason = f"24h涨幅{change:.1f}%7日涨幅{change_7d:.1f}%,短期超买风险"
impact = "bearish"
elif change > 8:
elif change > 5: # Lowered from 8 to 5
signal = "bullish_momentum"
strength = "medium"
reason = f"24h涨幅{change:.1f}%,上涨动能强劲"
@@ -1684,7 +1691,7 @@ def _analyze_opportunities_crypto(opportunities: list):
strength = "strong"
reason = f"24h跌幅{abs(change):.1f}%,可能超卖反弹"
impact = "bullish"
elif change < -8:
elif change < -5: # Lowered from -8 to -5
signal = "bearish_momentum"
strength = "medium"
reason = f"24h跌幅{abs(change):.1f}%,下跌趋势明显"
@@ -1713,6 +1720,12 @@ def _analyze_opportunities_stocks(opportunities: list):
stock_data = _fetch_stock_opportunity_prices()
if stock_data:
_set_cached("stock_opportunity_prices", stock_data, 3600)
if not stock_data:
logger.warning("_analyze_opportunities_stocks: No stock data available")
return
logger.debug(f"_analyze_opportunities_stocks: Analyzing {len(stock_data)} stocks")
for stock in (stock_data or []):
change = _safe_float(stock.get("change", 0))
@@ -1731,7 +1744,7 @@ def _analyze_opportunities_stocks(opportunities: list):
strength = "strong"
reason = f"日涨幅{change:.1f}%,短期涨幅较大,注意回调风险"
impact = "bearish"
elif change > 3:
elif change > 2: # Lowered from 3 to 2
signal = "bullish_momentum"
strength = "medium"
reason = f"日涨幅{change:.1f}%,上涨动能强劲"
@@ -1741,7 +1754,7 @@ def _analyze_opportunities_stocks(opportunities: list):
strength = "strong"
reason = f"日跌幅{abs(change):.1f}%,可能超卖反弹"
impact = "bullish"
elif change < -3:
elif change < -2: # Lowered from -3 to -2
signal = "bearish_momentum"
strength = "medium"
reason = f"日跌幅{abs(change):.1f}%,下跌趋势明显"
@@ -1769,6 +1782,12 @@ def _analyze_opportunities_forex(opportunities: list):
forex_data = _fetch_forex_pairs()
if forex_data:
_set_cached("forex_pairs", forex_data, 3600)
if not forex_data:
logger.warning("_analyze_opportunities_forex: No forex data available")
return
logger.debug(f"_analyze_opportunities_forex: Analyzing {len(forex_data)} forex pairs")
for pair in (forex_data or []):
change = _safe_float(pair.get("change", 0))
@@ -1787,7 +1806,7 @@ def _analyze_opportunities_forex(opportunities: list):
strength = "strong"
reason = f"日涨幅{change:.2f}%,汇率波动剧烈,注意回调"
impact = "bearish"
elif change > 0.8:
elif change > 0.5: # Lowered from 0.8 to 0.5
signal = "bullish_momentum"
strength = "medium"
reason = f"日涨幅{change:.2f}%,上涨动能较强"
@@ -1797,7 +1816,7 @@ def _analyze_opportunities_forex(opportunities: list):
strength = "strong"
reason = f"日跌幅{abs(change):.2f}%,汇率波动剧烈,可能反弹"
impact = "bullish"
elif change < -0.8:
elif change < -0.5: # Lowered from -0.8 to -0.5
signal = "bearish_momentum"
strength = "medium"
reason = f"日跌幅{abs(change):.2f}%,下跌趋势明显"
@@ -1836,17 +1855,34 @@ def trading_opportunities():
opportunities = []
# 1) Crypto
_analyze_opportunities_crypto(opportunities)
try:
_analyze_opportunities_crypto(opportunities)
crypto_count = len([o for o in opportunities if o.get("market") == "Crypto"])
logger.info(f"Trading opportunities: found {crypto_count} crypto opportunities")
except Exception as e:
logger.error(f"Failed to analyze crypto opportunities: {e}", exc_info=True)
# 2) US Stocks
_analyze_opportunities_stocks(opportunities)
try:
_analyze_opportunities_stocks(opportunities)
stock_count = len([o for o in opportunities if o.get("market") == "USStock"])
logger.info(f"Trading opportunities: found {stock_count} US stock opportunities")
except Exception as e:
logger.error(f"Failed to analyze stock opportunities: {e}", exc_info=True)
# 3) Forex
_analyze_opportunities_forex(opportunities)
try:
_analyze_opportunities_forex(opportunities)
forex_count = len([o for o in opportunities if o.get("market") == "Forex"])
logger.info(f"Trading opportunities: found {forex_count} forex opportunities")
except Exception as e:
logger.error(f"Failed to analyze forex opportunities: {e}", exc_info=True)
# Sort by absolute change descending
opportunities.sort(key=lambda x: abs(x.get("change_24h", 0)), reverse=True)
logger.info(f"Trading opportunities: total {len(opportunities)} opportunities found (Crypto: {len([o for o in opportunities if o.get('market') == 'Crypto'])}, USStock: {len([o for o in opportunities if o.get('market') == 'USStock'])}, Forex: {len([o for o in opportunities if o.get('market') == 'Forex'])})")
_set_cached("trading_opportunities", opportunities, 3600)
return jsonify({"code": 1, "msg": "success", "data": opportunities})
+13 -1
View File
@@ -830,6 +830,18 @@ def close_position():
avg_fill = float(getattr(result, "avg_price", 0) or 0)
raw = getattr(result, "raw", {}) or {}
# ---- calculate USDT amount for recording ----
# Convert base asset quantity to USDT amount for consistent recording
# amount (USDT) = base_qty * price
usdt_amount = actual_close_size * avg_fill if avg_fill > 0 else 0
# If price is not available, try to use entry price or mark price as fallback
if usdt_amount <= 0:
entry_price = float(position.get("entry_price") or 0)
mark_price = float(position.get("mark_price") or 0)
fallback_price = mark_price if mark_price > 0 else entry_price
if fallback_price > 0:
usdt_amount = actual_close_size * fallback_price
# ---- record trade ----
trade_id = _record_quick_trade(
user_id=user_id,
@@ -838,7 +850,7 @@ def close_position():
symbol=symbol,
side="sell" if position_side == "long" else "buy", # Opposite of position side
order_type="market",
amount=actual_close_size, # Record position size
amount=usdt_amount, # Record USDT amount, not base asset quantity
price=avg_fill,
leverage=float(position.get("leverage") or 1),
market_type=market_type,
@@ -214,6 +214,19 @@ class PendingOrderWorker:
market_type = str(market_type or "swap").strip().lower()
if market_type in ("futures", "future", "perp", "perpetual"):
market_type = "swap"
# Get strategy's trading symbol(s) to filter positions
# Only sync positions for symbols that this strategy actually trades
strategy_symbol = (sc.get("symbol") or "").strip()
trading_config = sc.get("trading_config") or {}
symbol_list = trading_config.get("symbol_list") or []
# Normalize symbol list: convert to set for fast lookup
allowed_symbols = set()
if strategy_symbol:
allowed_symbols.add(strategy_symbol.upper())
for sym in symbol_list:
if sym and isinstance(sym, str):
allowed_symbols.add(sym.strip().upper())
# Lazy import MT5 here to allow elif chain later
global MT5Client
@@ -500,10 +513,23 @@ class PendingOrderWorker:
to_update.append({"id": rid, "size": exch_qty, "entry_price": exch_price})
# [New Feature] Detect positions that exist on exchange but not in local DB, and insert them.
# IMPORTANT: Only insert positions for symbols that this strategy actually trades
# This prevents syncing positions from quick trade or other sources
to_insert: List[Dict[str, Any]] = []
local_symbols_sides = {(str(r.get("symbol") or "").strip(), str(r.get("side") or "").strip().lower()) for r in plist}
for _sym, _sides_map in exch_size.items():
# Filter: only sync positions for symbols that this strategy trades
# If strategy has no symbol configured, skip auto-insert to prevent syncing quick trade positions
_sym_upper = _sym.strip().upper()
if allowed_symbols and _sym_upper not in allowed_symbols:
logger.debug(f"[PositionSync] Skipping {_sym}: not in strategy's symbol list (strategy trades: {allowed_symbols})")
continue
elif not allowed_symbols:
# Strategy has no symbol configured - skip to prevent syncing unrelated positions
logger.debug(f"[PositionSync] Skipping {_sym}: strategy has no symbol configured (preventing quick trade position sync)")
continue
for _side, _qty in _sides_map.items():
if _qty > 1e-12 and (_sym, _side) not in local_symbols_sides:
# Exchange has this position but local DB does not