diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 7b6c4cf..2e3a27e 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/templates/core/mobile/base_mobile.html b/core/templates/core/mobile/base_mobile.html new file mode 100644 index 0000000..f5ab30c --- /dev/null +++ b/core/templates/core/mobile/base_mobile.html @@ -0,0 +1,49 @@ + + + + + + {{ site_settings.site_name|default:"BitCrypto" }} - 移动版 + + + + {% block extra_css %}{% endblock %} + + +
+ {% block content %}{% endblock %} +
+ +
+ + 首页 + + + 行情 + + + 交易 + + + 我的 + +
+ + + {% block scripts %}{% endblock %} + + \ No newline at end of file diff --git a/core/templates/core/mobile/deposit.html b/core/templates/core/mobile/deposit.html new file mode 100644 index 0000000..4eae5ec --- /dev/null +++ b/core/templates/core/mobile/deposit.html @@ -0,0 +1,44 @@ +{% extends 'core/mobile/base_mobile.html' %} +{% block content %} +
+
+ 返回 +

充值 USDT

+
+ +
+
选择币种
+
+ USDT + TRC20 +
+ +
+ QR +
+ +
充值地址
+
+ + +
+ +
+ 请务必确认网络为 TRC20,否则资产将无法找回。 +
+
+ +
+ {% csrf_token %} +
+ + +
+
+ + +
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/mobile/index.html b/core/templates/core/mobile/index.html new file mode 100644 index 0000000..5ebfb05 --- /dev/null +++ b/core/templates/core/mobile/index.html @@ -0,0 +1,88 @@ +{% extends 'core/mobile/base_mobile.html' %} +{% load static %} + +{% block content %} +
+ Logo +

全球领先的数字资产交易平台

+

安全、稳定、快捷的加密货币交易体验

