mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-15 00:36:56 +08:00
Merge branch 'tinkle-community:main' into main
This commit is contained in:
@@ -57,7 +57,7 @@ func corsMiddleware() gin.HandlerFunc {
|
|||||||
// setupRoutes 设置路由
|
// setupRoutes 设置路由
|
||||||
func (s *Server) setupRoutes() {
|
func (s *Server) setupRoutes() {
|
||||||
// 健康检查
|
// 健康检查
|
||||||
s.router.GET("/health", s.handleHealth)
|
s.router.Any("/health", s.handleHealth)
|
||||||
|
|
||||||
// API路由组
|
// API路由组
|
||||||
api := s.router.Group("/api")
|
api := s.router.Group("/api")
|
||||||
|
|||||||
@@ -195,11 +195,11 @@ func (t *AsterTrader) formatQuantity(symbol string, quantity float64) (float64,
|
|||||||
func (t *AsterTrader) formatFloatWithPrecision(value float64, precision int) string {
|
func (t *AsterTrader) formatFloatWithPrecision(value float64, precision int) string {
|
||||||
// 使用指定精度格式化
|
// 使用指定精度格式化
|
||||||
formatted := strconv.FormatFloat(value, 'f', precision, 64)
|
formatted := strconv.FormatFloat(value, 'f', precision, 64)
|
||||||
|
|
||||||
// 去除末尾的0和小数点(如果有)
|
// 去除末尾的0和小数点(如果有)
|
||||||
formatted = strings.TrimRight(formatted, "0")
|
formatted = strings.TrimRight(formatted, "0")
|
||||||
formatted = strings.TrimRight(formatted, ".")
|
formatted = strings.TrimRight(formatted, ".")
|
||||||
|
|
||||||
return formatted
|
return formatted
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,6 +522,11 @@ func (t *AsterTrader) GetPositions() ([]map[string]interface{}, error) {
|
|||||||
|
|
||||||
// OpenLong 开多单
|
// OpenLong 开多单
|
||||||
func (t *AsterTrader) OpenLong(symbol string, quantity float64, leverage int) (map[string]interface{}, error) {
|
func (t *AsterTrader) OpenLong(symbol string, quantity float64, leverage int) (map[string]interface{}, error) {
|
||||||
|
// 开仓前先取消所有挂单,防止残留挂单导致仓位叠加
|
||||||
|
if err := t.CancelAllOrders(symbol); err != nil {
|
||||||
|
log.Printf(" ⚠ 取消挂单失败(继续开仓): %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 先设置杠杆
|
// 先设置杠杆
|
||||||
if err := t.SetLeverage(symbol, leverage); err != nil {
|
if err := t.SetLeverage(symbol, leverage); err != nil {
|
||||||
return nil, fmt.Errorf("设置杠杆失败: %w", err)
|
return nil, fmt.Errorf("设置杠杆失败: %w", err)
|
||||||
@@ -556,7 +561,7 @@ func (t *AsterTrader) OpenLong(symbol string, quantity float64, leverage int) (m
|
|||||||
priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision)
|
priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision)
|
||||||
qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision)
|
qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision)
|
||||||
|
|
||||||
log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)",
|
log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)",
|
||||||
limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision)
|
limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision)
|
||||||
|
|
||||||
params := map[string]interface{}{
|
params := map[string]interface{}{
|
||||||
@@ -584,6 +589,11 @@ func (t *AsterTrader) OpenLong(symbol string, quantity float64, leverage int) (m
|
|||||||
|
|
||||||
// OpenShort 开空单
|
// OpenShort 开空单
|
||||||
func (t *AsterTrader) OpenShort(symbol string, quantity float64, leverage int) (map[string]interface{}, error) {
|
func (t *AsterTrader) OpenShort(symbol string, quantity float64, leverage int) (map[string]interface{}, error) {
|
||||||
|
// 开仓前先取消所有挂单,防止残留挂单导致仓位叠加
|
||||||
|
if err := t.CancelAllOrders(symbol); err != nil {
|
||||||
|
log.Printf(" ⚠ 取消挂单失败(继续开仓): %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 先设置杠杆
|
// 先设置杠杆
|
||||||
if err := t.SetLeverage(symbol, leverage); err != nil {
|
if err := t.SetLeverage(symbol, leverage); err != nil {
|
||||||
return nil, fmt.Errorf("设置杠杆失败: %w", err)
|
return nil, fmt.Errorf("设置杠杆失败: %w", err)
|
||||||
@@ -618,7 +628,7 @@ func (t *AsterTrader) OpenShort(symbol string, quantity float64, leverage int) (
|
|||||||
priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision)
|
priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision)
|
||||||
qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision)
|
qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision)
|
||||||
|
|
||||||
log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)",
|
log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)",
|
||||||
limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision)
|
limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision)
|
||||||
|
|
||||||
params := map[string]interface{}{
|
params := map[string]interface{}{
|
||||||
@@ -693,7 +703,7 @@ func (t *AsterTrader) CloseLong(symbol string, quantity float64) (map[string]int
|
|||||||
priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision)
|
priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision)
|
||||||
qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision)
|
qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision)
|
||||||
|
|
||||||
log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)",
|
log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)",
|
||||||
limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision)
|
limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision)
|
||||||
|
|
||||||
params := map[string]interface{}{
|
params := map[string]interface{}{
|
||||||
@@ -717,6 +727,12 @@ func (t *AsterTrader) CloseLong(symbol string, quantity float64) (map[string]int
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("✓ 平多仓成功: %s 数量: %s", symbol, qtyStr)
|
log.Printf("✓ 平多仓成功: %s 数量: %s", symbol, qtyStr)
|
||||||
|
|
||||||
|
// 平仓后取消该币种的所有挂单(止损止盈单)
|
||||||
|
if err := t.CancelAllOrders(symbol); err != nil {
|
||||||
|
log.Printf(" ⚠ 取消挂单失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -770,7 +786,7 @@ func (t *AsterTrader) CloseShort(symbol string, quantity float64) (map[string]in
|
|||||||
priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision)
|
priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision)
|
||||||
qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision)
|
qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision)
|
||||||
|
|
||||||
log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)",
|
log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)",
|
||||||
limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision)
|
limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision)
|
||||||
|
|
||||||
params := map[string]interface{}{
|
params := map[string]interface{}{
|
||||||
@@ -794,6 +810,12 @@ func (t *AsterTrader) CloseShort(symbol string, quantity float64) (map[string]in
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("✓ 平空仓成功: %s 数量: %s", symbol, qtyStr)
|
log.Printf("✓ 平空仓成功: %s 数量: %s", symbol, qtyStr)
|
||||||
|
|
||||||
|
// 平仓后取消该币种的所有挂单(止损止盈单)
|
||||||
|
if err := t.CancelAllOrders(symbol); err != nil {
|
||||||
|
log.Printf(" ⚠ 取消挂单失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,10 @@ export function EquityChart({ traderId }: EquityChartProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!history || history.length === 0) {
|
// 过滤掉无效数据:total_equity为0或小于1的数据点(API失败导致)
|
||||||
|
const validHistory = history?.filter(point => point.total_equity > 1) || [];
|
||||||
|
|
||||||
|
if (!validHistory || validHistory.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="binance-card p-6">
|
<div className="binance-card p-6">
|
||||||
<h3 className="text-lg font-semibold mb-6" style={{ color: '#EAECEF' }}>{t('accountEquityCurve', language)}</h3>
|
<h3 className="text-lg font-semibold mb-6" style={{ color: '#EAECEF' }}>{t('accountEquityCurve', language)}</h3>
|
||||||
@@ -76,12 +79,12 @@ export function EquityChart({ traderId }: EquityChartProps) {
|
|||||||
// 限制显示最近的数据点(性能优化)
|
// 限制显示最近的数据点(性能优化)
|
||||||
// 如果数据超过2000个点,只显示最近2000个
|
// 如果数据超过2000个点,只显示最近2000个
|
||||||
const MAX_DISPLAY_POINTS = 2000;
|
const MAX_DISPLAY_POINTS = 2000;
|
||||||
const displayHistory = history.length > MAX_DISPLAY_POINTS
|
const displayHistory = validHistory.length > MAX_DISPLAY_POINTS
|
||||||
? history.slice(-MAX_DISPLAY_POINTS)
|
? validHistory.slice(-MAX_DISPLAY_POINTS)
|
||||||
: history;
|
: validHistory;
|
||||||
|
|
||||||
// 计算初始余额(使用第一个数据点,如果无数据则从account获取,最后才用默认值)
|
// 计算初始余额(使用第一个有效数据点,如果无数据则从account获取,最后才用默认值)
|
||||||
const initialBalance = history[0]?.total_equity
|
const initialBalance = validHistory[0]?.total_equity
|
||||||
|| account?.total_equity
|
|| account?.total_equity
|
||||||
|| 100; // 默认值改为100,与常见配置一致
|
|| 100; // 默认值改为100,与常见配置一致
|
||||||
|
|
||||||
@@ -250,12 +253,13 @@ export function EquityChart({ traderId }: EquityChartProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Line
|
<Line
|
||||||
type="monotone"
|
type="natural"
|
||||||
dataKey="value"
|
dataKey="value"
|
||||||
stroke="url(#colorGradient)"
|
stroke="url(#colorGradient)"
|
||||||
strokeWidth={2.5}
|
strokeWidth={3}
|
||||||
dot={chartData.length > 50 ? false : { fill: '#F0B90B', r: 3 }}
|
dot={chartData.length > 50 ? false : { fill: '#F0B90B', r: 3 }}
|
||||||
activeDot={{ r: 6, fill: '#FCD535', stroke: '#F0B90B', strokeWidth: 2 }}
|
activeDot={{ r: 6, fill: '#FCD535', stroke: '#F0B90B', strokeWidth: 2 }}
|
||||||
|
connectNulls={true}
|
||||||
/>
|
/>
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
@@ -277,12 +281,12 @@ export function EquityChart({ traderId }: EquityChartProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('historicalCycles', language)}</div>
|
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('historicalCycles', language)}</div>
|
||||||
<div className="text-xs sm:text-sm font-bold mono" style={{ color: '#EAECEF' }}>{history.length} {t('cycles', language)}</div>
|
<div className="text-xs sm:text-sm font-bold mono" style={{ color: '#EAECEF' }}>{validHistory.length} {t('cycles', language)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('displayRange', language)}</div>
|
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('displayRange', language)}</div>
|
||||||
<div className="text-xs sm:text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
<div className="text-xs sm:text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
||||||
{history.length > MAX_DISPLAY_POINTS
|
{validHistory.length > MAX_DISPLAY_POINTS
|
||||||
? `${t('recent', language)} ${MAX_DISPLAY_POINTS}`
|
? `${t('recent', language)} ${MAX_DISPLAY_POINTS}`
|
||||||
: t('allData', language)
|
: t('allData', language)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user