Files
nofx/web/src/components/TraderConfigModal.tsx
CoderMageFox 47f34f75a2 fix: 修复InitialBalance配置错误导致的P&L统计不准确问题
用户在使用Aster交易员时发现,即使没有开始交易,P&L统计也显示了12.5 USDT (83.33%)的盈亏。经过调查发现:

**根本原因**:
- 实际Aster账户余额:27.5 USDT
- Web界面配置的InitialBalance:15 USDT 
- 错误的P&L计算:27.5 - 15 = 12.5 USDT (83.33%)

**问题根源**:
1. Web界面创建交易员时默认initial_balance为1000 USDT
2. 用户手动修改时容易输入错误的值
3. 缺少自动获取实际余额的功能
4. 缺少明确的警告提示

**文件**: `trader/aster_trader.go`

-  验证Aster API完全兼容Binance格式
- 添加详细的注释说明字段含义
- 添加调试日志以便排查问题
- 确认balance字段不包含未实现盈亏(与Binance一致)

**关键确认**:
```go
//  Aster API完全兼容Binance API格式
// balance字段 = wallet balance(不包含未实现盈亏)
// crossUnPnl = unrealized profit(未实现盈亏)
// crossWalletBalance = balance + crossUnPnl(全仓钱包余额,包含盈亏)
```

**文件**: `web/src/components/TraderConfigModal.tsx`

**新增功能**:
1. **编辑模式**:添加"获取当前余额"按钮
   - 一键从交易所API获取当前账户净值
   - 自动填充到InitialBalance字段
   - 显示加载状态和错误提示

2. **创建模式**:添加警告提示
   - ⚠️ 提醒用户必须输入交易所的当前实际余额
   - 警告:如果输入不准确,P&L统计将会错误

3. **改进输入体验**:
   - 支持小数输入(step="0.01")
   - 必填字段标记(创建模式)
   - 实时错误提示

**代码实现**:
```typescript
const handleFetchCurrentBalance = async () => {
  const response = await fetch(`/api/account?trader_id=${traderData.trader_id}`);
  const data = await response.json();
  const currentBalance = data.total_equity; // 当前净值
  setFormData(prev => ({ ...prev, initial_balance: currentBalance }));
};
```

通过查阅Binance官方文档确认:

| 项目 | Binance | Aster (修复后) |
|------|---------|----------------|
| **余额字段** | balance = 钱包余额(不含盈亏) |  相同 |
| **盈亏字段** | crossUnPnl = 未实现盈亏 |  相同 |
| **总权益** | balance + crossUnPnl |  相同 |
| **P&L计算** | totalEquity - initialBalance |  相同 |

1. 编辑交易员配置
2. 点击"获取当前余额"按钮
3. 系统自动填充正确的InitialBalance
4. 保存配置

1. 查看交易所账户的实际余额
2. 准确输入到InitialBalance字段
3. 注意查看警告提示
4. 完成创建

- [x] 确认Aster API返回格式与Binance一致
- [x] 验证"获取当前余额"功能正常工作
- [x] 确认P&L计算公式正确
- [x] 前端构建成功
- [x] 警告提示正常显示

- **修复**: 解决InitialBalance配置错误导致的P&L统计不准确问题
- **改进**: 提升用户体验,减少配置错误
- **兼容**: 完全向后兼容,不影响现有功能

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 12:49:55 +08:00

