@@ -1657,6 +1657,12 @@ def _analyze_opportunities_crypto(opportunities: list):
|
|||||||
if crypto_data:
|
if crypto_data:
|
||||||
_set_cached("crypto_prices", 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]:
|
for coin in (crypto_data or [])[:20]:
|
||||||
change = _safe_float(coin.get("change_24h", 0))
|
change = _safe_float(coin.get("change_24h", 0))
|
||||||
change_7d = _safe_float(coin.get("change_7d", 0))
|
change_7d = _safe_float(coin.get("change_7d", 0))
|
||||||
@@ -1669,12 +1675,13 @@ def _analyze_opportunities_crypto(opportunities: list):
|
|||||||
reason = ""
|
reason = ""
|
||||||
impact = "neutral"
|
impact = "neutral"
|
||||||
|
|
||||||
|
# Lower thresholds to show more opportunities
|
||||||
if change > 15:
|
if change > 15:
|
||||||
signal = "overbought"
|
signal = "overbought"
|
||||||
strength = "strong"
|
strength = "strong"
|
||||||
reason = f"24h涨幅{change:.1f}%,7日涨幅{change_7d:.1f}%,短期超买风险"
|
reason = f"24h涨幅{change:.1f}%,7日涨幅{change_7d:.1f}%,短期超买风险"
|
||||||
impact = "bearish"
|
impact = "bearish"
|
||||||
elif change > 8:
|
elif change > 5: # Lowered from 8 to 5
|
||||||
signal = "bullish_momentum"
|
signal = "bullish_momentum"
|
||||||
strength = "medium"
|
strength = "medium"
|
||||||
reason = f"24h涨幅{change:.1f}%,上涨动能强劲"
|
reason = f"24h涨幅{change:.1f}%,上涨动能强劲"
|
||||||
@@ -1684,7 +1691,7 @@ def _analyze_opportunities_crypto(opportunities: list):
|
|||||||
strength = "strong"
|
strength = "strong"
|
||||||
reason = f"24h跌幅{abs(change):.1f}%,可能超卖反弹"
|
reason = f"24h跌幅{abs(change):.1f}%,可能超卖反弹"
|
||||||
impact = "bullish"
|
impact = "bullish"
|
||||||
elif change < -8:
|
elif change < -5: # Lowered from -8 to -5
|
||||||
signal = "bearish_momentum"
|
signal = "bearish_momentum"
|
||||||
strength = "medium"
|
strength = "medium"
|
||||||
reason = f"24h跌幅{abs(change):.1f}%,下跌趋势明显"
|
reason = f"24h跌幅{abs(change):.1f}%,下跌趋势明显"
|
||||||
@@ -1714,6 +1721,12 @@ def _analyze_opportunities_stocks(opportunities: list):
|
|||||||
if stock_data:
|
if stock_data:
|
||||||
_set_cached("stock_opportunity_prices", stock_data, 3600)
|
_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 []):
|
for stock in (stock_data or []):
|
||||||
change = _safe_float(stock.get("change", 0))
|
change = _safe_float(stock.get("change", 0))
|
||||||
symbol = stock.get("symbol", "")
|
symbol = stock.get("symbol", "")
|
||||||
@@ -1731,7 +1744,7 @@ def _analyze_opportunities_stocks(opportunities: list):
|
|||||||
strength = "strong"
|
strength = "strong"
|
||||||
reason = f"日涨幅{change:.1f}%,短期涨幅较大,注意回调风险"
|
reason = f"日涨幅{change:.1f}%,短期涨幅较大,注意回调风险"
|
||||||
impact = "bearish"
|
impact = "bearish"
|
||||||
elif change > 3:
|
elif change > 2: # Lowered from 3 to 2
|
||||||
signal = "bullish_momentum"
|
signal = "bullish_momentum"
|
||||||
strength = "medium"
|
strength = "medium"
|
||||||
reason = f"日涨幅{change:.1f}%,上涨动能强劲"
|
reason = f"日涨幅{change:.1f}%,上涨动能强劲"
|
||||||
@@ -1741,7 +1754,7 @@ def _analyze_opportunities_stocks(opportunities: list):
|
|||||||
strength = "strong"
|
strength = "strong"
|
||||||
reason = f"日跌幅{abs(change):.1f}%,可能超卖反弹"
|
reason = f"日跌幅{abs(change):.1f}%,可能超卖反弹"
|
||||||
impact = "bullish"
|
impact = "bullish"
|
||||||
elif change < -3:
|
elif change < -2: # Lowered from -3 to -2
|
||||||
signal = "bearish_momentum"
|
signal = "bearish_momentum"
|
||||||
strength = "medium"
|
strength = "medium"
|
||||||
reason = f"日跌幅{abs(change):.1f}%,下跌趋势明显"
|
reason = f"日跌幅{abs(change):.1f}%,下跌趋势明显"
|
||||||
@@ -1770,6 +1783,12 @@ def _analyze_opportunities_forex(opportunities: list):
|
|||||||
if forex_data:
|
if forex_data:
|
||||||
_set_cached("forex_pairs", forex_data, 3600)
|
_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 []):
|
for pair in (forex_data or []):
|
||||||
change = _safe_float(pair.get("change", 0))
|
change = _safe_float(pair.get("change", 0))
|
||||||
symbol = pair.get("symbol", pair.get("name", ""))
|
symbol = pair.get("symbol", pair.get("name", ""))
|
||||||
@@ -1787,7 +1806,7 @@ def _analyze_opportunities_forex(opportunities: list):
|
|||||||
strength = "strong"
|
strength = "strong"
|
||||||
reason = f"日涨幅{change:.2f}%,汇率波动剧烈,注意回调"
|
reason = f"日涨幅{change:.2f}%,汇率波动剧烈,注意回调"
|
||||||
impact = "bearish"
|
impact = "bearish"
|
||||||
elif change > 0.8:
|
elif change > 0.5: # Lowered from 0.8 to 0.5
|
||||||
signal = "bullish_momentum"
|
signal = "bullish_momentum"
|
||||||
strength = "medium"
|
strength = "medium"
|
||||||
reason = f"日涨幅{change:.2f}%,上涨动能较强"
|
reason = f"日涨幅{change:.2f}%,上涨动能较强"
|
||||||
@@ -1797,7 +1816,7 @@ def _analyze_opportunities_forex(opportunities: list):
|
|||||||
strength = "strong"
|
strength = "strong"
|
||||||
reason = f"日跌幅{abs(change):.2f}%,汇率波动剧烈,可能反弹"
|
reason = f"日跌幅{abs(change):.2f}%,汇率波动剧烈,可能反弹"
|
||||||
impact = "bullish"
|
impact = "bullish"
|
||||||
elif change < -0.8:
|
elif change < -0.5: # Lowered from -0.8 to -0.5
|
||||||
signal = "bearish_momentum"
|
signal = "bearish_momentum"
|
||||||
strength = "medium"
|
strength = "medium"
|
||||||
reason = f"日跌幅{abs(change):.2f}%,下跌趋势明显"
|
reason = f"日跌幅{abs(change):.2f}%,下跌趋势明显"
|
||||||
@@ -1836,17 +1855,34 @@ def trading_opportunities():
|
|||||||
opportunities = []
|
opportunities = []
|
||||||
|
|
||||||
# 1) Crypto
|
# 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
|
# 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
|
# 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
|
# Sort by absolute change descending
|
||||||
opportunities.sort(key=lambda x: abs(x.get("change_24h", 0)), reverse=True)
|
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)
|
_set_cached("trading_opportunities", opportunities, 3600)
|
||||||
|
|
||||||
return jsonify({"code": 1, "msg": "success", "data": opportunities})
|
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)
|
avg_fill = float(getattr(result, "avg_price", 0) or 0)
|
||||||
raw = getattr(result, "raw", {}) or {}
|
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 ----
|
# ---- record trade ----
|
||||||
trade_id = _record_quick_trade(
|
trade_id = _record_quick_trade(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
@@ -838,7 +850,7 @@ def close_position():
|
|||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
side="sell" if position_side == "long" else "buy", # Opposite of position side
|
side="sell" if position_side == "long" else "buy", # Opposite of position side
|
||||||
order_type="market",
|
order_type="market",
|
||||||
amount=actual_close_size, # Record position size
|
amount=usdt_amount, # Record USDT amount, not base asset quantity
|
||||||
price=avg_fill,
|
price=avg_fill,
|
||||||
leverage=float(position.get("leverage") or 1),
|
leverage=float(position.get("leverage") or 1),
|
||||||
market_type=market_type,
|
market_type=market_type,
|
||||||
|
|||||||
@@ -215,6 +215,19 @@ class PendingOrderWorker:
|
|||||||
if market_type in ("futures", "future", "perp", "perpetual"):
|
if market_type in ("futures", "future", "perp", "perpetual"):
|
||||||
market_type = "swap"
|
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
|
# Lazy import MT5 here to allow elif chain later
|
||||||
global MT5Client
|
global MT5Client
|
||||||
if MT5Client is None:
|
if MT5Client is None:
|
||||||
@@ -500,10 +513,23 @@ class PendingOrderWorker:
|
|||||||
to_update.append({"id": rid, "size": exch_qty, "entry_price": exch_price})
|
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.
|
# [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]] = []
|
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}
|
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():
|
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():
|
for _side, _qty in _sides_map.items():
|
||||||
if _qty > 1e-12 and (_sym, _side) not in local_symbols_sides:
|
if _qty > 1e-12 and (_sym, _side) not in local_symbols_sides:
|
||||||
# Exchange has this position but local DB does not
|
# Exchange has this position but local DB does not
|
||||||
|
|||||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -421,4 +421,4 @@
|
|||||||
.brand-text {
|
.brand-text {
|
||||||
font-size: 20px;
|
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>
|
||||||
Vendored
+18
File diff suppressed because one or more lines are too long
-18
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
-18
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
+18
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user