+ {% if not user.is_authenticated %} +
+ 立即注册 + 登录已有账户 +
+ {% else %} +
+
+
账户总资产 (USDT)
+
{{ account.balance|add:account.frozen_balance }}
+
+
+ {% endif %} +
+ +
+
+ + +
充币
+
+
+
+ + +
提币
+
+
+
+ + +
认证
+
+
+
+ +
+
+ 热门行情 + 更多 +
+
+ +
+
+ + +{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/mobile/login.html b/core/templates/core/mobile/login.html new file mode 100644 index 0000000..1b5e90c --- /dev/null +++ b/core/templates/core/mobile/login.html @@ -0,0 +1,26 @@ +{% extends 'core/mobile/base_mobile.html' %} +{% block content %} +
+
+

欢迎回来

+

登录您的 {{ site_settings.site_name|default:"BitCrypto" }} 账户

+
+ +
+ {% csrf_token %} +
+ + +
+
+ + +
+ +
+ 还没有账户? + 立即注册 +
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/mobile/market_center.html b/core/templates/core/mobile/market_center.html new file mode 100644 index 0000000..8a8d0c7 --- /dev/null +++ b/core/templates/core/mobile/market_center.html @@ -0,0 +1,63 @@ +{% extends 'core/mobile/base_mobile.html' %} +{% block content %} +
+
+ + +
+
+ +
+
+ 名称 + 最新价 + 24h 涨跌 +
+
+ +
+
+ + +{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/mobile/profile.html b/core/templates/core/mobile/profile.html new file mode 100644 index 0000000..351a9e1 --- /dev/null +++ b/core/templates/core/mobile/profile.html @@ -0,0 +1,83 @@ +{% extends 'core/mobile/base_mobile.html' %} +{% block content %} +
+
+ {{ user.username|first|upper }} +
+
+
{{ user.username }}
+
UID: {{ account.uid }} | {{ account.get_account_type_display }}
+
+
+ + {{ account.get_kyc_status_display }} + +
+
+ +
+
总资产估值 (USDT)
+
{{ account.balance|add:account.frozen_balance }}
+ +
+
+
+
可用
+
{{ account.balance }}
+
+
+
+
+
冻结
+
{{ account.frozen_balance }}
+
+
+
+
+ +
+
+ + 充币 + + + + 提币 + + + + 实名认证 + + +
+
+ +
+
资产明细
+ + + + + + + + + + {% for asset in account.assets.all %} + + + + + + {% endfor %} + +
币种可用冻结
{{ asset.currency }}{{ asset.balance }}{{ asset.frozen }}
+
+ +
+
+ {% csrf_token %} + +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/mobile/register.html b/core/templates/core/mobile/register.html new file mode 100644 index 0000000..58af614 --- /dev/null +++ b/core/templates/core/mobile/register.html @@ -0,0 +1,45 @@ +{% extends 'core/mobile/base_mobile.html' %} +{% block content %} +
+
+

注册账户

+

加入 {{ site_settings.site_name|default:"BitCrypto" }},开启您的交易之旅

+
+ + {% if messages %} +
+ {% for message in messages %} +
{{ message }}
+ {% endfor %} +
+ {% endif %} + +
+ {% csrf_token %} +
+ + +
+
+ + +
+
+ + +
+
+ +
+ + {{ captcha_text }} +
+
+ +
+ 已有账户? + 立即登录 +
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/mobile/trade.html b/core/templates/core/mobile/trade.html new file mode 100644 index 0000000..c6e4554 --- /dev/null +++ b/core/templates/core/mobile/trade.html @@ -0,0 +1,376 @@ +{% extends 'core/mobile/base_mobile.html' %} +{% load static %} + +{% block content %} +
+ +
+ 现货 + 合约 +
+
+ +
+ +
+
+
+ 价格数量 +
+
+
--
+
+
+
+ + +
+
+ {% if trade_type == 'SPOT' %} +
+ + + + +
+ + {% else %} +
+ + +
+ {% endif %} + +
+
+ + +
+
+ +
+
+ + + {{ base_symbol }} +
+
+ +
+ +
+ 0%25%50%75%100% +
+
+ +
+ 可用: + -- +
+ + {% if trade_type == 'CONTRACT' %} +
+
+
+
+ {% else %} + + {% endif %} +
+
+
+ + +
+
+
+ + +
+
+ 当前持仓/挂单 + 刷新 +
+
+ {% if trade_type == 'CONTRACT' %} + {% for p in account.positions.all %} + {% if p.is_active %} +
+
+ {{ p.symbol }} {{ p.leverage }}x {% if p.side == 'LONG' %}多{% else %}空{% endif %} + -- +
+
+ 手数: {{ p.lots }} + 开仓价: {{ p.entry_price|floatformat:2 }} +
+
+ 保证金: {{ p.margin|floatformat:2 }} + 当前: -- +
+ +
+ {% endif %} + {% endfor %} + {% endif %} + {% for o in account.orders.all %} + {% if o.status == 'PENDING' %} +
+
+ {{ o.symbol }} ({{ o.get_trade_type_display }}) {{ o.get_side_display }} + 委托中 +
+
+ 价格: {{ o.price|default:"市价" }} + 数量: {{ o.amount }} +
+ +
+ {% endif %} + {% endfor %} +
+
+ + +{% endblock %} + +{% block scripts %} + + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/mobile/verify.html b/core/templates/core/mobile/verify.html new file mode 100644 index 0000000..fc8850c --- /dev/null +++ b/core/templates/core/mobile/verify.html @@ -0,0 +1,37 @@ +{% extends 'core/mobile/base_mobile.html' %} +{% block content %} +
+
+ 返回 +

实名认证

+
+ +
+ {% if account.kyc_status == 'UNVERIFIED' %} + +
未认证
+

完成实名认证以提高账户额度和安全性

+
+ {% csrf_token %} +
+ + +
+
+ + +
+ +
+ {% elif account.kyc_status == 'PENDING' %} + +
审核中
+

您的认证申请正在处理中,请耐心等待。

+ {% elif account.kyc_status == 'VERIFIED' %} + +
已认证
+

您的账户已通过实名认证。

+ {% endif %} +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/mobile/withdraw.html b/core/templates/core/mobile/withdraw.html new file mode 100644 index 0000000..1d2278a --- /dev/null +++ b/core/templates/core/mobile/withdraw.html @@ -0,0 +1,40 @@ +{% extends 'core/mobile/base_mobile.html' %} +{% block content %} +
+
+ 返回 +

提现 USDT

+
+ +
+
+ 可用余额 + {{ account.balance }} USDT +
+
+ +
+ {% csrf_token %} +
+ + +
+
+ + +
+
+ +
+ + +
+
手续费: 1 USDT
+
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/trade.html b/core/templates/core/trade.html index 390bc34..4fa0021 100644 --- a/core/templates/core/trade.html +++ b/core/templates/core/trade.html @@ -4,7 +4,7 @@ {% block content %}
- +
@@ -23,7 +23,6 @@ -
@@ -40,7 +39,6 @@ {{ symbol }}
@@ -82,7 +80,7 @@
价格 - + USDT
@@ -117,7 +115,7 @@
价格 - + USDT
@@ -164,7 +162,7 @@
价格 - + USDT
@@ -261,8 +259,8 @@
- -
+ +
价格(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