@@ -273,6 +273,13 @@ def place_order():
|
||||
tp_price = float(body.get("tp_price") or 0)
|
||||
sl_price = float(body.get("sl_price") or 0)
|
||||
source = str(body.get("source") or "manual").strip()
|
||||
margin_mode = str(body.get("margin_mode") or body.get("marginMode") or "").strip().lower()
|
||||
if margin_mode in ("cross", "crossed"):
|
||||
margin_mode = "cross"
|
||||
elif margin_mode in ("iso", "isolated"):
|
||||
margin_mode = "isolated"
|
||||
else:
|
||||
margin_mode = ""
|
||||
|
||||
# ---- validation ----
|
||||
if not credential_id:
|
||||
@@ -286,28 +293,35 @@ def place_order():
|
||||
if order_type == "limit" and price <= 0:
|
||||
return jsonify({"code": 0, "msg": "price required for limit orders"}), 400
|
||||
|
||||
# ---- Auto-determine market_type from leverage ----
|
||||
# leverage = 1 -> spot, leverage > 1 -> swap
|
||||
if not market_type:
|
||||
market_type = "spot" if leverage == 1 else "swap"
|
||||
# ---- market_type: leverage 1 => spot API, else perpetual (swap) ----
|
||||
if market_type in ("futures", "future", "perp", "perpetual"):
|
||||
market_type = "swap"
|
||||
# Override only when user did not explicitly choose market_type.
|
||||
if leverage > 1:
|
||||
market_type = "swap"
|
||||
elif leverage == 1 and not str(body.get("market_type") or "").strip():
|
||||
else:
|
||||
market_type = "spot"
|
||||
|
||||
# ---- build exchange client ----
|
||||
exchange_config = _build_exchange_config(credential_id, user_id, {
|
||||
"market_type": market_type,
|
||||
})
|
||||
cfg_overrides: Dict[str, Any] = {"market_type": market_type}
|
||||
if margin_mode in ("cross", "isolated"):
|
||||
cfg_overrides["margin_mode"] = margin_mode
|
||||
cfg_overrides["td_mode"] = margin_mode
|
||||
exchange_config = _build_exchange_config(credential_id, user_id, cfg_overrides)
|
||||
exchange_id = (exchange_config.get("exchange_id") or "").strip().lower()
|
||||
if not exchange_id:
|
||||
return jsonify({"code": 0, "msg": "Invalid credential: missing exchange_id"}), 400
|
||||
|
||||
client = _create_client(exchange_config, market_type=market_type)
|
||||
|
||||
# Binance USDT-M: sync isolated/cross margin mode (best-effort; may fail if open orders exist)
|
||||
if market_type != "spot" and margin_mode in ("cross", "isolated"):
|
||||
try:
|
||||
from app.services.live_trading.binance import BinanceFuturesClient
|
||||
if isinstance(client, BinanceFuturesClient):
|
||||
client.set_margin_type(symbol=symbol, margin_mode=margin_mode)
|
||||
except Exception as me:
|
||||
logger.warning(f"Binance set_margin_type failed (non-fatal): {me}")
|
||||
|
||||
# ---- Convert USDT amount to base asset quantity ----
|
||||
# Quick trade always accepts USDT amount, convert to base qty for all exchanges
|
||||
# For limit orders, use the provided price; for market orders, fetch current price
|
||||
@@ -543,7 +557,16 @@ def get_balance():
|
||||
raw = client.get_account()
|
||||
balance_data = _parse_balance(raw, exchange_id, market_type)
|
||||
elif hasattr(client, "get_accounts"):
|
||||
raw = client.get_accounts()
|
||||
from app.services.live_trading.bitget import BitgetMixClient
|
||||
|
||||
if isinstance(client, BitgetMixClient):
|
||||
pt = str(exchange_config.get("product_type") or exchange_config.get("productType") or "USDT-FUTURES")
|
||||
raw = client.get_accounts(product_type=pt)
|
||||
else:
|
||||
raw = client.get_accounts()
|
||||
balance_data = _parse_balance(raw, exchange_id, market_type)
|
||||
elif (exchange_id or "").lower() == "bitget" and market_type == "spot" and hasattr(client, "get_assets"):
|
||||
raw = client.get_assets()
|
||||
balance_data = _parse_balance(raw, exchange_id, market_type)
|
||||
except Exception as be:
|
||||
logger.warning(f"Balance fetch failed: {be}")
|
||||
@@ -575,22 +598,62 @@ def _parse_balance(raw: Any, exchange_id: str, market_type: str) -> Dict[str, An
|
||||
result["total"] = float(b.get("free") or 0) + float(b.get("locked") or 0)
|
||||
return result
|
||||
return result
|
||||
ex = (exchange_id or "").lower()
|
||||
# Bitget mix: { code, data: [ { marginCoin, available, accountEquity, ... } ] }
|
||||
# Must run before OKX — both use data as a list; OKX fallback would zero Bitget.
|
||||
if ex == "bitget" and (market_type or "").lower() != "spot":
|
||||
bg_data = raw.get("data")
|
||||
if isinstance(bg_data, list) and bg_data:
|
||||
row = None
|
||||
for item in bg_data:
|
||||
if isinstance(item, dict) and str(item.get("marginCoin") or "").upper() == "USDT":
|
||||
row = item
|
||||
break
|
||||
if row is None and isinstance(bg_data[0], dict):
|
||||
row = bg_data[0]
|
||||
if isinstance(row, dict):
|
||||
av = (
|
||||
row.get("available")
|
||||
or row.get("availableBalance")
|
||||
or row.get("crossedMaxAvailable")
|
||||
or row.get("isolatedMaxAvailable")
|
||||
or 0
|
||||
)
|
||||
eq = row.get("accountEquity") or row.get("usdtEquity") or row.get("equity") or av
|
||||
result["available"] = float(av or 0)
|
||||
result["total"] = float(eq or 0) if eq is not None else result["available"]
|
||||
return result
|
||||
# Bitget spot: GET /api/v2/spot/account/assets
|
||||
if ex == "bitget" and (market_type or "").lower() == "spot":
|
||||
bg_data = raw.get("data")
|
||||
if isinstance(bg_data, list):
|
||||
for b in bg_data:
|
||||
if isinstance(b, dict) and str(b.get("coin") or "").upper() == "USDT":
|
||||
avail = float(b.get("available") or 0)
|
||||
frozen = float(b.get("frozen") or b.get("locked") or 0)
|
||||
result["available"] = avail
|
||||
result["total"] = avail + frozen
|
||||
return result
|
||||
return result
|
||||
# OKX
|
||||
data = raw.get("data")
|
||||
if isinstance(data, list) and data:
|
||||
first = data[0] if isinstance(data[0], dict) else {}
|
||||
# Account balance
|
||||
details = first.get("details", [])
|
||||
if isinstance(details, list):
|
||||
if isinstance(details, list) and details:
|
||||
for d in details:
|
||||
if str(d.get("ccy") or "").upper() == "USDT":
|
||||
result["available"] = float(d.get("availBal") or d.get("availEq") or 0)
|
||||
result["total"] = float(d.get("eq") or d.get("cashBal") or 0)
|
||||
return result
|
||||
# Fallback
|
||||
result["available"] = float(first.get("availBal") or first.get("totalEq") or 0)
|
||||
result["total"] = float(first.get("totalEq") or 0)
|
||||
return result
|
||||
# OKX-style single-account row (not Bitget — Bitget handled above)
|
||||
if "availBal" in first or "availEq" in first or "totalEq" in first or "adjEq" in first:
|
||||
result["available"] = float(
|
||||
first.get("availBal") or first.get("availEq") or first.get("adjEq") or first.get("totalEq") or 0
|
||||
)
|
||||
result["total"] = float(first.get("totalEq") or first.get("adjEq") or 0)
|
||||
return result
|
||||
# Bybit
|
||||
if "result" in raw:
|
||||
res = raw["result"]
|
||||
@@ -636,6 +699,106 @@ def _parse_balance(raw: Any, exchange_id: str, market_type: str) -> Dict[str, An
|
||||
return result
|
||||
|
||||
|
||||
def _fetch_exchange_positions_raw(
|
||||
client: Any,
|
||||
exchange_config: Dict[str, Any],
|
||||
*,
|
||||
symbol: str,
|
||||
market_type: str,
|
||||
) -> Any:
|
||||
"""
|
||||
Fetch raw position payload for quick-trade / close-position.
|
||||
|
||||
Many clients do not accept ``symbol=`` on ``get_positions()`` (Gate, KuCoin, Bybit, Bitfinex),
|
||||
or need extra args (Bitget ``product_type``, OKX ``inst_type``). Centralize here.
|
||||
"""
|
||||
from app.services.live_trading.binance import BinanceFuturesClient
|
||||
from app.services.live_trading.bitget import BitgetMixClient
|
||||
from app.services.live_trading.bybit import BybitClient
|
||||
from app.services.live_trading.deepcoin import DeepcoinClient
|
||||
from app.services.live_trading.gate import GateUsdtFuturesClient
|
||||
from app.services.live_trading.htx import HtxClient
|
||||
from app.services.live_trading.kucoin import KucoinFuturesClient
|
||||
from app.services.live_trading.okx import OkxClient
|
||||
from app.services.live_trading.symbols import (
|
||||
to_bybit_symbol,
|
||||
to_gate_currency_pair,
|
||||
to_kucoin_futures_symbol,
|
||||
to_okx_spot_inst_id,
|
||||
to_okx_swap_inst_id,
|
||||
)
|
||||
|
||||
mt = (market_type or "swap").strip().lower()
|
||||
|
||||
if isinstance(client, OkxClient):
|
||||
if mt == "spot":
|
||||
inst_id = to_okx_spot_inst_id(symbol)
|
||||
inst_type = "SPOT"
|
||||
else:
|
||||
inst_id = to_okx_swap_inst_id(symbol)
|
||||
inst_type = "SWAP"
|
||||
return client.get_positions(inst_id=inst_id, inst_type=inst_type)
|
||||
|
||||
if isinstance(client, BinanceFuturesClient):
|
||||
return client.get_positions(symbol=symbol)
|
||||
|
||||
if isinstance(client, BitgetMixClient):
|
||||
pt = str(exchange_config.get("product_type") or exchange_config.get("productType") or "USDT-FUTURES")
|
||||
return client.get_positions(product_type=pt, symbol=symbol)
|
||||
|
||||
if isinstance(client, BybitClient):
|
||||
raw = client.get_positions()
|
||||
lst = (((raw or {}).get("result") or {}).get("list")) if isinstance(raw, dict) else None
|
||||
if not isinstance(lst, list):
|
||||
return raw
|
||||
sym_norm = to_bybit_symbol(symbol)
|
||||
filtered = [p for p in lst if isinstance(p, dict) and str(p.get("symbol") or "").strip() == sym_norm]
|
||||
if isinstance(raw, dict):
|
||||
out = dict(raw)
|
||||
res = dict((raw.get("result") or {}) if isinstance(raw.get("result"), dict) else {})
|
||||
res["list"] = filtered
|
||||
out["result"] = res
|
||||
return out
|
||||
return {"result": {"list": filtered}}
|
||||
|
||||
if isinstance(client, GateUsdtFuturesClient):
|
||||
raw = client.get_positions()
|
||||
items = raw if isinstance(raw, list) else []
|
||||
c = to_gate_currency_pair(symbol)
|
||||
filtered = [p for p in items if isinstance(p, dict) and str(p.get("contract") or "").strip() == c]
|
||||
return filtered
|
||||
|
||||
if isinstance(client, KucoinFuturesClient):
|
||||
raw = client.get_positions()
|
||||
data = raw.get("data") if isinstance(raw, dict) else []
|
||||
sym = to_kucoin_futures_symbol(symbol)
|
||||
if not isinstance(data, list):
|
||||
data = []
|
||||
filtered = [p for p in data if isinstance(p, dict) and str(p.get("symbol") or "").strip() == sym]
|
||||
if isinstance(raw, dict):
|
||||
out = dict(raw)
|
||||
out["data"] = filtered
|
||||
return out
|
||||
return {"data": filtered}
|
||||
|
||||
if isinstance(client, HtxClient):
|
||||
return client.get_positions(symbol=symbol)
|
||||
|
||||
if isinstance(client, DeepcoinClient):
|
||||
return client.get_positions(symbol=symbol)
|
||||
|
||||
if hasattr(client, "get_positions"):
|
||||
try:
|
||||
return client.get_positions(symbol=symbol)
|
||||
except TypeError:
|
||||
return client.get_positions()
|
||||
|
||||
if hasattr(client, "get_position"):
|
||||
return client.get_position(symbol=symbol)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@quick_trade_bp.route('/position', methods=['GET'])
|
||||
@login_required
|
||||
def get_position():
|
||||
@@ -658,25 +821,10 @@ def get_position():
|
||||
|
||||
positions = []
|
||||
try:
|
||||
# OKX requires inst_id instead of symbol
|
||||
from app.services.live_trading.okx import OkxClient
|
||||
if isinstance(client, OkxClient):
|
||||
from app.services.live_trading.symbols import to_okx_swap_inst_id, to_okx_spot_inst_id
|
||||
if market_type == "spot":
|
||||
inst_id = to_okx_spot_inst_id(symbol)
|
||||
inst_type = "SPOT"
|
||||
else:
|
||||
inst_id = to_okx_swap_inst_id(symbol)
|
||||
inst_type = "SWAP"
|
||||
raw = client.get_positions(inst_id=inst_id, inst_type=inst_type)
|
||||
positions = _parse_positions(raw)
|
||||
logger.info(f"OKX positions query: inst_id={inst_id}, inst_type={inst_type}, found {len(positions)} positions")
|
||||
elif hasattr(client, "get_positions"):
|
||||
raw = client.get_positions(symbol=symbol)
|
||||
positions = _parse_positions(raw)
|
||||
elif hasattr(client, "get_position"):
|
||||
raw = client.get_position(symbol=symbol)
|
||||
positions = _parse_positions(raw)
|
||||
raw = _fetch_exchange_positions_raw(
|
||||
client, exchange_config, symbol=symbol, market_type=market_type
|
||||
)
|
||||
positions = _parse_positions(raw)
|
||||
except Exception as pe:
|
||||
logger.warning(f"Position fetch failed: {pe}")
|
||||
logger.warning(traceback.format_exc())
|
||||
@@ -698,33 +846,62 @@ def _parse_positions(raw: Any) -> list:
|
||||
if isinstance(raw, list):
|
||||
items = raw
|
||||
elif isinstance(raw, dict):
|
||||
data = raw.get("data") or raw.get("result") or raw.get("positions") or []
|
||||
if isinstance(data, list):
|
||||
items = data
|
||||
elif isinstance(data, dict):
|
||||
items = data.get("list", []) if "list" in data else [data]
|
||||
if isinstance(raw.get("raw"), list):
|
||||
items = raw["raw"]
|
||||
else:
|
||||
data = raw.get("data") or raw.get("result") or raw.get("positions") or []
|
||||
if isinstance(data, list):
|
||||
items = data
|
||||
elif isinstance(data, dict):
|
||||
items = data.get("list", []) if "list" in data else [data]
|
||||
else:
|
||||
items = []
|
||||
|
||||
for item in items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
# For OKX, position size can be in different fields
|
||||
# SWAP: posAmt, pos
|
||||
# Binance futures: positionAmt
|
||||
# SPOT: bal (balance), availBal (available balance)
|
||||
size = float(item.get("posAmt") or item.get("pos") or item.get("size") or item.get("contracts") or
|
||||
item.get("bal") or item.get("availBal") or item.get("volume") or 0)
|
||||
size = float(
|
||||
item.get("positionAmt")
|
||||
or item.get("posAmt")
|
||||
or item.get("pos")
|
||||
or item.get("total")
|
||||
or item.get("currentQty")
|
||||
or item.get("available")
|
||||
or item.get("size")
|
||||
or item.get("contracts")
|
||||
or item.get("bal")
|
||||
or item.get("availBal")
|
||||
or item.get("volume")
|
||||
or 0
|
||||
)
|
||||
if abs(size) < 1e-10:
|
||||
continue
|
||||
|
||||
# For spot, side is always "long" (you own the asset)
|
||||
# For swap, determine side from sign of size
|
||||
# Binance hedge: positionSide LONG/SHORT with positive positionAmt; one-way: BOTH + signed amt
|
||||
side = "long"
|
||||
if size < 0:
|
||||
psu = str(item.get("positionSide", "")).strip().upper()
|
||||
if psu == "SHORT":
|
||||
side = "short"
|
||||
elif psu == "LONG":
|
||||
side = "long"
|
||||
elif item.get("posSide"):
|
||||
# OKX may have posSide field: "long" or "short"
|
||||
pos_side = str(item.get("posSide", "")).strip().lower()
|
||||
if pos_side in ("long", "short"):
|
||||
side = pos_side
|
||||
elif str(item.get("holdSide") or "").strip().lower() == "short":
|
||||
side = "short"
|
||||
elif str(item.get("holdSide") or "").strip().lower() == "long":
|
||||
side = "long"
|
||||
elif str(item.get("side") or "").strip().lower() in ("sell", "s"):
|
||||
side = "short"
|
||||
elif str(item.get("side") or "").strip().lower() in ("buy", "b"):
|
||||
side = "long"
|
||||
elif size < 0:
|
||||
side = "short"
|
||||
elif item.get("direction"):
|
||||
dir_side = str(item.get("direction") or "").strip().lower()
|
||||
if dir_side in ("buy", "long"):
|
||||
@@ -736,10 +913,36 @@ def _parse_positions(raw: Any) -> list:
|
||||
"symbol": item.get("symbol") or item.get("instId") or "",
|
||||
"side": side,
|
||||
"size": abs(size),
|
||||
"entry_price": float(item.get("entryPrice") or item.get("avgCost") or item.get("avgPx") or item.get("cost_open") or 0),
|
||||
"unrealized_pnl": float(item.get("unRealizedProfit") or item.get("upl") or item.get("unrealisedPnl") or item.get("profit_unreal") or item.get("pnl") or 0),
|
||||
"entry_price": float(
|
||||
item.get("entryPrice")
|
||||
or item.get("openPriceAvg")
|
||||
or item.get("avgEntryPrice")
|
||||
or item.get("avgPrice")
|
||||
or item.get("avgCost")
|
||||
or item.get("avgPx")
|
||||
or item.get("cost_open")
|
||||
or item.get("trade_avg_price")
|
||||
or 0
|
||||
),
|
||||
"unrealized_pnl": float(
|
||||
item.get("unRealizedProfit")
|
||||
or item.get("unrealizedProfit")
|
||||
or item.get("unrealizedPnl")
|
||||
or item.get("upl")
|
||||
or item.get("unrealisedPnl")
|
||||
or item.get("profit_unreal")
|
||||
or item.get("pnl")
|
||||
or 0
|
||||
),
|
||||
"leverage": float(item.get("leverage") or item.get("lever") or 1),
|
||||
"mark_price": float(item.get("markPrice") or item.get("markPx") or item.get("last_price") or item.get("last") or 0),
|
||||
"mark_price": float(
|
||||
item.get("markPrice")
|
||||
or item.get("markPx")
|
||||
or item.get("last_price")
|
||||
or item.get("last")
|
||||
or item.get("indexPrice")
|
||||
or 0
|
||||
),
|
||||
})
|
||||
except Exception as e:
|
||||
logger.warning(f"_parse_positions error: {e}")
|
||||
@@ -791,21 +994,10 @@ def close_position():
|
||||
# ---- get current position ----
|
||||
positions = []
|
||||
try:
|
||||
from app.services.live_trading.okx import OkxClient
|
||||
if isinstance(client, OkxClient):
|
||||
from app.services.live_trading.symbols import to_okx_swap_inst_id, to_okx_spot_inst_id
|
||||
if market_type == "spot":
|
||||
inst_id = to_okx_spot_inst_id(symbol)
|
||||
else:
|
||||
inst_id = to_okx_swap_inst_id(symbol)
|
||||
raw = client.get_positions(inst_id=inst_id)
|
||||
positions = _parse_positions(raw)
|
||||
elif hasattr(client, "get_positions"):
|
||||
raw = client.get_positions(symbol=symbol)
|
||||
positions = _parse_positions(raw)
|
||||
elif hasattr(client, "get_position"):
|
||||
raw = client.get_position(symbol=symbol)
|
||||
positions = _parse_positions(raw)
|
||||
raw = _fetch_exchange_positions_raw(
|
||||
client, exchange_config, symbol=symbol, market_type=market_type
|
||||
)
|
||||
positions = _parse_positions(raw)
|
||||
except Exception as pe:
|
||||
logger.warning(f"Position fetch failed: {pe}")
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ Trading Strategy API Routes
|
||||
from flask import Blueprint, request, jsonify, g
|
||||
from datetime import datetime
|
||||
import json
|
||||
import re
|
||||
import traceback
|
||||
import time
|
||||
|
||||
@@ -770,15 +771,16 @@ def start_strategy():
|
||||
|
||||
# Get strategy type
|
||||
strategy_type = get_strategy_service().get_strategy_type(strategy_id)
|
||||
|
||||
# Update strategy status
|
||||
get_strategy_service().update_strategy_status(strategy_id, 'running', user_id=user_id)
|
||||
|
||||
# Local backend: AI strategy executor was removed. Only indicator strategies are supported.
|
||||
if strategy_type == 'PromptBasedStrategy':
|
||||
return jsonify({'code': 0, 'msg': 'AI strategy has been removed; local edition does not support starting AI strategies', 'data': None}), 400
|
||||
|
||||
# Indicator strategy
|
||||
# IndicatorStrategy and ScriptStrategy are executed by TradingExecutor.
|
||||
if strategy_type == 'PromptBasedStrategy':
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': 'AI strategy has been removed; local edition does not support starting AI strategies',
|
||||
'data': None
|
||||
}), 400
|
||||
get_strategy_service().update_strategy_status(strategy_id, 'running', user_id=user_id)
|
||||
|
||||
success = get_trading_executor().start_strategy(strategy_id)
|
||||
|
||||
if not success:
|
||||
@@ -1241,12 +1243,65 @@ def verify_strategy_code():
|
||||
@strategy_bp.route('/strategies/ai-generate', methods=['POST'])
|
||||
@login_required
|
||||
def ai_generate_strategy():
|
||||
"""Generate strategy code using AI."""
|
||||
"""Generate strategy code or suggest template parameter updates using AI."""
|
||||
try:
|
||||
payload = request.get_json() or {}
|
||||
prompt = payload.get('prompt', '')
|
||||
if not prompt.strip():
|
||||
return jsonify({'code': '', 'msg': 'Prompt is empty'})
|
||||
return jsonify({'code': '', 'msg': 'Prompt is empty', 'params': None})
|
||||
|
||||
intent = (payload.get('intent') or 'generate_code').strip()
|
||||
from app.services.llm import LLMService
|
||||
llm = LLMService()
|
||||
api_key = llm.get_api_key()
|
||||
if not api_key:
|
||||
return jsonify({'code': '', 'msg': 'No LLM API key configured', 'params': None})
|
||||
|
||||
if intent == 'adjust_params':
|
||||
template_key = payload.get('template_key') or ''
|
||||
current_params = payload.get('params') or {}
|
||||
code_snapshot = (payload.get('code') or '')[:8000]
|
||||
system_prompt = """You tune quantitative strategy template parameters from the user's request.
|
||||
Return ONLY a single JSON object: keys are parameter names (strings), values are JSON numbers or booleans.
|
||||
You may return a partial object (only keys that should change) or a full object.
|
||||
Do not use markdown fences, do not add explanations before or after the JSON."""
|
||||
|
||||
user_content = (
|
||||
f"Template key: {template_key}\n"
|
||||
f"Current parameters (JSON):\n{json.dumps(current_params, ensure_ascii=False)}\n\n"
|
||||
f"Strategy code excerpt (context):\n{code_snapshot}\n\n"
|
||||
f"User request:\n{prompt.strip()}\n\n"
|
||||
"Respond with JSON only."
|
||||
)
|
||||
|
||||
content = llm.call_llm_api(
|
||||
messages=[
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_content},
|
||||
],
|
||||
model=llm.get_code_generation_model(),
|
||||
temperature=0.3,
|
||||
use_json_mode=False
|
||||
)
|
||||
|
||||
raw = (content or '').strip()
|
||||
if raw.startswith('```'):
|
||||
raw = re.sub(r'^```[a-zA-Z]*', '', raw).strip()
|
||||
if raw.endswith('```'):
|
||||
raw = raw[:-3].strip()
|
||||
updates = None
|
||||
try:
|
||||
updates = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
m = re.search(r'\{[\s\S]*\}', raw)
|
||||
if m:
|
||||
try:
|
||||
updates = json.loads(m.group(0))
|
||||
except json.JSONDecodeError:
|
||||
updates = None
|
||||
if not isinstance(updates, dict):
|
||||
return jsonify({'code': '', 'params': None, 'msg': 'AI did not return valid JSON parameters'})
|
||||
return jsonify({'code': '', 'params': updates, 'msg': 'success'})
|
||||
|
||||
system_prompt = """You are a quantitative trading strategy code generator.
|
||||
Generate Python strategy code that follows this framework:
|
||||
@@ -1264,16 +1319,26 @@ Generate Python strategy code that follows this framework:
|
||||
|
||||
Return ONLY the Python code, no explanations."""
|
||||
|
||||
from app.services.llm import LLMService
|
||||
llm = LLMService()
|
||||
api_key = llm.get_api_key()
|
||||
if not api_key:
|
||||
return jsonify({'code': '', 'msg': 'No LLM API key configured'})
|
||||
extra = ''
|
||||
template_key = payload.get('template_key')
|
||||
params = payload.get('params')
|
||||
code_ctx = (payload.get('code') or '').strip()
|
||||
if template_key or params is not None or code_ctx:
|
||||
extra_parts = []
|
||||
if template_key:
|
||||
extra_parts.append(f"Current template key: {template_key}")
|
||||
if isinstance(params, dict) and params:
|
||||
extra_parts.append('Current template parameters (JSON):\n' + json.dumps(params, ensure_ascii=False))
|
||||
if code_ctx:
|
||||
extra_parts.append('Current code (may be long):\n' + code_ctx[:12000])
|
||||
extra = '\n\n' + '\n\n'.join(extra_parts)
|
||||
|
||||
user_prompt = prompt.strip() + extra
|
||||
|
||||
content = llm.call_llm_api(
|
||||
messages=[
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": prompt},
|
||||
{"role": "user", "content": user_prompt},
|
||||
],
|
||||
model=llm.get_code_generation_model(),
|
||||
temperature=0.7,
|
||||
@@ -1290,12 +1355,12 @@ Return ONLY the Python code, no explanations."""
|
||||
content = content.strip()
|
||||
|
||||
if content:
|
||||
return jsonify({'code': content, 'msg': 'success'})
|
||||
return jsonify({'code': content, 'msg': 'success', 'params': None})
|
||||
else:
|
||||
return jsonify({'code': '', 'msg': 'AI generation returned empty result'})
|
||||
return jsonify({'code': '', 'msg': 'AI generation returned empty result', 'params': None})
|
||||
except Exception as e:
|
||||
logger.error(f"ai_generate_strategy failed: {str(e)}")
|
||||
return jsonify({'code': '', 'msg': str(e)})
|
||||
return jsonify({'code': '', 'msg': str(e), 'params': None})
|
||||
|
||||
|
||||
@strategy_bp.route('/strategies/performance', methods=['GET'])
|
||||
|
||||
Reference in New Issue
Block a user