// src/views/TradingSimulation/hooks/useTradingAccount.js - 模拟盘账户管理 Hook import { useState, useCallback } from 'react'; import { useAuth } from '../../../contexts/AuthContext'; import { logger } from '../../../utils/logger'; import { getApiBase } from '../../../utils/apiConfig'; // API 基础URL - 修复HTTPS混合内容问题 const API_BASE_URL = getApiBase(); // API 请求封装 const apiRequest = async (url, options = {}) => { const response = await fetch(`${API_BASE_URL}${url}`, { credentials: 'include', // 包含session cookie headers: { 'Content-Type': 'application/json', ...options.headers }, ...options }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.error || `HTTP ${response.status}`); } return response.json(); }; // 数据字段映射函数 const mapAccountData = (backendData) => { return { id: backendData.account_id, accountName: backendData.account_name, initialCash: backendData.initial_capital, availableCash: backendData.available_cash, frozenCash: backendData.frozen_cash, marketValue: backendData.position_value, totalAssets: backendData.total_assets, totalProfit: backendData.total_profit, totalProfitPercent: backendData.total_profit_rate, dailyProfit: backendData.daily_profit, dailyProfitRate: backendData.daily_profit_rate, riskLevel: 'MEDIUM', // 默认值 marginBalance: 0, shortBalance: 0, lastUpdated: backendData.updated_at, createdAt: backendData.created_at }; }; const mapPositionData = (backendPositions) => { return backendPositions.map(pos => ({ id: pos.id, stockCode: pos.stock_code, stockName: pos.stock_name, quantity: pos.position_qty, availableQuantity: pos.available_qty, frozenQuantity: pos.frozen_qty, avgPrice: pos.avg_cost, currentPrice: pos.current_price, totalCost: pos.position_qty * pos.avg_cost, marketValue: pos.market_value, profit: pos.profit, profitRate: pos.profit_rate, todayProfit: pos.today_profit, todayProfitRate: pos.today_profit_rate, updatedAt: pos.updated_at })); }; const mapOrderData = (backendOrders) => { return backendOrders.map(order => ({ id: order.id, orderId: order.order_no, stockCode: order.stock_code, stockName: order.stock_name, type: order.order_type, // 添加 type 字段 orderType: order.order_type, priceType: order.price_type, orderPrice: order.order_price, quantity: order.order_qty, filledQuantity: order.filled_qty, price: order.filled_price, // 添加 price 字段 filledPrice: order.filled_price, totalAmount: order.filled_amount, // 添加 totalAmount 字段 filledAmount: order.filled_amount, commission: order.commission, stampTax: order.stamp_tax, transferFee: order.transfer_fee, totalFee: order.total_fee, status: order.status, rejectReason: order.reject_reason, createdAt: order.order_time, filledAt: order.filled_time })); }; export function useTradingAccount() { const { user } = useAuth(); const [account, setAccount] = useState(null); const [positions, setPositions] = useState([]); const [tradingHistory, setTradingHistory] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [stockQuotes, setStockQuotes] = useState({}); // 搜索股票 const searchStocks = useCallback(async (keyword) => { // 调试模式:返回模拟数据 if (!user || user.id === 'demo') { logger.debug('useTradingAccount', '调试模式:模拟股票搜索', { keyword }); const mockStocks = [ { stock_code: '000001', stock_name: '平安银行', current_price: 12.50, pinyin_abbr: 'payh', security_type: 'A股', exchange: '深交所' }, { stock_code: '600036', stock_name: '招商银行', current_price: 42.80, pinyin_abbr: 'zsyh', security_type: 'A股', exchange: '上交所' }, { stock_code: '688256', stock_name: '寒武纪', current_price: 1394.94, pinyin_abbr: 'hwj', security_type: 'A股', exchange: '上交所科创板' } ]; return mockStocks.filter(stock => stock.stock_code.includes(keyword) || stock.stock_name.includes(keyword) || stock.pinyin_abbr.includes(keyword.toLowerCase()) ); } try { const response = await apiRequest(`/api/stocks/search?q=${encodeURIComponent(keyword)}&limit=10`); return response.data || []; } catch (error) { logger.error('useTradingAccount', 'searchStocks', error, { keyword }); return []; } }, [user]); // 刷新账户数据 const refreshAccount = useCallback(async () => { // 调试模式:使用模拟数据(因为后端API可能有CORS问题) if (!user || user.id === 'demo') { logger.debug('useTradingAccount', '调试模式:使用模拟账户数据', { userId: user?.id }); setAccount({ id: 'demo', accountName: '演示账户', initialCash: 1000000, availableCash: 950000, frozenCash: 0, marketValue: 50000, totalAssets: 1000000, totalProfit: 0, totalProfitPercent: 0, dailyProfit: 0, dailyProfitRate: 0, riskLevel: 'MEDIUM', marginBalance: 0, shortBalance: 0, lastUpdated: new Date().toISOString(), createdAt: new Date().toISOString() }); setPositions([]); setTradingHistory([]); setIsLoading(false); return; } setIsLoading(true); setError(null); try { // 获取账户信息 const accountResponse = await apiRequest('/api/simulation/account'); setAccount(mapAccountData(accountResponse.data)); // 获取持仓信息 const positionsResponse = await apiRequest('/api/simulation/positions'); setPositions(mapPositionData(positionsResponse.data || [])); // 获取交易历史 const ordersResponse = await apiRequest('/api/simulation/orders?limit=100'); setTradingHistory(mapOrderData(ordersResponse.data || [])); } catch (err) { logger.error('useTradingAccount', 'refreshAccount', err, { userId: user?.id }); setError('加载账户数据失败: ' + err.message); } finally { setIsLoading(false); } }, [user]); // 买入股票 const buyStock = useCallback(async (stockCode, quantity, orderType = 'MARKET', limitPrice = null) => { if (!account) { throw new Error('账户未初始化'); } // 调试模式:模拟买入成功 if (!user || user.id === 'demo') { logger.debug('useTradingAccount', '调试模式:模拟买入', { stockCode, quantity, orderType }); return { success: true, orderId: 'demo_' + Date.now() }; } setIsLoading(true); try { const requestData = { stock_code: stockCode, order_type: 'BUY', order_qty: quantity, price_type: orderType }; const response = await apiRequest('/api/simulation/place-order', { method: 'POST', body: JSON.stringify(requestData) }); // 刷新账户数据 await refreshAccount(); return { success: true, orderId: response.data.order_no }; } catch (err) { throw new Error('买入失败: ' + err.message); } finally { setIsLoading(false); } }, [account, user, refreshAccount]); // 卖出股票 const sellStock = useCallback(async (stockCode, quantity, orderType = 'MARKET', limitPrice = null) => { if (!account) { throw new Error('账户未初始化'); } // 调试模式:模拟卖出成功 if (!user || user.id === 'demo') { logger.debug('useTradingAccount', '调试模式:模拟卖出', { stockCode, quantity, orderType }); return { success: true, orderId: 'demo_' + Date.now() }; } setIsLoading(true); try { const requestData = { stock_code: stockCode, order_type: 'SELL', order_qty: quantity, price_type: orderType }; const response = await apiRequest('/api/simulation/place-order', { method: 'POST', body: JSON.stringify(requestData) }); // 刷新账户数据 await refreshAccount(); return { success: true, orderId: response.data.order_no }; } catch (err) { throw new Error('卖出失败: ' + err.message); } finally { setIsLoading(false); } }, [account, user, refreshAccount]); // 撤销订单 const cancelOrder = useCallback(async (orderId) => { setIsLoading(true); try { const response = await apiRequest(`/api/simulation/cancel-order/${orderId}`, { method: 'POST' }); // 刷新交易历史 const ordersResponse = await apiRequest('/api/simulation/orders?limit=100'); setTradingHistory(mapOrderData(ordersResponse.data || [])); return { success: true }; } catch (err) { throw new Error('撤单失败: ' + err.message); } finally { setIsLoading(false); } }, []); // 获取交易记录 const getTransactions = useCallback(async (options = {}) => { try { const params = new URLSearchParams(); if (options.limit) params.append('limit', options.limit); if (options.date) params.append('date', options.date); const response = await apiRequest(`/api/simulation/transactions?${params.toString()}`); return response.data || []; } catch (error) { logger.error('useTradingAccount', 'getTransactions', error, options); return []; } }, []); // 获取资产历史 const getAssetHistory = useCallback(async (days = 30) => { // 调试模式:demo用户返回模拟数据,避免CORS if (!user || user.id === 'demo') { const now = Date.now(); const data = Array.from({ length: days }, (_, i) => { const date = new Date(now - (days - 1 - i) * 24 * 3600 * 1000); // 简单生成一条平滑的收益曲线 const value = Math.sin(i / 5) * 0.01 + 0.001 * i; return { date: date.toISOString().slice(0, 10), value }; }); return data; } try { const response = await apiRequest(`/api/simulation/statistics?days=${days}`); return response.data?.daily_returns || []; } catch (error) { logger.error('useTradingAccount', 'getAssetHistory', error, { days, userId: user?.id }); return []; } }, [user]); // 获取股票实时行情(如果需要的话) const getStockQuotes = useCallback(async (stockCodes) => { try { // 这里可以调用自选股实时行情接口 const response = await apiRequest('/api/account/watchlist/quotes'); if (response.success) { const quotes = {}; response.data.forEach(item => { quotes[item.stock_code] = { name: item.stock_name, price: item.current_price, change: item.change, changePercent: item.change_percent }; }); setStockQuotes(quotes); return quotes; } return {}; } catch (error) { logger.error('useTradingAccount', 'getStockQuotes', error, { stockCodes }); return {}; } }, []); return { account, positions, tradingHistory, isLoading, error, stockQuotes, buyStock, sellStock, cancelOrder, refreshAccount, searchStocks, getTransactions, getAssetHistory, getStockQuotes }; }