mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-09 22:10:57 +08:00
fix: adaptive price precision for meme coins
- Add adaptivePriceRound() in store/position.go for database storage - Update position_builder.go to use adaptive precision for entry/exit prices - Add Gate to OrderSync skip list in auto_trader.go - Add debug logging in gate/order_sync.go for price parsing issues - Create web/src/utils/format.ts with formatPrice() for frontend display - Update TraderDashboardPage.tsx and PositionHistory.tsx to use adaptive formatting Fixes issue where meme coin prices (e.g. 0.000000166) displayed as 0.0000
This commit is contained in:
@@ -3,6 +3,7 @@ import { api } from '../lib/api'
|
||||
import { useLanguage } from '../contexts/LanguageContext'
|
||||
import { t } from '../i18n/translations'
|
||||
import { MetricTooltip } from './MetricTooltip'
|
||||
import { formatPrice, formatQuantity } from '../utils/format'
|
||||
import type {
|
||||
HistoricalPosition,
|
||||
TraderStats,
|
||||
@@ -14,7 +15,7 @@ interface PositionHistoryProps {
|
||||
traderId: string
|
||||
}
|
||||
|
||||
// Format number with proper decimals
|
||||
// Format number with proper decimals (for large numbers)
|
||||
function formatNumber(value: number, decimals: number = 2): string {
|
||||
if (Math.abs(value) >= 1000000) {
|
||||
return (value / 1000000).toFixed(2) + 'M'
|
||||
@@ -25,14 +26,6 @@ function formatNumber(value: number, decimals: number = 2): string {
|
||||
return value.toFixed(decimals)
|
||||
}
|
||||
|
||||
// Format price with proper decimals
|
||||
function formatPrice(price: number): string {
|
||||
if (!price || price === 0) return '-'
|
||||
if (price >= 1000) return price.toFixed(2)
|
||||
if (price >= 1) return price.toFixed(4)
|
||||
return price.toFixed(6)
|
||||
}
|
||||
|
||||
// Format duration from minutes
|
||||
function formatDuration(minutes: number): string {
|
||||
if (!minutes || minutes <= 0) return '-'
|
||||
@@ -300,7 +293,7 @@ function PositionRow({ position }: { position: HistoricalPosition }) {
|
||||
|
||||
{/* Quantity */}
|
||||
<td className="py-3 px-4 text-right font-mono" style={{ color: '#848E9C' }}>
|
||||
{displayQty.toFixed(4)}
|
||||
{formatQuantity(displayQty)}
|
||||
</td>
|
||||
|
||||
{/* Position Value (Entry Price * Quantity) */}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { DecisionCard } from '../components/DecisionCard'
|
||||
import { PositionHistory } from '../components/PositionHistory'
|
||||
import { PunkAvatar, getTraderAvatar } from '../components/PunkAvatar'
|
||||
import { confirmToast, notify } from '../lib/notify'
|
||||
import { formatPrice, formatQuantity } from '../utils/format'
|
||||
import { t, type Language } from '../i18n/translations'
|
||||
import { LogOut, Loader2, Eye, EyeOff, Copy, Check } from 'lucide-react'
|
||||
import { DeepVoidBackground } from '../components/DeepVoidBackground'
|
||||
@@ -653,9 +654,9 @@ export function TraderDashboardPage({
|
||||
{language === 'zh' ? '平仓' : 'Close'}
|
||||
</button>
|
||||
</td>
|
||||
<td className="px-1 py-3 font-mono whitespace-nowrap text-right text-nofx-text-main hidden md:table-cell">{pos.entry_price.toFixed(4)}</td>
|
||||
<td className="px-1 py-3 font-mono whitespace-nowrap text-right text-nofx-text-main hidden md:table-cell">{pos.mark_price.toFixed(4)}</td>
|
||||
<td className="px-1 py-3 font-mono whitespace-nowrap text-right text-nofx-text-main">{pos.quantity.toFixed(4)}</td>
|
||||
<td className="px-1 py-3 font-mono whitespace-nowrap text-right text-nofx-text-main hidden md:table-cell">{formatPrice(pos.entry_price)}</td>
|
||||
<td className="px-1 py-3 font-mono whitespace-nowrap text-right text-nofx-text-main hidden md:table-cell">{formatPrice(pos.mark_price)}</td>
|
||||
<td className="px-1 py-3 font-mono whitespace-nowrap text-right text-nofx-text-main">{formatQuantity(pos.quantity)}</td>
|
||||
<td className="px-1 py-3 font-mono font-bold whitespace-nowrap text-right text-nofx-text-main hidden md:table-cell">{(pos.quantity * pos.mark_price).toFixed(2)}</td>
|
||||
<td className="px-1 py-3 font-mono whitespace-nowrap text-center text-nofx-gold hidden md:table-cell">{pos.leverage}x</td>
|
||||
<td className="px-1 py-3 font-mono whitespace-nowrap text-right">
|
||||
@@ -667,7 +668,7 @@ export function TraderDashboardPage({
|
||||
{pos.unrealized_pnl.toFixed(2)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-1 py-3 font-mono whitespace-nowrap text-right text-nofx-text-muted hidden md:table-cell">{pos.liquidation_price.toFixed(4)}</td>
|
||||
<td className="px-1 py-3 font-mono whitespace-nowrap text-right text-nofx-text-muted hidden md:table-cell">{formatPrice(pos.liquidation_price)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
135
web/src/utils/format.ts
Normal file
135
web/src/utils/format.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* 数字格式化工具
|
||||
*
|
||||
* formatPrice: 根据数值大小自适应显示精度,避免极小数显示为 0.0000
|
||||
*/
|
||||
|
||||
/**
|
||||
* 格式化价格,根据数值大小自适应精度
|
||||
* 对于极小的数字(如 meme 币价格 0.000000166),会保留足够的有效数字
|
||||
*
|
||||
* @param price 价格数值
|
||||
* @param minDecimals 最少小数位数(默认 2)
|
||||
* @returns 格式化后的字符串
|
||||
*/
|
||||
export function formatPrice(price: number | undefined | null, minDecimals = 2): string {
|
||||
if (price === undefined || price === null || isNaN(price)) {
|
||||
return '0'
|
||||
}
|
||||
|
||||
if (price === 0) {
|
||||
return '0'
|
||||
}
|
||||
|
||||
const absPrice = Math.abs(price)
|
||||
|
||||
// 根据价格大小决定显示精度
|
||||
let decimals: number
|
||||
if (absPrice < 0.000001) {
|
||||
// 极小价格 (如 CHEEMS, SHIB 等 meme 币)
|
||||
decimals = 15
|
||||
} else if (absPrice < 0.0001) {
|
||||
// 很小价格 (如 PEPE, FLOKI, BONK)
|
||||
decimals = 12
|
||||
} else if (absPrice < 0.01) {
|
||||
// 小价格
|
||||
decimals = 10
|
||||
} else if (absPrice < 1) {
|
||||
// 中等价格
|
||||
decimals = 8
|
||||
} else if (absPrice < 1000) {
|
||||
// 正常价格
|
||||
decimals = 4
|
||||
} else {
|
||||
// 大价格 (如 BTC)
|
||||
decimals = 2
|
||||
}
|
||||
|
||||
// 确保至少有 minDecimals 位小数
|
||||
decimals = Math.max(decimals, minDecimals)
|
||||
|
||||
// 格式化并去除尾部多余的零
|
||||
let formatted = price.toFixed(decimals)
|
||||
|
||||
// 去除尾部零(保留小数点后至少 minDecimals 位)
|
||||
if (formatted.includes('.')) {
|
||||
// 先去掉所有尾部零
|
||||
formatted = formatted.replace(/\.?0+$/, '')
|
||||
// 如果小数位不足 minDecimals,补零
|
||||
const dotIndex = formatted.indexOf('.')
|
||||
if (dotIndex === -1) {
|
||||
formatted += '.' + '0'.repeat(minDecimals)
|
||||
} else {
|
||||
const currentDecimals = formatted.length - dotIndex - 1
|
||||
if (currentDecimals < minDecimals) {
|
||||
formatted += '0'.repeat(minDecimals - currentDecimals)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return formatted
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数量,根据数值大小自适应精度
|
||||
*
|
||||
* @param quantity 数量
|
||||
* @param minDecimals 最少小数位数(默认 2)
|
||||
* @returns 格式化后的字符串
|
||||
*/
|
||||
export function formatQuantity(quantity: number | undefined | null, minDecimals = 2): string {
|
||||
if (quantity === undefined || quantity === null || isNaN(quantity)) {
|
||||
return '0'
|
||||
}
|
||||
|
||||
if (quantity === 0) {
|
||||
return '0'
|
||||
}
|
||||
|
||||
const absQty = Math.abs(quantity)
|
||||
|
||||
let decimals: number
|
||||
if (absQty >= 1000000) {
|
||||
decimals = 0
|
||||
} else if (absQty >= 1000) {
|
||||
decimals = 2
|
||||
} else if (absQty >= 1) {
|
||||
decimals = 4
|
||||
} else {
|
||||
decimals = 8
|
||||
}
|
||||
|
||||
decimals = Math.max(decimals, minDecimals)
|
||||
|
||||
let formatted = quantity.toFixed(decimals)
|
||||
if (formatted.includes('.')) {
|
||||
formatted = formatted.replace(/\.?0+$/, '')
|
||||
const dotIndex = formatted.indexOf('.')
|
||||
if (dotIndex === -1) {
|
||||
formatted += '.' + '0'.repeat(minDecimals)
|
||||
} else {
|
||||
const currentDecimals = formatted.length - dotIndex - 1
|
||||
if (currentDecimals < minDecimals) {
|
||||
formatted += '0'.repeat(minDecimals - currentDecimals)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return formatted
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化百分比
|
||||
*
|
||||
* @param value 百分比值
|
||||
* @param decimals 小数位数(默认 2)
|
||||
* @returns 格式化后的字符串
|
||||
*/
|
||||
export function formatPercent(value: number | undefined | null, decimals = 2): string {
|
||||
if (value === undefined || value === null || isNaN(value)) {
|
||||
return '0.00'
|
||||
}
|
||||
return value.toFixed(decimals)
|
||||
}
|
||||
|
||||
export default { formatPrice, formatQuantity, formatPercent }
|
||||
Reference in New Issue
Block a user