diff --git a/core/__pycache__/context_processors.cpython-311.pyc b/core/__pycache__/context_processors.cpython-311.pyc
index 000b231..bd209b1 100644
Binary files a/core/__pycache__/context_processors.cpython-311.pyc and b/core/__pycache__/context_processors.cpython-311.pyc differ
diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc
index d3e41d7..782412c 100644
Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ
diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc
index af3b707..fa55f1d 100644
Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ
diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc
index 84d71d7..7b6c4cf 100644
Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ
diff --git a/core/context_processors.py b/core/context_processors.py
index c0cf4a5..b76295e 100644
--- a/core/context_processors.py
+++ b/core/context_processors.py
@@ -1,6 +1,6 @@
from .models import SiteSettings
-def site_settings(request):
+def project_context(request):
settings = SiteSettings.objects.first()
if not settings:
settings = SiteSettings.objects.create(site_name="BitCrypto")
@@ -9,3 +9,6 @@ def site_settings(request):
'project_name': settings.site_name,
'project_description': "全球领先的数字资产交易平台"
}
+
+# Alias for compatibility if needed
+site_settings = project_context
\ No newline at end of file
diff --git a/core/migrations/0006_remove_order_close_price_remove_order_entry_price_and_more.py b/core/migrations/0006_remove_order_close_price_remove_order_entry_price_and_more.py
new file mode 100644
index 0000000..e32ffa9
--- /dev/null
+++ b/core/migrations/0006_remove_order_close_price_remove_order_entry_price_and_more.py
@@ -0,0 +1,69 @@
+# Generated by Django 5.2.7 on 2026-02-06 11:38
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0005_sitesettings'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='order',
+ name='close_price',
+ ),
+ migrations.RemoveField(
+ model_name='order',
+ name='entry_price',
+ ),
+ migrations.RemoveField(
+ model_name='order',
+ name='filled_amount',
+ ),
+ migrations.RemoveField(
+ model_name='order',
+ name='profit_loss',
+ ),
+ migrations.AddField(
+ model_name='account',
+ name='frozen_balance',
+ field=models.DecimalField(decimal_places=8, default=0, max_digits=30, verbose_name='冻结余额 (USDT)'),
+ ),
+ migrations.AddField(
+ model_name='order',
+ name='total_usdt',
+ field=models.DecimalField(blank=True, decimal_places=8, max_digits=30, null=True, verbose_name='成交额 (USDT)'),
+ ),
+ migrations.AlterField(
+ model_name='order',
+ name='amount',
+ field=models.DecimalField(decimal_places=8, max_digits=30, verbose_name='数量/手数'),
+ ),
+ migrations.AlterField(
+ model_name='order',
+ name='status',
+ field=models.CharField(choices=[('PENDING', '等待成交'), ('PARTIALLY_FILLED', '部分成交'), ('FILLED', '已成交'), ('CANCELED', '已撤销')], default='PENDING', max_length=20, verbose_name='状态'),
+ ),
+ migrations.CreateModel(
+ name='Position',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('symbol', models.CharField(max_length=20, verbose_name='交易对')),
+ ('side', models.CharField(choices=[('LONG', '做多'), ('SHORT', '做空')], max_length=10, verbose_name='方向')),
+ ('leverage', models.IntegerField(default=20, verbose_name='杠杆')),
+ ('entry_price', models.DecimalField(decimal_places=8, max_digits=30, verbose_name='开仓均价')),
+ ('lots', models.DecimalField(decimal_places=8, max_digits=30, verbose_name='手数')),
+ ('margin', models.DecimalField(decimal_places=8, max_digits=30, verbose_name='占用保证金')),
+ ('is_active', models.BooleanField(default=True, verbose_name='是否持仓')),
+ ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='时间')),
+ ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='positions', to='core.account')),
+ ],
+ options={
+ 'verbose_name': '持仓',
+ 'verbose_name_plural': '持仓管理',
+ },
+ ),
+ ]
diff --git a/core/migrations/__pycache__/0006_remove_order_close_price_remove_order_entry_price_and_more.cpython-311.pyc b/core/migrations/__pycache__/0006_remove_order_close_price_remove_order_entry_price_and_more.cpython-311.pyc
new file mode 100644
index 0000000..b9f16c6
Binary files /dev/null and b/core/migrations/__pycache__/0006_remove_order_close_price_remove_order_entry_price_and_more.cpython-311.pyc differ
diff --git a/core/models.py b/core/models.py
index a9c352c..759b4b8 100644
--- a/core/models.py
+++ b/core/models.py
@@ -57,6 +57,8 @@ class Account(models.Model):
account_type = models.CharField(max_length=20, choices=ACCOUNT_TYPES, default='SIMULATED', verbose_name=_("账户类型"))
balance = models.DecimalField(max_digits=30, decimal_places=8, default=0, verbose_name=_("可用余额 (USDT)"))
+ frozen_balance = models.DecimalField(max_digits=30, decimal_places=8, default=0, verbose_name=_("冻结余额 (USDT)"))
+
credit_score = models.IntegerField(default=80, verbose_name=_("信用分"))
kyc_status = models.CharField(max_length=20, choices=KYC_STATUS, default='UNVERIFIED', verbose_name=_("实名认证状态"))
@@ -95,11 +97,10 @@ class Order(models.Model):
TYPE_CHOICES = (('LIMIT', _('限价')), ('MARKET', _('市价')))
TRADE_TYPE_CHOICES = (('SPOT', _('现货')), ('CONTRACT', _('合约')))
STATUS_CHOICES = (
- ('LIVE', _('进行中')),
+ ('PENDING', _('等待成交')),
('PARTIALLY_FILLED', _('部分成交')),
('FILLED', _('已成交')),
('CANCELED', _('已撤销')),
- ('CLOSED', _('已平仓')),
)
account = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='orders')
@@ -109,15 +110,11 @@ class Order(models.Model):
order_type = models.CharField(max_length=10, choices=TYPE_CHOICES, verbose_name=_("类型"))
price = models.DecimalField(max_digits=30, decimal_places=8, null=True, blank=True, verbose_name=_("委托价格"))
- amount = models.DecimalField(max_digits=30, decimal_places=8, verbose_name=_("数量"))
- filled_amount = models.DecimalField(max_digits=30, decimal_places=8, default=0, verbose_name=_("已成交数量"))
+ amount = models.DecimalField(max_digits=30, decimal_places=8, verbose_name=_("数量/手数"))
+ total_usdt = models.DecimalField(max_digits=30, decimal_places=8, null=True, blank=True, verbose_name=_("成交额 (USDT)"))
leverage = models.IntegerField(default=1, verbose_name=_("杠杆倍数"))
- entry_price = models.DecimalField(max_digits=30, decimal_places=8, null=True, blank=True, verbose_name=_("入场价格"))
- close_price = models.DecimalField(max_digits=30, decimal_places=8, null=True, blank=True, verbose_name=_("平仓价格"))
- profit_loss = models.DecimalField(max_digits=30, decimal_places=8, default=0, verbose_name=_("盈亏"))
-
- status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='LIVE', verbose_name=_("状态"))
+ status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING', verbose_name=_("状态"))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("创建时间"))
class Meta:
@@ -127,6 +124,25 @@ class Order(models.Model):
def __str__(self):
return f"{self.trade_type} {self.side} {self.amount} {self.symbol}"
+class Position(models.Model):
+ SIDE_CHOICES = (('LONG', _('做多')), ('SHORT', _('做空')))
+ account = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='positions')
+ symbol = models.CharField(max_length=20, verbose_name=_("交易对"))
+ side = models.CharField(max_length=10, choices=SIDE_CHOICES, verbose_name=_("方向"))
+ leverage = models.IntegerField(default=20, verbose_name=_("杠杆"))
+ entry_price = models.DecimalField(max_digits=30, decimal_places=8, verbose_name=_("开仓均价"))
+ lots = models.DecimalField(max_digits=30, decimal_places=8, verbose_name=_("手数"))
+ margin = models.DecimalField(max_digits=30, decimal_places=8, verbose_name=_("占用保证金"))
+ is_active = models.BooleanField(default=True, verbose_name=_("是否持仓"))
+ created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("时间"))
+
+ class Meta:
+ verbose_name = _("持仓")
+ verbose_name_plural = _("持仓管理")
+
+ def __str__(self):
+ return f"{self.account.uid} {self.symbol} {self.side} {self.lots}张"
+
class Transaction(models.Model):
TX_TYPE = (('deposit', _('充值')), ('withdraw', _('提现')))
TX_STATUS = (('pending', _('待处理')), ('completed', _('成功')), ('failed', _('失败')))
@@ -141,4 +157,4 @@ class Transaction(models.Model):
class Meta:
verbose_name = _("充提记录")
- verbose_name_plural = _("充提管理")
\ No newline at end of file
+ verbose_name_plural = _("充提管理")
diff --git a/core/templates/base.html b/core/templates/base.html
index 43afb9b..b55a06f 100644
--- a/core/templates/base.html
+++ b/core/templates/base.html
@@ -100,10 +100,10 @@
{% if user.is_authenticated %}
@@ -113,15 +113,15 @@
{{ user.username }}
{% else %}
-
登录
-
立即注册
+
登录
+
立即注册
{% endif %}
@@ -149,32 +149,32 @@
diff --git a/core/templates/core/index.html b/core/templates/core/index.html
index 3cb6fda..26b3984 100644
--- a/core/templates/core/index.html
+++ b/core/templates/core/index.html
@@ -18,8 +18,8 @@
开启您的
加密货币之旅
在全球最受信任的交易平台买卖和存储加密货币。BitCrypto 为您提供安全、稳定、高效的服务。
@@ -32,7 +32,7 @@
领先的衍生品交易平台
最高200倍杠杆,支持多种永续合约,毫秒级撮合引擎。多空双向交易,灵活捕捉市场机会。
-
进入合约交易
+
进入合约交易
@@ -98,7 +98,7 @@
热门行情
实时获取全球顶级加密货币价格走势
- 查看更多市场
+ 查看更多市场
diff --git a/core/templates/core/login.html b/core/templates/core/login.html
index 8d6ce2c..7798100 100644
--- a/core/templates/core/login.html
+++ b/core/templates/core/login.html
@@ -41,7 +41,7 @@
-
还没有账户? 立即注册
+
还没有账户? 立即注册
diff --git a/core/templates/core/market_center.html b/core/templates/core/market_center.html
new file mode 100644
index 0000000..d41f222
--- /dev/null
+++ b/core/templates/core/market_center.html
@@ -0,0 +1,107 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+ | 币种 |
+ 最新价 |
+ 24h 涨跌 |
+ 24h 最高 |
+ 24h 最低 |
+ 24h 成交额 |
+ 操作 |
+
+
+
+ {% for crypto in cryptos %}
+
+
+
+ 
+
+ {{ crypto.symbol }}
+ {{ crypto.name }}
+
+
+ |
+ -- |
+ -- |
+ -- |
+ -- |
+ -- |
+
+ 现货
+ 合约
+ |
+
+ {% endfor %}
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block scripts %}
+
+{% endblock %}
diff --git a/core/templates/core/profile.html b/core/templates/core/profile.html
index 437e3ee..f472f2a 100644
--- a/core/templates/core/profile.html
+++ b/core/templates/core/profile.html
@@ -23,8 +23,8 @@
@@ -122,7 +122,7 @@
身份认证 (KYC)
提升提现额度至 100 BTC
- 立即认证
+ 立即认证
diff --git a/core/templates/core/register.html b/core/templates/core/register.html
index 2aaa8be..6c189de 100644
--- a/core/templates/core/register.html
+++ b/core/templates/core/register.html
@@ -66,7 +66,7 @@
@@ -75,7 +75,7 @@
-
已有账户? 立即登录
+
已有账户? 立即登录
diff --git a/core/templates/core/trade.html b/core/templates/core/trade.html
index 912103a..390bc34 100644
--- a/core/templates/core/trade.html
+++ b/core/templates/core/trade.html
@@ -57,8 +57,8 @@
@@ -68,7 +68,7 @@
-
+
{% if trade_type == 'SPOT' %}
@@ -87,9 +87,9 @@
- 成交额
-
- USDT
+ 数量
+
+ {{ base_symbol }}
@@ -103,7 +103,7 @@
可用余额
{{ account.balance|default:"0.00" }} USDT
-
+
@@ -124,7 +124,7 @@
数量
- {{ symbol|slice:":-4" }}
+ {{ base_symbol }}
@@ -135,10 +135,10 @@
- 可用 {{ symbol|slice:":-4" }}
- 0.00
+ 可用 {{ base_symbol }}
+ {{ base_asset_balance|default:"0.00" }} {{ base_symbol }}
-
+
{% else %}
@@ -147,9 +147,9 @@
-
+
-
+
@@ -169,32 +169,96 @@
手数
-
+
张
-
+
- 保证金: 0.00 USDT
+ 所需保证金: 0.00 USDT
可用: {{ account.balance|default:"0.00" }} USDT
-
-
+
+
-
合约面值100 USDT
+
合约面值100 USDT / 张
当前杠杆20x
预计手续费0.05%
-
维持保证金0.4%
+
维持保证金率0.4%
{% endif %}
+
+
+
+
+
+
+
+
+
+
+ | 合约/现货 |
+ 方向 |
+ 数量/手数 |
+ 开仓/委托价 |
+ 当前价 |
+ 保证金 |
+ 盈亏 |
+ 操作 |
+
+
+
+ {% if trade_type == 'CONTRACT' %}
+ {% for p in account.positions.all %}
+ {% if p.is_active %}
+
+ | {{ p.symbol }} {{ p.leverage }}x |
+ {{ p.get_side_display }} |
+ {{ p.lots }} |
+ {{ p.entry_price }} |
+ -- |
+ {{ 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.amount }} |
+ {{ o.price|default:"市价" }} |
+ -- |
+ -- |
+ 等待成交 |
+ |
+
+ {% endif %}
+ {% endfor %}
+
+
+
+
+
+
@@ -202,7 +266,7 @@
价格(USDT)
- 数量({{ symbol|slice:":-4" }})
+ 数量({{ base_symbol }})
@@ -216,7 +280,7 @@
{% endblock %}
@@ -234,6 +299,9 @@
const symbol = '{{ symbol }}';
const tradeType = '{{ trade_type }}';
const balance = parseFloat("{{ account.balance|default:0 }}");
+ const baseAsset = '{{ base_symbol }}';
+ const assetBalance = parseFloat("{{ base_asset_balance|default:0 }}");
+
let currentPrice = 0;
let leverage = 20;
let allCoins = [];
@@ -250,7 +318,7 @@
try {
const r = await fetch('https://api.binance.com/api/v3/ticker/24hr');
const data = await r.json();
- allCoins = data.filter(c => c.symbol.endsWith('USDT')).slice(0, 50);
+ allCoins = data.filter(c => c.symbol.endsWith('USDT')).slice(0, 100);
renderCoins();
} catch(e) {}
}
@@ -264,14 +332,15 @@
if (filter && !c.symbol.toLowerCase().includes(filter.toLowerCase())) return;
const base = c.symbol.replace('USDT', '');
const chg = parseFloat(c.priceChangePercent);
+ const iconUrl = `https://static.okx.com/cdn/oksupport/asset/currency/icon/${base.toLowerCase()}.png`;
const row = `
- | ${base} |
- ${parseFloat(c.lastPrice).toFixed(2)} |
+ ${base} |
+ ${parseFloat(c.lastPrice).toLocaleString()} |
${chg>=0?'+':''}${chg.toFixed(2)}% |
`;
list.innerHTML += row;
- mobileList.innerHTML += `
${base}${parseFloat(c.lastPrice).toFixed(2)}`;
+ mobileList.innerHTML += `
${base}${parseFloat(c.lastPrice).toLocaleString()}`;
});
}
@@ -280,32 +349,90 @@
}
function toggleMode(side, mode) {
- const pInput = document.getElementById(side === 'contract' ? 'c-price' : side + '-price');
- if (mode === 'market') {
- pInput.value = '市价价格';
- pInput.disabled = true;
- pInput.style.color = '#f0b90b';
- } else {
- pInput.value = currentPrice || '';
- pInput.disabled = false;
- pInput.style.color = 'white';
+ if (side === 'buy') {
+ const pInput = document.getElementById('buy-price');
+ const label = document.getElementById('buy-label');
+ const unit = document.getElementById('buy-unit');
+ if (mode === 'market') {
+ pInput.value = '市价价格';
+ pInput.disabled = true;
+ pInput.style.color = '#f0b90b';
+ label.textContent = '成交额';
+ unit.textContent = 'USDT';
+ } else {
+ pInput.value = currentPrice || '';
+ pInput.disabled = false;
+ pInput.style.color = 'white';
+ label.textContent = '数量';
+ unit.textContent = baseAsset;
+ }
+ } else if (side === 'sell') {
+ const pInput = document.getElementById('sell-price');
+ if (mode === 'market') {
+ pInput.value = '市价价格';
+ pInput.disabled = true;
+ pInput.style.color = '#f0b90b';
+ } else {
+ pInput.value = currentPrice || '';
+ pInput.disabled = false;
+ pInput.style.color = 'white';
+ }
+ } else if (side === 'contract') {
+ const pInput = document.getElementById('c-price');
+ if (mode === 'market') {
+ pInput.value = '市价价格';
+ pInput.disabled = true;
+ pInput.style.color = '#f0b90b';
+ } else {
+ pInput.value = currentPrice || '';
+ pInput.disabled = false;
+ pInput.style.color = 'white';
+ }
}
}
function applySlider(side) {
const val = document.getElementById(side + '-slider').value;
if (side === 'buy') {
- document.getElementById('buy-total').value = (balance * (val / 100)).toFixed(2);
+ const mode = document.getElementById('buy-limit').checked ? 'limit' : 'market';
+ if (mode === 'market') {
+ document.getElementById('buy-amount').value = (balance * (val / 100)).toFixed(2);
+ } else {
+ const price = parseFloat(document.getElementById('buy-price').value);
+ if (price > 0) document.getElementById('buy-amount').value = (balance * (val / 100) / price).toFixed(4);
+ }
} else if (side === 'sell') {
- // Simulated 1.5 BTC for testing
- document.getElementById('sell-amount').value = (1.5 * (val / 100)).toFixed(4);
+ document.getElementById('sell-amount').value = (assetBalance * (val / 100)).toFixed(4);
} else if (side === 'contract') {
const margin = (balance * (val / 100));
- document.getElementById('c-margin').textContent = margin.toFixed(2);
+ document.getElementById('c-margin-display').textContent = margin.toFixed(2);
document.getElementById('c-amount').value = Math.floor((margin * leverage) / 100);
}
}
+ function updateSlider(side) {
+ if (side === 'buy') {
+ const mode = document.getElementById('buy-limit').checked ? 'limit' : 'market';
+ const amount = parseFloat(document.getElementById('buy-amount').value) || 0;
+ let percent = 0;
+ if (mode === 'market') {
+ percent = (amount / balance) * 100;
+ } else {
+ const price = parseFloat(document.getElementById('buy-price').value) || 0;
+ percent = (amount * price / balance) * 100;
+ }
+ document.getElementById('buy-slider').value = Math.min(100, percent);
+ } else if (side === 'sell') {
+ const amount = parseFloat(document.getElementById('sell-amount').value) || 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 margin = (100 * lots) / leverage;
+ document.getElementById('c-margin-display').textContent = margin.toFixed(2);
+ document.getElementById('c-slider').value = Math.min(100, (margin / balance) * 100);
+ }
+ }
+
function setLev(l) {
leverage = l;
document.getElementById('lev-btn').textContent = '杠杆: ' + l + 'x';
@@ -328,7 +455,6 @@
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';
- // Order Book
const askD = document.getElementById('asks'); const bidD = document.getElementById('bids');
askD.innerHTML = ''; bidD.innerHTML = '';
for (let i = 0; i < 10; i++) {
@@ -336,21 +462,75 @@
bidD.innerHTML += `
${(currentPrice-(i+1)*0.1).toFixed(2)}${(Math.random()*1.2).toFixed(4)} `;
}
- // Sync limit inputs if empty
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;
}
+
+ document.querySelectorAll('.current-p-val').forEach(el => { el.textContent = currentPrice.toLocaleString(); });
+ document.querySelectorAll('.upl-val').forEach(el => {
+ const entry = parseFloat(el.getAttribute('data-entry'));
+ const side = el.getAttribute('data-side');
+ const lots = parseFloat(el.getAttribute('data-lots'));
+ let upl = 0;
+ if (side === 'LONG') upl = (currentPrice - entry) / entry * (lots * 100);
+ else upl = (entry - currentPrice) / entry * (lots * 100);
+ el.textContent = (upl >= 0 ? '+' : '') + upl.toFixed(2) + ' USDT';
+ el.className = 'upl-val ' + (upl >= 0 ? 'text-success' : 'text-danger');
+ });
+
} catch(e) {}
}
- function submitOrder(side) {
- alert('订单已提交,正在匹配中...');
+ async function submitOrder(side) {
+ const mode = tradeType === 'SPOT'
+ ? (document.getElementById(side.toLowerCase() + '-limit').checked ? 'LIMIT' : 'MARKET')
+ : (document.getElementById('c-limit').checked ? 'LIMIT' : 'MARKET');
+
+ let price = 0;
+ let amount = 0;
+
+ if (tradeType === 'SPOT') {
+ price = document.getElementById(side.toLowerCase() + '-price').value;
+ amount = document.getElementById(side.toLowerCase() + '-amount').value;
+ } else {
+ price = document.getElementById('c-price').value;
+ amount = document.getElementById('c-amount').value;
+ }
+
+ 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
+ })
+ });
+ const res = await r.json();
+ if (res.status === 'success') {
+ alert('下单成功!');
+ location.reload();
+ } else {
+ alert('下单失败: ' + res.message);
+ }
+ } catch(e) { alert('提交出错'); }
+ }
+
+ async function closePos(id) {
+ if (confirm('确定要平仓吗?')) {
+ const r = await fetch(`/api/close_position/${id}/`, {
+ method: 'POST',
+ headers: { 'X-CSRFToken': '{{ csrf_token }}' }
+ });
+ const res = await r.json();
+ if (res.status === 'success') location.reload();
+ }
}
initTV(); getMarkets(); tick();
setInterval(tick, 2000);
-{% endblock %}
+{% endblock %}
\ No newline at end of file
diff --git a/core/urls.py b/core/urls.py
index 3c7eeea..36bca48 100644
--- a/core/urls.py
+++ b/core/urls.py
@@ -1,8 +1,11 @@
from django.urls import path
from . import views
+app_name = 'core'
+
urlpatterns = [
path('', views.index, name='index'),
+ path('trade/
/', views.trade, name='trade'),
path('spot/', views.trade, {'trade_type': 'spot'}, name='spot_trade'),
path('contract/', views.trade, {'trade_type': 'contract'}, name='contract_trade'),
path('markets/', views.market_center, name='market_center'),
@@ -14,6 +17,10 @@ urlpatterns = [
path('register/', views.register_view, name='register'),
path('api/market_data/', views.market_data, name='market_data'),
path('api/submit_order/', views.submit_order, name='submit_order'),
+ path('api/close_position//', views.close_position, name='close_position'),
+
+ # Dynamic Article/Placeholder Route
+ path('article//', views.placeholder_view, name='placeholder'),
# Footer links
path('help/', views.placeholder_view, {'title': '帮助中心'}, name='help_center'),
diff --git a/core/views.py b/core/views.py
index 270d68f..bb6bb3d 100644
--- a/core/views.py
+++ b/core/views.py
@@ -1,32 +1,49 @@
-from django.shortcuts import render, redirect
+from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib.auth import login, authenticate
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.http import JsonResponse
from django.contrib.auth.models import User
from django.contrib import messages
-from .models import Account, Order, Transaction, Cryptocurrency, SiteSettings
+from .models import Account, Order, Transaction, Cryptocurrency, SiteSettings, Asset, Position
import random
import decimal
import json
from django.views.decorators.csrf import csrf_exempt
+from django.db import transaction
def index(request):
return render(request, 'core/index.html')
def trade(request, trade_type='spot'):
symbol = request.GET.get('symbol', 'BTCUSDT')
- # Fetch all active cryptocurrencies for the sidebar
+ base_symbol = symbol.replace('USDT', '')
cryptos = Cryptocurrency.objects.filter(is_active=True)
+ account = None
+ assets = {}
+ base_asset_balance = decimal.Decimal('0')
+
+ if request.user.is_authenticated:
+ account, _ = Account.objects.get_or_create(user=request.user)
+ for asset in account.assets.all():
+ assets[asset.currency] = asset
+ if asset.currency == base_symbol:
+ base_asset_balance = asset.balance
+
context = {
'symbol': symbol,
+ 'base_symbol': base_symbol,
'trade_type': trade_type.upper(),
'cryptos': cryptos,
+ 'account': account,
+ 'assets': assets,
+ 'base_asset_balance': base_asset_balance,
}
return render(request, 'core/trade.html', context)
def market_center(request):
- return render(request, 'core/index.html', {'market_only': True})
+ cryptos = Cryptocurrency.objects.filter(is_active=True)
+ return render(request, 'core/market_center.html', {'cryptos': cryptos})
def placeholder_view(request, title):
settings = SiteSettings.objects.first()
@@ -61,10 +78,12 @@ def profile(request):
account, created = Account.objects.get_or_create(user=request.user)
recent_transactions = Transaction.objects.filter(account=account).order_by('-timestamp')[:10]
recent_orders = Order.objects.filter(account=account).order_by('-created_at')[:10]
+ positions = Position.objects.filter(account=account, is_active=True)
context = {
'account': account,
'recent_transactions': recent_transactions,
'recent_orders': recent_orders,
+ 'positions': positions,
}
return render(request, 'core/profile.html', context)
@@ -82,7 +101,7 @@ def deposit(request):
status='pending',
tx_hash=tx_id
)
- return redirect('profile')
+ return redirect('core:profile')
return render(request, 'core/deposit.html')
@login_required
@@ -103,7 +122,7 @@ def withdraw(request):
status='completed',
tx_hash=f"wd_{random.randint(1000, 9999)}"
)
- return redirect('profile')
+ return redirect('core:profile')
return render(request, 'core/withdraw.html', {'account': account})
@login_required
@@ -112,21 +131,9 @@ def verify(request):
if request.method == 'POST':
account.kyc_status = 'pending'
account.save()
- return redirect('profile')
+ return redirect('core:profile')
return render(request, 'core/verify.html', {'account': account})
-def generate_captcha():
- a = random.randint(1, 10)
- b = random.randint(1, 10)
- op = random.choice(['+', '-', '*'])
- if op == '+':
- res = a + b
- elif op == '-':
- res = a - b
- else:
- res = a * b
- return f"{a} {op} {b} = ?", res
-
def register_view(request):
if request.method == 'POST':
username = request.POST.get('username')
@@ -145,9 +152,8 @@ def register_view(request):
user = User.objects.create_user(username=username, password=password)
Account.objects.get_or_create(user=user)
login(request, user)
- return redirect('index')
+ return redirect('core:index')
- # Generate new captcha
captcha_text, captcha_result = generate_captcha()
request.session['captcha_result'] = captcha_result
@@ -155,13 +161,22 @@ def register_view(request):
'captcha_text': captcha_text
})
+def generate_captcha():
+ a = random.randint(1, 10)
+ b = random.randint(1, 10)
+ op = random.choice(['+', '-', '*'])
+ if op == '+': res = a + b
+ elif op == '-': res = a - b
+ else: res = a * b
+ return f"{a} {op} {b} = ?", res
+
def login_view(request):
if request.method == 'POST':
form = AuthenticationForm(data=request.POST)
if form.is_valid():
user = form.get_user()
login(request, user)
- return redirect('index')
+ return redirect('core:index')
else:
form = AuthenticationForm()
return render(request, 'core/login.html', {'form': form})
@@ -169,76 +184,181 @@ def login_view(request):
@login_required
@csrf_exempt
def submit_order(request):
- if request.method == 'POST':
- try:
- data = json.loads(request.body)
- symbol = data.get('symbol', 'BTCUSDT')
- side = data.get('side')
- amount = decimal.Decimal(data.get('amount', 0))
- trade_type = data.get('trade_type', 'SPOT')
- order_type = data.get('order_type', 'MARKET')
- price = data.get('price')
+ if request.method != 'POST':
+ return JsonResponse({'status': 'error', 'message': 'Invalid request'})
+
+ try:
+ data = json.loads(request.body)
+ symbol = data.get('symbol', 'BTCUSDT')
+ side = data.get('side') # BUY or SELL
+ trade_type = data.get('trade_type', 'SPOT')
+ order_type = data.get('order_type', 'MARKET')
+ price_val = data.get('price')
+ amount_val = data.get('amount', 0)
+ leverage = int(data.get('leverage', 20))
+
+ account = request.user.account
+ base_symbol = symbol.replace('USDT', '')
+
+ # Get current market price for validations and market orders
+ crypto = Cryptocurrency.objects.filter(symbol=base_symbol).first()
+ current_price = crypto.current_price if (crypto and crypto.current_price > 0) else decimal.Decimal('48000')
+
+ with transaction.atomic():
+ if trade_type == 'SPOT':
+ if order_type == 'MARKET':
+ # Spot Market Order: Execute Immediately
+ if side == 'BUY':
+ # BUY: amount_val is USDT
+ total_usdt = decimal.Decimal(str(amount_val))
+ if account.balance < total_usdt:
+ return JsonResponse({'status': 'error', 'message': '余额不足'})
+
+ exec_amount = total_usdt / current_price
+ account.balance -= total_usdt
+ account.save()
+
+ asset, _ = Asset.objects.get_or_create(account=account, currency=base_symbol)
+ asset.balance += exec_amount
+ asset.save()
+
+ Order.objects.create(
+ account=account, symbol=symbol, side=side, order_type=order_type,
+ trade_type=trade_type, amount=exec_amount, total_usdt=total_usdt, status='FILLED'
+ )
+ else: # SELL
+ # SELL: amount_val is coin quantity
+ exec_amount = decimal.Decimal(str(amount_val))
+ asset, _ = Asset.objects.get_or_create(account=account, currency=base_symbol)
+ if asset.balance < exec_amount:
+ return JsonResponse({'status': 'error', 'message': f'{base_symbol} 余额不足'})
+
+ total_usdt = exec_amount * current_price
+ asset.balance -= exec_amount
+ asset.save()
+
+ account.balance += total_usdt
+ account.save()
+
+ Order.objects.create(
+ account=account, symbol=symbol, side=side, order_type=order_type,
+ trade_type=trade_type, amount=exec_amount, total_usdt=total_usdt, status='FILLED'
+ )
+ else: # LIMIT
+ # Spot Limit Order: Freeze Assets, Pend
+ price = decimal.Decimal(str(price_val))
+ amount = decimal.Decimal(str(amount_val))
+ if side == 'BUY':
+ total_usdt = price * amount
+ if account.balance < total_usdt:
+ return JsonResponse({'status': 'error', 'message': '余额不足'})
+
+ account.balance -= total_usdt
+ account.frozen_balance += total_usdt
+ account.save()
+
+ Order.objects.create(
+ account=account, symbol=symbol, side=side, order_type=order_type,
+ trade_type=trade_type, amount=amount, price=price, total_usdt=total_usdt, status='PENDING'
+ )
+ else: # SELL
+ asset, _ = Asset.objects.get_or_create(account=account, currency=base_symbol)
+ if asset.balance < amount:
+ return JsonResponse({'status': 'error', 'message': f'{base_symbol} 余额不足'})
+
+ asset.balance -= amount
+ asset.frozen += amount
+ asset.save()
+
+ Order.objects.create(
+ account=account, symbol=symbol, side=side, order_type=order_type,
+ trade_type=trade_type, amount=amount, price=price, status='PENDING'
+ )
- account = request.user.account
-
- # Fetch current price from DB or simulated
- crypto = Cryptocurrency.objects.filter(symbol=symbol.replace('USDT', '')).first()
- current_price = crypto.current_price if crypto else decimal.Decimal('48000')
-
- # If market order, use current price. If limit, use provided price.
- exec_price = decimal.Decimal(str(price)) if order_type == 'LIMIT' else current_price
-
- if side == 'BUY':
- cost = amount * exec_price
- if account.balance >= cost:
- account.balance -= cost
- account.save()
- Order.objects.create(
- account=account,
- symbol=symbol,
- side=side,
- amount=amount,
- trade_type=trade_type,
- order_type=order_type,
- status='FILLED',
- entry_price=exec_price,
- price=exec_price if order_type == 'LIMIT' else None
- )
- return JsonResponse({'status': 'success'})
- else:
- return JsonResponse({'status': 'error', 'message': '余额不足'})
- else:
- # SELL
- revenue = amount * exec_price
- Order.objects.create(
- account=account,
- symbol=symbol,
- side=side,
- amount=amount,
- trade_type=trade_type,
- order_type=order_type,
- status='FILLED',
- entry_price=exec_price,
- price=exec_price if order_type == 'LIMIT' else None
- )
- account.balance += revenue
- account.save()
- return JsonResponse({'status': 'success'})
+ else: # CONTRACT
+ # Contract: Initial Margin = face_value(100) * lots / leverage
+ lots = decimal.Decimal(str(amount_val))
+ face_value = decimal.Decimal('100')
+ margin_required = (face_value * lots) / decimal.Decimal(str(leverage))
- except Exception as e:
- return JsonResponse({'status': 'error', 'message': str(e)})
- return JsonResponse({'status': 'error', 'message': 'Invalid request'})
+ if account.balance < margin_required:
+ return JsonResponse({'status': 'error', 'message': '保证金不足'})
+
+ if order_type == 'MARKET':
+ # Contract Market Order: Immediate Open Position
+ account.balance -= margin_required
+ account.save()
+
+ Position.objects.create(
+ account=account, symbol=symbol, side='LONG' if side == 'BUY' else 'SHORT',
+ leverage=leverage, entry_price=current_price, lots=lots, margin=margin_required
+ )
+
+ Order.objects.create(
+ account=account, symbol=symbol, side=side, order_type=order_type,
+ trade_type=trade_type, amount=lots, leverage=leverage, status='FILLED'
+ )
+ else: # LIMIT
+ # Contract Limit Order: Freeze Margin, Pend
+ price = decimal.Decimal(str(price_val))
+ account.balance -= margin_required
+ account.frozen_balance += margin_required
+ account.save()
+
+ Order.objects.create(
+ account=account, symbol=symbol, side=side, order_type=order_type,
+ trade_type=trade_type, amount=lots, price=price, leverage=leverage, status='PENDING'
+ )
+
+ return JsonResponse({'status': 'success'})
+
+ except Exception as e:
+ return JsonResponse({'status': 'error', 'message': str(e)})
+
+@login_required
+@csrf_exempt
+def close_position(request, position_id):
+ if request.method != 'POST':
+ return JsonResponse({'status': 'error', 'message': 'Invalid request'})
+
+ position = get_object_or_404(Position, id=position_id, account=request.user.account, is_active=True)
+
+ base_symbol = position.symbol.replace('USDT', '')
+ crypto = Cryptocurrency.objects.filter(symbol=base_symbol).first()
+ current_price = crypto.current_price if (crypto and crypto.current_price > 0) else decimal.Decimal('48000')
+
+ face_value = decimal.Decimal('100')
+ # 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)
+
+ 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)
+ account.save()
+
+ position.is_active = False
+ position.save()
+
+ # Log closing order
+ Order.objects.create(
+ account=account, symbol=position.symbol, side='SELL' if position.side == 'LONG' else 'BUY',
+ order_type='MARKET', trade_type='CONTRACT', amount=position.lots, status='FILLED'
+ )
+
+ return JsonResponse({'status': 'success'})
def market_data(request):
- # This might be used by some local scripts, though index/trade use Binance API directly
- symbols = ['BTC', 'ETH', 'BNB', 'SOL', 'ADA', 'XRP', 'DOT', 'DOGE']
+ cryptos = Cryptocurrency.objects.filter(is_active=True)
data = []
- for s in symbols:
- price = random.uniform(10, 60000)
- change = random.uniform(-5, 5)
+ for c in cryptos:
data.append({
- 'symbol': s,
- 'price': round(price, 4),
- 'change': round(change, 2)
+ 'symbol': c.symbol,
+ 'price': float(c.current_price),
+ 'change': float(c.change_24h)
})
return JsonResponse(data, safe=False)
\ No newline at end of file