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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -421,4 +421,4 @@
.brand-text {
font-size: 20px;
}
}</style><script defer="defer" src="/js/chunk-vendors.6f65f877.js" type="module"></script><script defer="defer" src="/js/app.78319175.js" type="module"></script><link href="/css/chunk-vendors.b8cb9e53.css" rel="stylesheet"><link href="/css/app.b8761398.css" rel="stylesheet"><script defer="defer" src="/js/chunk-vendors-legacy.9d679269.js" nomodule></script><script defer="defer" src="/js/app-legacy.b9bb3b91.js" nomodule></script></head><body><noscript><strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"><div class="first-loading-wrp"><h2>Landing</h2><div class="loading-wrp"><div class="pixel-cat-container"><div class="ground"></div><div class="pixel-cat"><div class="cat-head"><div class="cat-ear-left"></div><div class="cat-ear-right"></div><div class="cat-eye-left"></div><div class="cat-eye-right"></div><div class="cat-nose"></div><div class="cat-whiskers"></div></div><div class="cat-body"></div><div class="cat-leg-front-left"></div><div class="cat-leg-front-right"></div><div class="cat-leg-back-left"></div><div class="cat-leg-back-right"></div><div class="cat-tail"></div></div></div></div><div class="brand-text">QuantDinger</div></div></div></body></html>
}</style><script defer="defer" src="/js/chunk-vendors.8e02e3db.js" type="module"></script><script defer="defer" src="/js/app.e81906b1.js" type="module"></script><link href="/css/chunk-vendors.b8cb9e53.css" rel="stylesheet"><link href="/css/app.b8761398.css" rel="stylesheet"><script defer="defer" src="/js/chunk-vendors-legacy.36882298.js" nomodule></script><script defer="defer" src="/js/app-legacy.56c65dc3.js" nomodule></script></head><body><noscript><strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"><div class="first-loading-wrp"><h2>Landing</h2><div class="loading-wrp"><div class="pixel-cat-container"><div class="ground"></div><div class="pixel-cat"><div class="cat-head"><div class="cat-ear-left"></div><div class="cat-ear-right"></div><div class="cat-eye-left"></div><div class="cat-eye-right"></div><div class="cat-nose"></div><div class="cat-whiskers"></div></div><div class="cat-body"></div><div class="cat-leg-front-left"></div><div class="cat-leg-front-right"></div><div class="cat-leg-back-left"></div><div class="cat-leg-back-right"></div><div class="cat-tail"></div></div></div></div><div class="brand-text">QuantDinger</div></div></div></body></html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long