| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- """
- 智能股票分析助手 — 模拟交易服务层
- 封装模拟交易操作(持仓查询、资金查询、委托下单、撤单等),供API路由层调用。
- """
- import sys
- from pathlib import Path
- from typing import Optional
- # 确保skills路径可导入
- _PROJECT_ROOT = Path(__file__).parent.parent.parent.parent # backend/app/services -> project root
- _AGENTS_DIR = _PROJECT_ROOT / "agents"
- _SKILLS_MONI = _PROJECT_ROOT / "skills" / "模拟组合管理" / "mx-moni"
- for p in [_AGENTS_DIR, _SKILLS_MONI, str(_PROJECT_ROOT)]:
- if str(p) not in sys.path:
- sys.path.insert(0, str(p))
- import requests
- from app.config import settings
- from app.utils.mock_trading_normalize import extract_orders_dicts, normalize_mock_order_row
- # API基础地址
- MX_API_URL = "https://mkapi2.dfcfs.com/finskillshub"
- def _make_request(endpoint: str, body: dict) -> dict:
- """发送模拟交易API请求
- Args:
- endpoint: API端点路径
- body: 请求体
- Returns:
- API响应JSON
- """
- headers = {
- "apikey": settings.MX_APIKEY,
- "Content-Type": "application/json",
- }
- response = requests.post(
- f"{MX_API_URL}{endpoint}",
- headers=headers,
- json=body,
- timeout=30,
- )
- response.raise_for_status()
- return response.json()
- def _check_api_ready() -> Optional[dict]:
- """检查API是否就绪,未就绪返回错误字典"""
- if not settings.MX_APIKEY or settings.MX_APIKEY == "your-mx-apikey-here":
- return {"error": "MX_APIKEY 未配置"}
- return None
- def get_positions() -> dict:
- """查询模拟持仓
- Returns:
- {
- "success": True/False,
- "positions": [{"code": str, "name": str, "quantity": int, ...}, ...],
- "total": int,
- "error": str or None
- }
- """
- result = {
- "success": False,
- "positions": [],
- "total": 0,
- "error": None,
- }
- api_error = _check_api_ready()
- if api_error:
- result["error"] = api_error["error"]
- return result
- try:
- raw = _make_request("/api/claw/mockTrading/positions", {"moneyUnit": 1})
- if not raw.get("success") and str(raw.get("code")) != "200":
- result["error"] = raw.get("message", "查询持仓失败")
- return result
- data = raw.get("data", {})
- positions = data.get("positions", [])
- parsed = []
- for pos in (positions or []):
- parsed.append({
- "stock_code": pos.get("stockCode", ""),
- "stock_name": pos.get("stockName", ""),
- "quantity": pos.get("quantity", 0),
- "cost_price": pos.get("costPrice", 0),
- "current_price": pos.get("currentPrice", 0),
- "profit_loss": pos.get("profitLoss", 0),
- "profit_loss_rate": pos.get("profitLossRate", 0),
- "market_value": pos.get("marketValue", 0),
- })
- result["success"] = True
- result["positions"] = parsed
- result["total"] = len(parsed)
- return result
- except Exception as e:
- result["error"] = str(e)
- return result
- def get_balance() -> dict:
- """查询模拟账户资金
- Returns:
- {
- "success": True/False,
- "balance": {...},
- "error": str or None
- }
- """
- result = {
- "success": False,
- "balance": {},
- "error": None,
- }
- api_error = _check_api_ready()
- if api_error:
- result["error"] = api_error["error"]
- return result
- try:
- raw = _make_request("/api/claw/mockTrading/balance", {"moneyUnit": 1})
- if not raw.get("success") and str(raw.get("code")) != "200":
- result["error"] = raw.get("message", "查询资金失败")
- return result
- data = raw.get("data", {})
- result["success"] = True
- result["balance"] = {
- "total_assets": data.get("totalAssets", 0),
- "available_balance": data.get("availBalance", 0),
- "frozen_balance": data.get("frozenBalance", 0),
- "market_value": data.get("marketValue", 0),
- "total_profit_loss": data.get("totalProfitLoss", 0),
- }
- return result
- except Exception as e:
- result["error"] = str(e)
- return result
- def get_orders() -> dict:
- """查询委托记录
- Returns:
- {
- "success": True/False,
- "orders": [...],
- "total": int,
- "error": str or None
- }
- """
- result = {
- "success": False,
- "orders": [],
- "total": 0,
- "error": None,
- }
- api_error = _check_api_ready()
- if api_error:
- result["error"] = api_error["error"]
- return result
- try:
- raw = _make_request("/api/claw/mockTrading/orders", {
- "fltOrderDrt": 0,
- "fltOrderStatus": 0,
- })
- if not raw.get("success") and str(raw.get("code")) != "200":
- result["error"] = raw.get("message", "查询委托失败")
- return result
- data = raw.get("data", {}) or {}
- # 妙想可能返回 list,或 { rows: [] },或当日/历史分段字段
- orders = extract_orders_dicts(data)
- parsed = []
- for order in orders:
- if not isinstance(order, dict):
- continue
- # 统一解析字段名与枚举(数值买卖方向、委托状态等)
- parsed.append(normalize_mock_order_row(order))
- result["success"] = True
- result["orders"] = parsed
- result["total"] = len(parsed)
- return result
- except Exception as e:
- result["error"] = str(e)
- return result
- def place_order(
- trade_type: str,
- stock_code: str,
- quantity: int,
- price: Optional[float] = None,
- ) -> dict:
- """模拟下单(买入/卖出)
- Args:
- trade_type: 交易类型 "buy" 或 "sell"
- stock_code: 6位股票代码
- quantity: 委托数量(必须为100的整数倍)
- price: 委托价格(None表示市价委托)
- Returns:
- {
- "success": True/False,
- "order_id": str,
- "message": str,
- "error": str or None
- }
- """
- result = {
- "success": False,
- "order_id": "",
- "message": "",
- "error": None,
- }
- # 参数校验
- if trade_type not in ("buy", "sell"):
- result["error"] = "交易类型无效,请使用 buy 或 sell"
- return result
- if not stock_code or len(str(stock_code)) < 6:
- result["error"] = "请输入有效的6位股票代码"
- return result
- if quantity <= 0:
- result["error"] = "委托数量必须大于0"
- return result
- if quantity % 100 != 0:
- result["error"] = "A股交易数量必须为100股的整数倍"
- return result
- api_error = _check_api_ready()
- if api_error:
- result["error"] = api_error["error"]
- return result
- try:
- body = {
- "type": trade_type,
- "stockCode": str(stock_code),
- "quantity": int(quantity),
- "useMarketPrice": price is None,
- }
- if price is not None:
- body["price"] = float(price)
- raw = _make_request("/api/claw/mockTrading/trade", body)
- if not raw.get("success") and str(raw.get("code")) != "200":
- result["error"] = raw.get("message", "下单失败")
- return result
- data = raw.get("data", {})
- order_id = data.get("orderId", "")
- result["success"] = True
- result["order_id"] = order_id
- direction_cn = "买入" if trade_type == "buy" else "卖出"
- price_info = f"@{price}元" if price else "市价"
- result["message"] = f"{direction_cn}委托已提交: {stock_code} {quantity}股 {price_info}"
- return result
- except Exception as e:
- result["error"] = str(e)
- return result
- def cancel_order(order_id: str, stock_code: str = "") -> dict:
- """撤单
- Args:
- order_id: 委托编号
- stock_code: 股票代码(可选)
- Returns:
- {
- "success": True/False,
- "message": str,
- "error": str or None
- }
- """
- result = {
- "success": False,
- "message": "",
- "error": None,
- }
- if not order_id:
- result["error"] = "请提供委托编号"
- return result
- api_error = _check_api_ready()
- if api_error:
- result["error"] = api_error["error"]
- return result
- try:
- body = {
- "type": "order",
- "orderId": str(order_id),
- }
- if stock_code:
- body["stockCode"] = str(stock_code)
- raw = _make_request("/api/claw/mockTrading/cancel", body)
- if not raw.get("success") and str(raw.get("code")) != "200":
- result["error"] = raw.get("message", "撤单失败")
- return result
- result["success"] = True
- result["message"] = f"委托 {order_id} 已撤销"
- return result
- except Exception as e:
- result["error"] = str(e)
- return result
- def cancel_all_orders() -> dict:
- """一键撤单(撤销所有未成交委托)
- Returns:
- {
- "success": True/False,
- "message": str,
- "error": str or None
- }
- """
- result = {
- "success": False,
- "message": "",
- "error": None,
- }
- api_error = _check_api_ready()
- if api_error:
- result["error"] = api_error["error"]
- return result
- try:
- raw = _make_request("/api/claw/mockTrading/cancel", {"type": "all"})
- if not raw.get("success") and str(raw.get("code")) != "200":
- result["error"] = raw.get("message", "一键撤单失败")
- return result
- result["success"] = True
- result["message"] = "所有未成交委托已撤销"
- return result
- except Exception as e:
- result["error"] = str(e)
- return result
|