Initial commit

This commit is contained in:
2025-10-11 11:55:25 +08:00
parent 467dad8449
commit 8107dee8d3
2879 changed files with 610575 additions and 0 deletions

View File

@@ -0,0 +1,383 @@
// src/views/TradingSimulation/hooks/useTradingAccount.js - 模拟盘账户管理 Hook
import { useState, useCallback } from 'react';
import { useAuth } from '../../../contexts/AuthContext';
// API 基础URL - 修复HTTPS混合内容问题
const API_BASE_URL = process.env.NODE_ENV === 'production'
? '' // 生产环境使用相对路径通过nginx代理
: (process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001');
// 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') {
console.log('🔧 调试模式:模拟股票搜索', 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) {
console.error('搜索股票失败:', error);
return [];
}
}, [user]);
// 刷新账户数据
const refreshAccount = useCallback(async () => {
// 调试模式使用模拟数据因为后端API可能有CORS问题
if (!user || user.id === 'demo') {
console.log('🔧 调试模式:使用模拟账户数据');
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) {
console.error('刷新账户数据失败:', err);
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') {
console.log('🔧 调试模式:模拟买入', { 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') {
console.log('🔧 调试模式:模拟卖出', { 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) {
console.error('获取交易记录失败:', error);
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) {
console.error('获取资产历史失败:', error);
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) {
console.error('获取股票行情失败:', error);
return {};
}
}, []);
return {
account,
positions,
tradingHistory,
isLoading,
error,
stockQuotes,
buyStock,
sellStock,
cancelOrder,
refreshAccount,
searchStocks,
getTransactions,
getAssetHistory,
getStockQuotes
};
}