价格(USDT)
@@ -316,9 +314,9 @@
async function getMarkets() {
try {
- const r = await fetch('https://api.binance.com/api/v3/ticker/24hr');
+ const r = await fetch('/api/market_data/');
const data = await r.json();
- allCoins = data.filter(c => c.symbol.endsWith('USDT')).slice(0, 100);
+ allCoins = data;
renderCoins();
} catch(e) {}
}
@@ -326,21 +324,22 @@
function renderCoins(filter = '') {
const list = document.getElementById('left-coin-list');
const mobileList = document.getElementById('mobile-coin-list');
- list.innerHTML = ''; mobileList.innerHTML = '';
+ if (list) list.innerHTML = '';
+ if (mobileList) mobileList.innerHTML = '';
allCoins.forEach(c => {
if (filter && !c.symbol.toLowerCase().includes(filter.toLowerCase())) return;
const base = c.symbol.replace('USDT', '');
- const chg = parseFloat(c.priceChangePercent);
+ const chg = parseFloat(c.change);
const iconUrl = `https://static.okx.com/cdn/oksupport/asset/currency/icon/${base.toLowerCase()}.png`;
const row = `
${base} |
- ${parseFloat(c.lastPrice).toLocaleString()} |
+ ${parseFloat(c.price).toLocaleString()} |
${chg>=0?'+':''}${chg.toFixed(2)}% |
`;
- list.innerHTML += row;
- mobileList.innerHTML += `
${base}${parseFloat(c.lastPrice).toLocaleString()}`;
+ if (list) list.innerHTML += row;
+ if (mobileList) mobileList.innerHTML += `
${base}${parseFloat(c.price).toLocaleString()}`;
});
}
@@ -349,42 +348,43 @@
}
function toggleMode(side, mode) {
+ let pInput;
if (side === 'buy') {
- const pInput = document.getElementById('buy-price');
+ pInput = document.getElementById('buy-price');
const label = document.getElementById('buy-label');
const unit = document.getElementById('buy-unit');
if (mode === 'market') {
- pInput.value = '市价价格';
+ pInput.value = currentPrice || '市价';
pInput.disabled = true;
pInput.style.color = '#f0b90b';
label.textContent = '成交额';
unit.textContent = 'USDT';
} else {
- pInput.value = currentPrice || '';
+ pInput.value = '';
pInput.disabled = false;
pInput.style.color = 'white';
label.textContent = '数量';
unit.textContent = baseAsset;
}
} else if (side === 'sell') {
- const pInput = document.getElementById('sell-price');
+ pInput = document.getElementById('sell-price');
if (mode === 'market') {
- pInput.value = '市价价格';
+ pInput.value = currentPrice || '市价';
pInput.disabled = true;
pInput.style.color = '#f0b90b';
} else {
- pInput.value = currentPrice || '';
+ pInput.value = '';
pInput.disabled = false;
pInput.style.color = 'white';
}
} else if (side === 'contract') {
- const pInput = document.getElementById('c-price');
+ pInput = document.getElementById('c-price');
if (mode === 'market') {
- pInput.value = '市价价格';
+ pInput.value = currentPrice || '市价';
pInput.disabled = true;
pInput.style.color = '#f0b90b';
} else {
- pInput.value = currentPrice || '';
+ pInput.value = '';
pInput.disabled = false;
pInput.style.color = 'white';
}
@@ -398,15 +398,18 @@
if (mode === 'market') {
document.getElementById('buy-amount').value = (balance * (val / 100)).toFixed(2);
} else {
- const price = parseFloat(document.getElementById('buy-price').value);
+ const price = parseFloat(document.getElementById('buy-price').value) || currentPrice;
if (price > 0) document.getElementById('buy-amount').value = (balance * (val / 100) / price).toFixed(4);
+ else document.getElementById('buy-amount').value = 0;
}
} else if (side === 'sell') {
document.getElementById('sell-amount').value = (assetBalance * (val / 100)).toFixed(4);
} else if (side === 'contract') {
- const margin = (balance * (val / 100));
+ const maxLots = (balance * leverage) / 100;
+ const lots = Math.floor(maxLots * (val / 100));
+ document.getElementById('c-amount').value = lots;
+ const margin = (100 * lots) / leverage;
document.getElementById('c-margin-display').textContent = margin.toFixed(2);
- document.getElementById('c-amount').value = Math.floor((margin * leverage) / 100);
}
}
@@ -418,18 +421,19 @@
if (mode === 'market') {
percent = (amount / balance) * 100;
} else {
- const price = parseFloat(document.getElementById('buy-price').value) || 0;
- percent = (amount * price / balance) * 100;
+ const price = parseFloat(document.getElementById('buy-price').value) || currentPrice;
+ if (price > 0) percent = (amount * price / balance) * 100;
}
- document.getElementById('buy-slider').value = Math.min(100, percent);
+ document.getElementById('buy-slider').value = Math.min(100, percent || 0);
} else if (side === 'sell') {
const amount = parseFloat(document.getElementById('sell-amount').value) || 0;
- document.getElementById('sell-slider').value = Math.min(100, (amount / assetBalance) * 100);
+ if (assetBalance > 0) document.getElementById('sell-slider').value = Math.min(100, (amount / assetBalance) * 100);
} else if (side === 'contract') {
const lots = parseFloat(document.getElementById('c-amount').value) || 0;
+ const maxLots = (balance * leverage) / 100;
+ if (maxLots > 0) document.getElementById('c-slider').value = Math.min(100, (lots / maxLots) * 100);
const margin = (100 * lots) / leverage;
document.getElementById('c-margin-display').textContent = margin.toFixed(2);
- document.getElementById('c-slider').value = Math.min(100, (margin / balance) * 100);
}
}
@@ -442,31 +446,35 @@
async function tick() {
try {
- const r = await fetch('https://api.binance.com/api/v3/ticker/24hr?symbol=' + symbol);
- const d = await r.json();
- currentPrice = parseFloat(d.lastPrice);
+ const r = await fetch('/api/market_data/');
+ const data = await r.json();
+ const d = data.find(c => c.symbol === symbol);
+ if (!d) return;
+
+ currentPrice = parseFloat(d.price);
document.getElementById('header-price').textContent = currentPrice.toLocaleString();
document.getElementById('header-price-usd').textContent = currentPrice.toLocaleString();
- document.getElementById('current-price-book').textContent = currentPrice.toLocaleString();
+ const cpBook = document.getElementById('current-price-book');
+ if (cpBook) cpBook.textContent = currentPrice.toLocaleString();
- const chg = parseFloat(d.priceChangePercent);
+ // Update market price in box ONLY if disabled (Market Order)
+ ['buy-price', 'sell-price', 'c-price'].forEach(id => {
+ const el = document.getElementById(id);
+ if (el && el.disabled) el.value = currentPrice;
+ });
+
+ const chg = parseFloat(d.change);
document.getElementById('header-change').textContent = (chg>=0?'+':'') + chg.toFixed(2) + '%';
document.getElementById('header-change').className = 'fw-bold ' + (chg>=0?'text-success':'text-danger');
- document.getElementById('header-volume').textContent = (parseFloat(d.quoteVolume)/1000000).toFixed(2) + 'M';
const askD = document.getElementById('asks'); const bidD = document.getElementById('bids');
- askD.innerHTML = ''; bidD.innerHTML = '';
- for (let i = 0; i < 10; i++) {
- askD.innerHTML = `
${(currentPrice+(10-i)*0.1).toFixed(2)}${(Math.random()*1.2).toFixed(4)} ` + askD.innerHTML;
- bidD.innerHTML += `
${(currentPrice-(i+1)*0.1).toFixed(2)}${(Math.random()*1.2).toFixed(4)} `;
- }
-
- if (tradeType === 'SPOT') {
- if (!document.getElementById('buy-price').value && !document.getElementById('buy-price').disabled) document.getElementById('buy-price').value = currentPrice;
- if (!document.getElementById('sell-price').value && !document.getElementById('sell-price').disabled) document.getElementById('sell-price').value = currentPrice;
- } else {
- if (!document.getElementById('c-price').value && !document.getElementById('c-price').disabled) document.getElementById('c-price').value = currentPrice;
+ if (askD && bidD) {
+ askD.innerHTML = ''; bidD.innerHTML = '';
+ for (let i = 0; i < 10; i++) {
+ askD.innerHTML = `
${(currentPrice+(10-i)*0.1).toFixed(2)}${(Math.random()*1.2).toFixed(4)} ` + askD.innerHTML;
+ bidD.innerHTML += `
${(currentPrice-(i+1)*0.1).toFixed(2)}${(Math.random()*1.2).toFixed(4)} `;
+ }
}
document.querySelectorAll('.current-p-val').forEach(el => { el.textContent = currentPrice.toLocaleString(); });
@@ -500,13 +508,22 @@
amount = document.getElementById('c-amount').value;
}
+ if (mode === 'LIMIT' && !price) {
+ alert('请输入委托价格');
+ return;
+ }
+ if (!amount || amount <= 0) {
+ alert('请输入有效数量/手数');
+ return;
+ }
+
try {
const r = await fetch('{% url "core:submit_order" %}', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': '{{ csrf_token }}' },
body: JSON.stringify({
symbol: symbol, side: side, trade_type: tradeType, order_type: mode,
- price: price === '市价价格' ? null : price, amount: amount, leverage: leverage
+ price: mode === 'MARKET' ? null : price, amount: amount, leverage: leverage
})
});
const res = await r.json();
diff --git a/core/views.py b/core/views.py
index bb6bb3d..d77df27 100644
--- a/core/views.py
+++ b/core/views.py
@@ -12,7 +12,14 @@ import json
from django.views.decorators.csrf import csrf_exempt
from django.db import transaction
+def is_mobile(request):
+ user_agent = request.META.get('HTTP_USER_AGENT', '').lower()
+ mobile_indicators = ['iphone', 'android', 'phone', 'mobile']
+ return any(indicator in user_agent for indicator in mobile_indicators)
+
def index(request):
+ if is_mobile(request):
+ return render(request, 'core/mobile/index.html')
return render(request, 'core/index.html')
def trade(request, trade_type='spot'):
@@ -39,11 +46,14 @@ def trade(request, trade_type='spot'):
'assets': assets,
'base_asset_balance': base_asset_balance,
}
- return render(request, 'core/trade.html', context)
+
+ template = 'core/mobile/trade.html' if is_mobile(request) else 'core/trade.html'
+ return render(request, template, context)
def market_center(request):
cryptos = Cryptocurrency.objects.filter(is_active=True)
- return render(request, 'core/market_center.html', {'cryptos': cryptos})
+ template = 'core/mobile/market_center.html' if is_mobile(request) else 'core/market_center.html'
+ return render(request, template, {'cryptos': cryptos})
def placeholder_view(request, title):
settings = SiteSettings.objects.first()
@@ -85,7 +95,8 @@ def profile(request):
'recent_orders': recent_orders,
'positions': positions,
}
- return render(request, 'core/profile.html', context)
+ template = 'core/mobile/profile.html' if is_mobile(request) else 'core/profile.html'
+ return render(request, template, context)
@login_required
def deposit(request):
@@ -102,7 +113,8 @@ def deposit(request):
tx_hash=tx_id
)
return redirect('core:profile')
- return render(request, 'core/deposit.html')
+ template = 'core/mobile/deposit.html' if is_mobile(request) else 'core/deposit.html'
+ return render(request, template)
@login_required
def withdraw(request):
@@ -123,7 +135,8 @@ def withdraw(request):
tx_hash=f"wd_{random.randint(1000, 9999)}"
)
return redirect('core:profile')
- return render(request, 'core/withdraw.html', {'account': account})
+ template = 'core/mobile/withdraw.html' if is_mobile(request) else 'core/withdraw.html'
+ return render(request, template, {'account': account})
@login_required
def verify(request):
@@ -132,7 +145,8 @@ def verify(request):
account.kyc_status = 'pending'
account.save()
return redirect('core:profile')
- return render(request, 'core/verify.html', {'account': account})
+ template = 'core/mobile/verify.html' if is_mobile(request) else 'core/verify.html'
+ return render(request, template, {'account': account})
def register_view(request):
if request.method == 'POST':
@@ -157,7 +171,8 @@ def register_view(request):
captcha_text, captcha_result = generate_captcha()
request.session['captcha_result'] = captcha_result
- return render(request, 'core/register.html', {
+ template = 'core/mobile/register.html' if is_mobile(request) else 'core/register.html'
+ return render(request, template, {
'captcha_text': captcha_text
})
@@ -179,7 +194,8 @@ def login_view(request):
return redirect('core:index')
else:
form = AuthenticationForm()
- return render(request, 'core/login.html', {'form': form})
+ template = 'core/mobile/login.html' if is_mobile(request) else 'core/login.html'
+ return render(request, template, {'form': form})
@login_required
@csrf_exempt
@@ -200,10 +216,14 @@ def submit_order(request):
account = request.user.account
base_symbol = symbol.replace('USDT', '')
- # Get current market price for validations and market orders
+ # Get price (consider manual override)
crypto = Cryptocurrency.objects.filter(symbol=base_symbol).first()
- current_price = crypto.current_price if (crypto and crypto.current_price > 0) else decimal.Decimal('48000')
+ settings = SiteSettings.objects.first()
+ current_price = crypto.current_price if (crypto and crypto.current_price > 0) else decimal.Decimal('48000')
+ if settings and settings.is_pinning_active and crypto and crypto.manual_price:
+ current_price = crypto.manual_price
+
with transaction.atomic():
if trade_type == 'SPOT':
if order_type == 'MARKET':
@@ -325,17 +345,36 @@ def close_position(request, position_id):
base_symbol = position.symbol.replace('USDT', '')
crypto = Cryptocurrency.objects.filter(symbol=base_symbol).first()
+ settings = SiteSettings.objects.first()
current_price = crypto.current_price if (crypto and crypto.current_price > 0) else decimal.Decimal('48000')
+ if settings and settings.is_pinning_active and crypto and crypto.manual_price:
+ current_price = crypto.manual_price
+ account = request.user.account
face_value = decimal.Decimal('100')
+
+ # Win/Loss Control Logic
+ # 100 = Must Win, -100 = Must Loss
+ forced_upl = None
+ if account.win_loss_control != 0:
+ # Force a small profit or loss
+ total_value = position.lots * face_value
+ if account.win_loss_control > 0: # Force Profit
+ forced_upl = total_value * decimal.Decimal('0.05') # 5% profit
+ else: # Force Loss
+ forced_upl = -total_value * decimal.Decimal('0.05') # 5% loss
+
# Calculate Unrealized P&L
if position.side == 'LONG':
upl = (current_price - position.entry_price) / position.entry_price * (position.lots * face_value)
else:
upl = (position.entry_price - current_price) / position.entry_price * (position.lots * face_value)
+ # If control is active, override upl
+ if forced_upl is not None:
+ upl = forced_upl
+
with transaction.atomic():
- account = request.user.account
# Settlement: Margin + P&L - Fee (0.05%)
fee = (position.lots * face_value) * decimal.Decimal('0.0005')
account.balance += (position.margin + upl - fee)
@@ -353,12 +392,19 @@ def close_position(request, position_id):
return JsonResponse({'status': 'success'})
def market_data(request):
+ settings = SiteSettings.objects.first()
cryptos = Cryptocurrency.objects.filter(is_active=True)
data = []
for c in cryptos:
+ price = float(c.current_price)
+ if settings and settings.is_pinning_active and c.manual_price:
+ price = float(c.manual_price)
+
+ symbol_display = c.symbol if 'USDT' in c.symbol else f"{c.symbol}USDT"
+
data.append({
- 'symbol': c.symbol,
- 'price': float(c.current_price),
+ 'symbol': symbol_display,
+ 'price': price,
'change': float(c.change_24h)
})
return JsonResponse(data, safe=False)
\ No newline at end of file