mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-14 00:07:01 +08:00
Merge pull request #500 from CoderMageFox/feature/aster-field-help-tooltips
feat: add help tooltips for Aster exchange configuration fields
This commit is contained in:
@@ -438,13 +438,23 @@ func (t *AsterTrader) GetBalance() (map[string]interface{}, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔍 调试:打印原始API响应
|
||||||
|
log.Printf("🔍 Aster API原始响应: %s", string(body))
|
||||||
|
|
||||||
// 查找USDT余额
|
// 查找USDT余额
|
||||||
totalBalance := 0.0
|
totalBalance := 0.0
|
||||||
availableBalance := 0.0
|
availableBalance := 0.0
|
||||||
crossUnPnl := 0.0
|
crossUnPnl := 0.0
|
||||||
|
|
||||||
for _, bal := range balances {
|
for _, bal := range balances {
|
||||||
|
// 🔍 调试:打印每条余额记录
|
||||||
|
log.Printf("🔍 余额记录: %+v", bal)
|
||||||
|
|
||||||
if asset, ok := bal["asset"].(string); ok && asset == "USDT" {
|
if asset, ok := bal["asset"].(string); ok && asset == "USDT" {
|
||||||
|
// 🔍 调试:打印USDT余额详情
|
||||||
|
log.Printf("🔍 USDT余额详情: balance=%v, availableBalance=%v, crossUnPnl=%v",
|
||||||
|
bal["balance"], bal["availableBalance"], bal["crossUnPnl"])
|
||||||
|
|
||||||
if wb, ok := bal["balance"].(string); ok {
|
if wb, ok := bal["balance"].(string); ok {
|
||||||
totalBalance, _ = strconv.ParseFloat(wb, 64)
|
totalBalance, _ = strconv.ParseFloat(wb, 64)
|
||||||
}
|
}
|
||||||
@@ -458,11 +468,25 @@ func (t *AsterTrader) GetBalance() (map[string]interface{}, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ Aster API完全兼容Binance API格式
|
||||||
|
// balance字段 = wallet balance(不包含未实现盈亏)
|
||||||
|
// crossUnPnl = unrealized profit(未实现盈亏)
|
||||||
|
// crossWalletBalance = balance + crossUnPnl(全仓钱包余额,包含盈亏)
|
||||||
|
//
|
||||||
|
// 参考Binance官方文档:
|
||||||
|
// - Account Information V2: marginBalance = walletBalance + unrealizedProfit
|
||||||
|
// - Balance V3: crossWalletBalance = balance + crossUnPnl
|
||||||
|
|
||||||
|
log.Printf("✓ Aster API返回: 钱包余额=%.2f, 未实现盈亏=%.2f, 可用余额=%.2f",
|
||||||
|
totalBalance,
|
||||||
|
crossUnPnl,
|
||||||
|
availableBalance)
|
||||||
|
|
||||||
// 返回与Binance相同的字段名,确保AutoTrader能正确解析
|
// 返回与Binance相同的字段名,确保AutoTrader能正确解析
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"totalWalletBalance": totalBalance,
|
"totalWalletBalance": totalBalance, // 钱包余额(不含未实现盈亏)
|
||||||
"availableBalance": availableBalance,
|
"availableBalance": availableBalance,
|
||||||
"totalUnrealizedProfit": crossUnPnl,
|
"totalUnrealizedProfit": crossUnPnl, // 未实现盈亏
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
Users,
|
Users,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
BookOpen,
|
BookOpen,
|
||||||
|
HelpCircle,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
|
||||||
// 获取友好的AI模型名称
|
// 获取友好的AI模型名称
|
||||||
@@ -1064,6 +1065,51 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tooltip Helper Component
|
||||||
|
function Tooltip({
|
||||||
|
content,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
content: string
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
const [show, setShow] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative inline-block">
|
||||||
|
<div
|
||||||
|
onMouseEnter={() => setShow(true)}
|
||||||
|
onMouseLeave={() => setShow(false)}
|
||||||
|
onClick={() => setShow(!show)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
{show && (
|
||||||
|
<div
|
||||||
|
className="absolute z-10 px-3 py-2 text-sm rounded-lg shadow-lg w-64 left-1/2 transform -translate-x-1/2 bottom-full mb-2"
|
||||||
|
style={{
|
||||||
|
background: '#2B3139',
|
||||||
|
color: '#EAECEF',
|
||||||
|
border: '1px solid #474D57',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
<div
|
||||||
|
className="absolute left-1/2 transform -translate-x-1/2 top-full"
|
||||||
|
style={{
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
borderLeft: '6px solid transparent',
|
||||||
|
borderRight: '6px solid transparent',
|
||||||
|
borderTop: '6px solid #2B3139',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Signal Source Configuration Modal Component
|
// Signal Source Configuration Modal Component
|
||||||
function SignalSourceModal({
|
function SignalSourceModal({
|
||||||
coinPoolUrl,
|
coinPoolUrl,
|
||||||
@@ -1772,10 +1818,16 @@ function ExchangeConfigModal({
|
|||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
className="block text-sm font-semibold mb-2"
|
className="block text-sm font-semibold mb-2 flex items-center gap-2"
|
||||||
style={{ color: '#EAECEF' }}
|
style={{ color: '#EAECEF' }}
|
||||||
>
|
>
|
||||||
{t('user', language)}
|
{t('user', language)}
|
||||||
|
<Tooltip content={t('asterUserDesc', language)}>
|
||||||
|
<HelpCircle
|
||||||
|
className="w-4 h-4 cursor-help"
|
||||||
|
style={{ color: '#F0B90B' }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -1794,10 +1846,16 @@ function ExchangeConfigModal({
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
className="block text-sm font-semibold mb-2"
|
className="block text-sm font-semibold mb-2 flex items-center gap-2"
|
||||||
style={{ color: '#EAECEF' }}
|
style={{ color: '#EAECEF' }}
|
||||||
>
|
>
|
||||||
{t('signer', language)}
|
{t('signer', language)}
|
||||||
|
<Tooltip content={t('asterSignerDesc', language)}>
|
||||||
|
<HelpCircle
|
||||||
|
className="w-4 h-4 cursor-help"
|
||||||
|
style={{ color: '#F0B90B' }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -1816,10 +1874,16 @@ function ExchangeConfigModal({
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
className="block text-sm font-semibold mb-2"
|
className="block text-sm font-semibold mb-2 flex items-center gap-2"
|
||||||
style={{ color: '#EAECEF' }}
|
style={{ color: '#EAECEF' }}
|
||||||
>
|
>
|
||||||
{t('privateKey', language)}
|
{t('privateKey', language)}
|
||||||
|
<Tooltip content={t('asterPrivateKeyDesc', language)}>
|
||||||
|
<HelpCircle
|
||||||
|
className="w-4 h-4 cursor-help"
|
||||||
|
style={{ color: '#F0B90B' }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
@@ -1873,6 +1937,9 @@ function ExchangeConfigModal({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs space-y-1" style={{ color: '#848E9C' }}>
|
<div className="text-xs space-y-1" style={{ color: '#848E9C' }}>
|
||||||
|
{selectedExchange.id === 'aster' && (
|
||||||
|
<div>{t('asterUsdtWarning', language)}</div>
|
||||||
|
)}
|
||||||
<div>{t('exchangeConfigWarning1', language)}</div>
|
<div>{t('exchangeConfigWarning1', language)}</div>
|
||||||
<div>{t('exchangeConfigWarning2', language)}</div>
|
<div>{t('exchangeConfigWarning2', language)}</div>
|
||||||
<div>{t('exchangeConfigWarning3', language)}</div>
|
<div>{t('exchangeConfigWarning3', language)}</div>
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ export function TraderConfigModal({
|
|||||||
const [selectedCoins, setSelectedCoins] = useState<string[]>([])
|
const [selectedCoins, setSelectedCoins] = useState<string[]>([])
|
||||||
const [showCoinSelector, setShowCoinSelector] = useState(false)
|
const [showCoinSelector, setShowCoinSelector] = useState(false)
|
||||||
const [promptTemplates, setPromptTemplates] = useState<{ name: string }[]>([])
|
const [promptTemplates, setPromptTemplates] = useState<{ name: string }[]>([])
|
||||||
|
const [isFetchingBalance, setIsFetchingBalance] = useState(false)
|
||||||
|
const [balanceFetchError, setBalanceFetchError] = useState<string>('')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (traderData) {
|
if (traderData) {
|
||||||
@@ -182,6 +184,45 @@ export function TraderConfigModal({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFetchCurrentBalance = async () => {
|
||||||
|
if (!isEditMode || !traderData?.trader_id) {
|
||||||
|
setBalanceFetchError('只有在编辑模式下才能获取当前余额');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsFetchingBalance(true);
|
||||||
|
setBalanceFetchError('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
const response = await fetch(`/api/account?trader_id=${traderData.trader_id}`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('获取账户余额失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// total_equity = 当前账户净值(包含未实现盈亏)
|
||||||
|
// 这应该作为新的初始余额
|
||||||
|
const currentBalance = data.total_equity || data.balance || 0;
|
||||||
|
|
||||||
|
setFormData(prev => ({ ...prev, initial_balance: currentBalance }));
|
||||||
|
|
||||||
|
// 显示成功提示
|
||||||
|
console.log('已获取当前余额:', currentBalance);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取余额失败:', error);
|
||||||
|
setBalanceFetchError('获取余额失败,请检查网络连接');
|
||||||
|
} finally {
|
||||||
|
setIsFetchingBalance(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (!onSave) return
|
if (!onSave) return
|
||||||
|
|
||||||
@@ -346,9 +387,22 @@ export function TraderConfigModal({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm text-[#EAECEF] block mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
初始余额 ($)
|
<label className="text-sm text-[#EAECEF]">
|
||||||
</label>
|
初始余额 ($)
|
||||||
|
{!isEditMode && <span className="text-[#F0B90B] ml-1">*</span>}
|
||||||
|
</label>
|
||||||
|
{isEditMode && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleFetchCurrentBalance}
|
||||||
|
disabled={isFetchingBalance}
|
||||||
|
className="px-3 py-1 text-xs bg-[#F0B90B] text-black rounded hover:bg-[#E1A706] transition-colors disabled:bg-[#848E9C] disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{isFetchingBalance ? '获取中...' : '获取当前余额'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.initial_balance}
|
value={formData.initial_balance}
|
||||||
@@ -360,8 +414,22 @@ export function TraderConfigModal({
|
|||||||
}
|
}
|
||||||
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
|
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
|
||||||
min="100"
|
min="100"
|
||||||
step="100"
|
step="0.01"
|
||||||
/>
|
/>
|
||||||
|
{!isEditMode && (
|
||||||
|
<p className="text-xs text-[#F0B90B] mt-1 flex items-center gap-1">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/><line x1="12" x2="12" y1="9" y2="13"/><line x1="12" x2="12.01" y1="17" y2="17"/></svg>
|
||||||
|
请输入您交易所账户的当前实际余额。如果输入不准确,P&L统计将会错误。
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{isEditMode && (
|
||||||
|
<p className="text-xs text-[#848E9C] mt-1">
|
||||||
|
点击"获取当前余额"按钮可自动获取您交易所账户的当前净值
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{balanceFetchError && (
|
||||||
|
<p className="text-xs text-red-500 mt-1">{balanceFetchError}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -197,6 +197,14 @@ export const translations = {
|
|||||||
'Hyperliquid uses private key for trading authentication',
|
'Hyperliquid uses private key for trading authentication',
|
||||||
hyperliquidWalletAddressDesc:
|
hyperliquidWalletAddressDesc:
|
||||||
'Wallet address corresponding to the private key',
|
'Wallet address corresponding to the private key',
|
||||||
|
asterUserDesc:
|
||||||
|
'Main wallet address - The EVM wallet address you use to log in to Aster',
|
||||||
|
asterSignerDesc:
|
||||||
|
'API wallet address - Generate from https://www.asterdex.com/en/api-wallet',
|
||||||
|
asterPrivateKeyDesc:
|
||||||
|
'API wallet private key - Get from https://www.asterdex.com/en/api-wallet (only used locally for signing, never transmitted)',
|
||||||
|
asterUsdtWarning:
|
||||||
|
'Important: Aster only tracks USDT balance. Please ensure you use USDT as margin currency to avoid P&L calculation errors caused by price fluctuations of other assets (BNB, ETH, etc.)',
|
||||||
testnetDescription:
|
testnetDescription:
|
||||||
'Enable to connect to exchange test environment for simulated trading',
|
'Enable to connect to exchange test environment for simulated trading',
|
||||||
securityWarning: 'Security Warning',
|
securityWarning: 'Security Warning',
|
||||||
@@ -658,7 +666,15 @@ export const translations = {
|
|||||||
enterPassphrase: '输入Passphrase (OKX必填)',
|
enterPassphrase: '输入Passphrase (OKX必填)',
|
||||||
hyperliquidPrivateKeyDesc: 'Hyperliquid 使用私钥进行交易认证',
|
hyperliquidPrivateKeyDesc: 'Hyperliquid 使用私钥进行交易认证',
|
||||||
hyperliquidWalletAddressDesc: '与私钥对应的钱包地址',
|
hyperliquidWalletAddressDesc: '与私钥对应的钱包地址',
|
||||||
testnetDescription: '启用后将连接到交易所测试环境,用于模拟交易',
|
asterUserDesc:
|
||||||
|
'主钱包地址 - 您用于登录 Aster 的 EVM 钱包地址',
|
||||||
|
asterSignerDesc:
|
||||||
|
'API 钱包地址 - 从 https://www.asterdex.com/zh-CN/api-wallet 生成',
|
||||||
|
asterPrivateKeyDesc:
|
||||||
|
'API 钱包私钥 - 从 https://www.asterdex.com/zh-CN/api-wallet 获取(仅在本地用于签名,不会被传输)',
|
||||||
|
asterUsdtWarning:
|
||||||
|
'重要提示:Aster 仅统计 USDT 余额。请确保您使用 USDT 作为保证金币种,避免其他资产(BNB、ETH等)的价格波动导致盈亏统计错误',
|
||||||
|
testnetDescription: '启用后将连接到交易所测试环境,用于模拟交易',
|
||||||
securityWarning: '安全提示',
|
securityWarning: '安全提示',
|
||||||
saveConfiguration: '保存配置',
|
saveConfiguration: '保存配置',
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user