702 lines
27 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect } from 'react'
import type { AIModel, Exchange, CreateTraderRequest } from '../types'
import { useLanguage } from '../contexts/LanguageContext'
import { t } from '../i18n/translations'
// 提取下划线后面的名称部分
function getShortName(fullName: string): string {
const parts = fullName.split('_')
return parts.length > 1 ? parts[parts.length - 1] : fullName
}
interface TraderConfigData {
trader_id?: string
trader_name: string
ai_model: string
exchange_id: string
btc_eth_leverage: number
altcoin_leverage: number
trading_symbols: string
custom_prompt: string
override_base_prompt: boolean
system_prompt_template: string
is_cross_margin: boolean
use_coin_pool: boolean
use_oi_top: boolean
initial_balance: number
scan_interval_minutes: number
}
interface TraderConfigModalProps {
isOpen: boolean
onClose: () => void
traderData?: TraderConfigData | null
isEditMode?: boolean
availableModels?: AIModel[]
availableExchanges?: Exchange[]
onSave?: (data: CreateTraderRequest) => Promise<void>
}
export function TraderConfigModal({
isOpen,
onClose,
traderData,
isEditMode = false,
availableModels = [],
availableExchanges = [],
onSave,
}: TraderConfigModalProps) {
const { language } = useLanguage()
const [formData, setFormData] = useState<TraderConfigData>({
trader_name: '',
ai_model: '',
exchange_id: '',
btc_eth_leverage: 5,
altcoin_leverage: 3,
trading_symbols: '',
custom_prompt: '',
override_base_prompt: false,
system_prompt_template: 'default',
is_cross_margin: true,
use_coin_pool: false,
use_oi_top: false,
initial_balance: 1000,
scan_interval_minutes: 3,
})
const [isSaving, setIsSaving] = useState(false)
const [availableCoins, setAvailableCoins] = useState<string[]>([])
const [selectedCoins, setSelectedCoins] = useState<string[]>([])
const [showCoinSelector, setShowCoinSelector] = useState(false)
const [promptTemplates, setPromptTemplates] = useState<{ name: string }[]>([])
const [isFetchingBalance, setIsFetchingBalance] = useState(false)
const [balanceFetchError, setBalanceFetchError] = useState<string>('')
useEffect(() => {
if (traderData) {
setFormData(traderData)
// 设置已选择的币种
if (traderData.trading_symbols) {
const coins = traderData.trading_symbols
.split(',')
.map((s) => s.trim())
.filter((s) => s)
setSelectedCoins(coins)
}
} else if (!isEditMode) {
setFormData({
trader_name: '',
ai_model: availableModels[0]?.id || '',
exchange_id: availableExchanges[0]?.id || '',
btc_eth_leverage: 5,
altcoin_leverage: 3,
trading_symbols: '',
custom_prompt: '',
override_base_prompt: false,
system_prompt_template: 'default',
is_cross_margin: true,
use_coin_pool: false,
use_oi_top: false,
initial_balance: 1000,
scan_interval_minutes: 3,
})
}
// 确保旧数据也有默认的 system_prompt_template
if (traderData && !traderData.system_prompt_template) {
setFormData((prev) => ({
...prev,
system_prompt_template: 'default',
}))
}
}, [traderData, isEditMode, availableModels, availableExchanges])
// 获取系统配置中的币种列表
useEffect(() => {
const fetchConfig = async () => {
try {
const response = await fetch('/api/config')
const config = await response.json()
if (config.default_coins) {
setAvailableCoins(config.default_coins)
}
} catch (error) {
console.error('Failed to fetch config:', error)
// 使用默认币种列表
setAvailableCoins([
'BTCUSDT',
'ETHUSDT',
'SOLUSDT',
'BNBUSDT',
'XRPUSDT',
'DOGEUSDT',
'ADAUSDT',
])
}
}
fetchConfig()
}, [])
// 获取系统提示词模板列表
useEffect(() => {
const fetchPromptTemplates = async () => {
try {
const response = await fetch('/api/prompt-templates')
const data = await response.json()
if (data.templates) {
setPromptTemplates(data.templates)
}
} catch (error) {
console.error('Failed to fetch prompt templates:', error)
// 使用默认模板列表
setPromptTemplates([{ name: 'default' }, { name: 'aggressive' }])
}
}
fetchPromptTemplates()
}, [])
// 当选择的币种改变时,更新输入框
useEffect(() => {
const symbolsString = selectedCoins.join(',')
setFormData((prev) => ({ ...prev, trading_symbols: symbolsString }))
}, [selectedCoins])
if (!isOpen) return null
const handleInputChange = (field: keyof TraderConfigData, value: any) => {
setFormData((prev) => ({ ...prev, [field]: value }))
// 如果是直接编辑trading_symbols同步更新selectedCoins
if (field === 'trading_symbols') {
const coins = value
.split(',')
.map((s: string) => s.trim())
.filter((s: string) => s)
setSelectedCoins(coins)
}
}
const handleCoinToggle = (coin: string) => {
setSelectedCoins((prev) => {
if (prev.includes(coin)) {
return prev.filter((c) => c !== coin)
} else {
return [...prev, coin]
}
})
}
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 () => {
if (!onSave) return
setIsSaving(true)
try {
const saveData: CreateTraderRequest = {
name: formData.trader_name,
ai_model_id: formData.ai_model,
exchange_id: formData.exchange_id,
btc_eth_leverage: formData.btc_eth_leverage,
altcoin_leverage: formData.altcoin_leverage,
trading_symbols: formData.trading_symbols,
custom_prompt: formData.custom_prompt,
override_base_prompt: formData.override_base_prompt,
system_prompt_template: formData.system_prompt_template,
is_cross_margin: formData.is_cross_margin,
use_coin_pool: formData.use_coin_pool,
use_oi_top: formData.use_oi_top,
initial_balance: formData.initial_balance,
scan_interval_minutes: formData.scan_interval_minutes,
}
await onSave(saveData)
onClose()
} catch (error) {
console.error('保存失败:', error)
} finally {
setIsSaving(false)
}
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 backdrop-blur-sm">
<div
className="bg-[#1E2329] border border-[#2B3139] rounded-xl shadow-2xl max-w-3xl w-full mx-4 max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-[#2B3139] bg-gradient-to-r from-[#1E2329] to-[#252B35]">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-[#F0B90B] to-[#E1A706] flex items-center justify-center">
<span className="text-lg">{isEditMode ? '✏️' : ''}</span>
</div>
<div>
<h2 className="text-xl font-bold text-[#EAECEF]">
{isEditMode ? '修改交易员' : '创建交易员'}
</h2>
<p className="text-sm text-[#848E9C] mt-1">
{isEditMode ? '修改交易员配置参数' : '配置新的AI交易员'}
</p>
</div>
</div>
<button
onClick={onClose}
className="w-8 h-8 rounded-lg text-[#848E9C] hover:text-[#EAECEF] hover:bg-[#2B3139] transition-colors flex items-center justify-center"
>
</button>
</div>
{/* Content */}
<div className="p-6 space-y-8">
{/* Basic Info */}
<div className="bg-[#0B0E11] border border-[#2B3139] rounded-lg p-5">
<h3 className="text-lg font-semibold text-[#EAECEF] mb-5 flex items-center gap-2">
🤖
</h3>
<div className="space-y-4">
<div>
<label className="text-sm text-[#EAECEF] block mb-2">
</label>
<input
type="text"
value={formData.trader_name}
onChange={(e) =>
handleInputChange('trader_name', e.target.value)
}
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
placeholder="请输入交易员名称"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm text-[#EAECEF] block mb-2">
AI模型
</label>
<select
value={formData.ai_model}
onChange={(e) =>
handleInputChange('ai_model', e.target.value)
}
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
>
{availableModels.map((model) => (
<option key={model.id} value={model.id}>
{getShortName(model.name || model.id).toUpperCase()}
</option>
))}
</select>
</div>
<div>
<label className="text-sm text-[#EAECEF] block mb-2">
</label>
<select
value={formData.exchange_id}
onChange={(e) =>
handleInputChange('exchange_id', e.target.value)
}
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
>
{availableExchanges.map((exchange) => (
<option key={exchange.id} value={exchange.id}>
{getShortName(
exchange.name || exchange.id
).toUpperCase()}
</option>
))}
</select>
</div>
</div>
</div>
</div>
{/* Trading Configuration */}
<div className="bg-[#0B0E11] border border-[#2B3139] rounded-lg p-5">
<h3 className="text-lg font-semibold text-[#EAECEF] mb-5 flex items-center gap-2">
</h3>
<div className="space-y-4">
{/* 第一行:保证金模式和初始余额 */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm text-[#EAECEF] block mb-2">
</label>
<div className="flex gap-2">
<button
type="button"
onClick={() => handleInputChange('is_cross_margin', true)}
className={`flex-1 px-3 py-2 rounded text-sm ${
formData.is_cross_margin
? 'bg-[#F0B90B] text-black'
: 'bg-[#0B0E11] text-[#848E9C] border border-[#2B3139]'
}`}
>
</button>
<button
type="button"
onClick={() =>
handleInputChange('is_cross_margin', false)
}
className={`flex-1 px-3 py-2 rounded text-sm ${
!formData.is_cross_margin
? 'bg-[#F0B90B] text-black'
: 'bg-[#0B0E11] text-[#848E9C] border border-[#2B3139]'
}`}
>
</button>
</div>
</div>
<div>
<div className="flex items-center justify-between mb-2">
<label className="text-sm text-[#EAECEF]">
($)
{!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
type="number"
value={formData.initial_balance}
onChange={(e) =>
handleInputChange(
'initial_balance',
Number(e.target.value)
)
}
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
min="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>
{/* 第二行AI 扫描决策间隔 */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm text-[#EAECEF] block mb-2">
{t('aiScanInterval', language)}
</label>
<input
type="number"
value={formData.scan_interval_minutes}
onChange={(e) =>
handleInputChange(
'scan_interval_minutes',
Number(e.target.value)
)
}
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
min="1"
max="60"
step="1"
/>
<p className="text-xs text-gray-500 mt-1">
{t('scanIntervalRecommend', language)}
</p>
</div>
<div></div>
</div>
{/* 第三行:杠杆设置 */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm text-[#EAECEF] block mb-2">
BTC/ETH
</label>
<input
type="number"
value={formData.btc_eth_leverage}
onChange={(e) =>
handleInputChange(
'btc_eth_leverage',
Number(e.target.value)
)
}
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
min="1"
max="125"
/>
</div>
<div>
<label className="text-sm text-[#EAECEF] block mb-2">
</label>
<input
type="number"
value={formData.altcoin_leverage}
onChange={(e) =>
handleInputChange(
'altcoin_leverage',
Number(e.target.value)
)
}
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
min="1"
max="75"
/>
</div>
</div>
{/* 第三行:交易币种 */}
<div>
<div className="flex items-center justify-between mb-2">
<label className="text-sm text-[#EAECEF]">
(使)
</label>
<button
type="button"
onClick={() => setShowCoinSelector(!showCoinSelector)}
className="px-3 py-1 text-xs bg-[#F0B90B] text-black rounded hover:bg-[#E1A706] transition-colors"
>
{showCoinSelector ? '收起选择' : '快速选择'}
</button>
</div>
<input
type="text"
value={formData.trading_symbols}
onChange={(e) =>
handleInputChange('trading_symbols', e.target.value)
}
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
placeholder="例如: BTCUSDT,ETHUSDT,ADAUSDT"
/>
{/* 币种选择器 */}
{showCoinSelector && (
<div className="mt-3 p-3 bg-[#0B0E11] border border-[#2B3139] rounded">
<div className="text-xs text-[#848E9C] mb-2">
</div>
<div className="flex flex-wrap gap-2">
{availableCoins.map((coin) => (
<button
key={coin}
type="button"
onClick={() => handleCoinToggle(coin)}
className={`px-2 py-1 text-xs rounded transition-colors ${
selectedCoins.includes(coin)
? 'bg-[#F0B90B] text-black'
: 'bg-[#1E2329] text-[#848E9C] border border-[#2B3139] hover:border-[#F0B90B]'
}`}
>
{coin.replace('USDT', '')}
</button>
))}
</div>
</div>
)}
</div>
</div>
</div>
{/* Signal Sources */}
<div className="bg-[#0B0E11] border border-[#2B3139] rounded-lg p-5">
<h3 className="text-lg font-semibold text-[#EAECEF] mb-5 flex items-center gap-2">
📡
</h3>
<div className="grid grid-cols-2 gap-4">
<div className="flex items-center gap-3">
<input
type="checkbox"
checked={formData.use_coin_pool}
onChange={(e) =>
handleInputChange('use_coin_pool', e.target.checked)
}
className="w-4 h-4"
/>
<label className="text-sm text-[#EAECEF]">
使 Coin Pool
</label>
</div>
<div className="flex items-center gap-3">
<input
type="checkbox"
checked={formData.use_oi_top}
onChange={(e) =>
handleInputChange('use_oi_top', e.target.checked)
}
className="w-4 h-4"
/>
<label className="text-sm text-[#EAECEF]">
使 OI Top
</label>
</div>
</div>
</div>
{/* Trading Prompt */}
<div className="bg-[#0B0E11] border border-[#2B3139] rounded-lg p-5">
<h3 className="text-lg font-semibold text-[#EAECEF] mb-5 flex items-center gap-2">
💬
</h3>
<div className="space-y-4">
{/* 系统提示词模板选择 */}
<div>
<label className="text-sm text-[#EAECEF] block mb-2">
</label>
<select
value={formData.system_prompt_template}
onChange={(e) =>
handleInputChange('system_prompt_template', e.target.value)
}
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
>
{promptTemplates.map((template) => (
<option key={template.name} value={template.name}>
{template.name === 'default'
? 'Default (默认稳健)'
: template.name === 'aggressive'
? 'Aggressive (激进)'
: template.name.charAt(0).toUpperCase() +
template.name.slice(1)}
</option>
))}
</select>
<p className="text-xs text-[#848E9C] mt-1">
</p>
</div>
<div className="flex items-center gap-3">
<input
type="checkbox"
checked={formData.override_base_prompt}
onChange={(e) =>
handleInputChange('override_base_prompt', e.target.checked)
}
className="w-4 h-4"
/>
<label className="text-sm text-[#EAECEF]"></label>
<span className="text-xs text-[#F0B90B] inline-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>{' '}
</span>
</div>
<div>
<label className="text-sm text-[#EAECEF] block mb-2">
{formData.override_base_prompt
? '自定义提示词'
: '附加提示词'}
</label>
<textarea
value={formData.custom_prompt}
onChange={(e) =>
handleInputChange('custom_prompt', e.target.value)
}
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none h-24 resize-none"
placeholder={
formData.override_base_prompt
? '输入完整的交易策略提示词...'
: '输入额外的交易策略提示...'
}
/>
</div>
</div>
</div>
</div>
{/* Footer */}
<div className="flex justify-end gap-3 p-6 border-t border-[#2B3139] bg-gradient-to-r from-[#1E2329] to-[#252B35]">
<button
onClick={onClose}
className="px-6 py-3 bg-[#2B3139] text-[#EAECEF] rounded-lg hover:bg-[#404750] transition-all duration-200 border border-[#404750]"
>
</button>
{onSave && (
<button
onClick={handleSave}
disabled={
isSaving ||
!formData.trader_name ||
!formData.ai_model ||
!formData.exchange_id
}
className="px-8 py-3 bg-gradient-to-r from-[#F0B90B] to-[#E1A706] text-black rounded-lg hover:from-[#E1A706] hover:to-[#D4951E] transition-all duration-200 disabled:bg-[#848E9C] disabled:cursor-not-allowed font-medium shadow-lg"
>
{isSaving ? '保存中...' : isEditMode ? '保存修改' : '创建交易员'}
</button>
)}
</div>
</div>
</div>
)
}