@@ -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})
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user