BIT
This commit is contained in:
parent
6a5fce6db2
commit
65d8cd957f
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,6 +1,6 @@
|
|||||||
from .models import SiteSettings
|
from .models import SiteSettings
|
||||||
|
|
||||||
def site_settings(request):
|
def project_context(request):
|
||||||
settings = SiteSettings.objects.first()
|
settings = SiteSettings.objects.first()
|
||||||
if not settings:
|
if not settings:
|
||||||
settings = SiteSettings.objects.create(site_name="BitCrypto")
|
settings = SiteSettings.objects.create(site_name="BitCrypto")
|
||||||
@ -9,3 +9,6 @@ def site_settings(request):
|
|||||||
'project_name': settings.site_name,
|
'project_name': settings.site_name,
|
||||||
'project_description': "全球领先的数字资产交易平台"
|
'project_description': "全球领先的数字资产交易平台"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Alias for compatibility if needed
|
||||||
|
site_settings = project_context
|
||||||
@ -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': '持仓管理',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
@ -57,6 +57,8 @@ class Account(models.Model):
|
|||||||
account_type = models.CharField(max_length=20, choices=ACCOUNT_TYPES, default='SIMULATED', verbose_name=_("账户类型"))
|
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)"))
|
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=_("信用分"))
|
credit_score = models.IntegerField(default=80, verbose_name=_("信用分"))
|
||||||
kyc_status = models.CharField(max_length=20, choices=KYC_STATUS, default='UNVERIFIED', 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', _('市价')))
|
TYPE_CHOICES = (('LIMIT', _('限价')), ('MARKET', _('市价')))
|
||||||
TRADE_TYPE_CHOICES = (('SPOT', _('现货')), ('CONTRACT', _('合约')))
|
TRADE_TYPE_CHOICES = (('SPOT', _('现货')), ('CONTRACT', _('合约')))
|
||||||
STATUS_CHOICES = (
|
STATUS_CHOICES = (
|
||||||
('LIVE', _('进行中')),
|
('PENDING', _('等待成交')),
|
||||||
('PARTIALLY_FILLED', _('部分成交')),
|
('PARTIALLY_FILLED', _('部分成交')),
|
||||||
('FILLED', _('已成交')),
|
('FILLED', _('已成交')),
|
||||||
('CANCELED', _('已撤销')),
|
('CANCELED', _('已撤销')),
|
||||||
('CLOSED', _('已平仓')),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
account = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='orders')
|
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=_("类型"))
|
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=_("委托价格"))
|
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=_("数量"))
|
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=_("已成交数量"))
|
total_usdt = models.DecimalField(max_digits=30, decimal_places=8, null=True, blank=True, verbose_name=_("成交额 (USDT)"))
|
||||||
|
|
||||||
leverage = models.IntegerField(default=1, verbose_name=_("杠杆倍数"))
|
leverage = models.IntegerField(default=1, verbose_name=_("杠杆倍数"))
|
||||||
entry_price = models.DecimalField(max_digits=30, decimal_places=8, null=True, blank=True, verbose_name=_("入场价格"))
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='PENDING', 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=_("状态"))
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("创建时间"))
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("创建时间"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -127,6 +124,25 @@ class Order(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.trade_type} {self.side} {self.amount} {self.symbol}"
|
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):
|
class Transaction(models.Model):
|
||||||
TX_TYPE = (('deposit', _('充值')), ('withdraw', _('提现')))
|
TX_TYPE = (('deposit', _('充值')), ('withdraw', _('提现')))
|
||||||
TX_STATUS = (('pending', _('待处理')), ('completed', _('成功')), ('failed', _('失败')))
|
TX_STATUS = (('pending', _('待处理')), ('completed', _('成功')), ('failed', _('失败')))
|
||||||
|
|||||||
@ -100,10 +100,10 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
<ul class="navbar-nav me-auto">
|
<ul class="navbar-nav me-auto">
|
||||||
<li class="nav-item"><a class="nav-link" href="{% url 'index' %}">首页</a></li>
|
<li class="nav-item"><a class="nav-link" href="{% url 'core:index' %}">首页</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="{% url 'spot_trade' %}">现货交易</a></li>
|
<li class="nav-item"><a class="nav-link" href="{% url 'core:spot_trade' %}">现货交易</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="{% url 'contract_trade' %}">合约交易</a></li>
|
<li class="nav-item"><a class="nav-link" href="{% url 'core:contract_trade' %}">合约交易</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="{% url 'market_center' %}">行情中心</a></li>
|
<li class="nav-item"><a class="nav-link" href="{% url 'core:market_center' %}">行情中心</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
@ -113,15 +113,15 @@
|
|||||||
<span>{{ user.username }}</span>
|
<span>{{ user.username }}</span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end shadow">
|
<ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end shadow">
|
||||||
<li><a class="dropdown-item" href="{% url 'profile' %}"><i class="bi bi-person-badge me-2"></i>个人中心</a></li>
|
<li><a class="dropdown-item" href="{% url 'core:profile' %}"><i class="bi bi-person-badge me-2"></i>个人中心</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'profile' %}"><i class="bi bi-wallet2 me-2"></i>我的资产</a></li>
|
<li><a class="dropdown-item" href="{% url 'core:profile' %}"><i class="bi bi-wallet2 me-2"></i>我的资产</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item text-danger" href="/login/"><i class="bi bi-box-arrow-right me-2"></i>安全退出</a></li>
|
<li><a class="dropdown-item text-danger" href="/login/"><i class="bi bi-box-arrow-right me-2"></i>安全退出</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'login' %}" class="nav-link me-3">登录</a>
|
<a href="{% url 'core:login' %}" class="nav-link me-3">登录</a>
|
||||||
<a href="{% url 'register' %}" class="btn btn-primary">立即注册</a>
|
<a href="{% url 'core:register' %}" class="btn btn-primary">立即注册</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -149,32 +149,32 @@
|
|||||||
<div class="col-md-2 offset-md-1">
|
<div class="col-md-2 offset-md-1">
|
||||||
<h6>产品服务</h6>
|
<h6>产品服务</h6>
|
||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled">
|
||||||
<li><a href="{% url 'spot_trade' %}">现货交易</a></li>
|
<li><a href="{% url 'core:spot_trade' %}">现货交易</a></li>
|
||||||
<li><a href="{% url 'contract_trade' %}">永续合约</a></li>
|
<li><a href="{% url 'core:contract_trade' %}">永续合约</a></li>
|
||||||
<li><a href="{% url 'placeholder' '币安赚币' %}">理财中心</a></li>
|
<li><a href="{% url 'core:placeholder' '币安赚币' %}">理财中心</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<h6>法律法规</h6>
|
<h6>法律法规</h6>
|
||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled">
|
||||||
<li><a href="{% url 'placeholder' '服务条款' %}">服务条款</a></li>
|
<li><a href="{% url 'core:placeholder' '服务条款' %}">服务条款</a></li>
|
||||||
<li><a href="{% url 'placeholder' '隐私政策' %}">隐私政策</a></li>
|
<li><a href="{% url 'core:placeholder' '隐私政策' %}">隐私政策</a></li>
|
||||||
<li><a href="{% url 'placeholder' '免责声明' %}">免责声明</a></li>
|
<li><a href="{% url 'core:placeholder' '免责声明' %}">免责声明</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<h6>客户支持</h6>
|
<h6>客户支持</h6>
|
||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled">
|
||||||
<li><a href="{% url 'placeholder' '帮助中心' %}">帮助中心</a></li>
|
<li><a href="{% url 'core:placeholder' '帮助中心' %}">帮助中心</a></li>
|
||||||
<li><a href="{% url 'placeholder' '公告中心' %}">公告中心</a></li>
|
<li><a href="{% url 'core:placeholder' '公告中心' %}">公告中心</a></li>
|
||||||
<li><a href="{% url 'placeholder' '提交请求' %}">提交工单</a></li>
|
<li><a href="{% url 'core:placeholder' '提交请求' %}">提交工单</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<h6>关于我们</h6>
|
<h6>关于我们</h6>
|
||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled">
|
||||||
<li><a href="{% url 'placeholder' '公司简介' %}">公司简介</a></li>
|
<li><a href="{% url 'core:placeholder' '公司简介' %}">公司简介</a></li>
|
||||||
<li><a href="{% url 'placeholder' '加入我们' %}">招贤纳士</a></li>
|
<li><a href="{% url 'core:placeholder' '加入我们' %}">招贤纳士</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -18,8 +18,8 @@
|
|||||||
<h1 class="display-3 fw-bold mb-4">开启您的<br><span style="color: var(--accent-color);">加密货币</span>之旅</h1>
|
<h1 class="display-3 fw-bold mb-4">开启您的<br><span style="color: var(--accent-color);">加密货币</span>之旅</h1>
|
||||||
<p class="lead text-light mb-5">在全球最受信任的交易平台买卖和存储加密货币。BitCrypto 为您提供安全、稳定、高效的服务。</p>
|
<p class="lead text-light mb-5">在全球最受信任的交易平台买卖和存储加密货币。BitCrypto 为您提供安全、稳定、高效的服务。</p>
|
||||||
<div class="d-flex gap-3">
|
<div class="d-flex gap-3">
|
||||||
<a href="{% url 'register' %}" class="btn btn-warning btn-lg px-5 fw-bold">立即注册</a>
|
<a href="{% url 'core:register' %}" class="btn btn-warning btn-lg px-5 fw-bold">立即注册</a>
|
||||||
<a href="{% url 'spot_trade' %}" class="btn btn-outline-light btn-lg px-5 fw-bold">开始交易</a>
|
<a href="{% url 'core:spot_trade' %}" class="btn btn-outline-light btn-lg px-5 fw-bold">开始交易</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -32,7 +32,7 @@
|
|||||||
<div class="col-lg-7 text-lg-end">
|
<div class="col-lg-7 text-lg-end">
|
||||||
<h1 class="display-4 fw-bold mb-4">领先的<span style="color: var(--accent-color);">衍生品</span>交易平台</h1>
|
<h1 class="display-4 fw-bold mb-4">领先的<span style="color: var(--accent-color);">衍生品</span>交易平台</h1>
|
||||||
<p class="lead text-light mb-5">最高200倍杠杆,支持多种永续合约,毫秒级撮合引擎。多空双向交易,灵活捕捉市场机会。</p>
|
<p class="lead text-light mb-5">最高200倍杠杆,支持多种永续合约,毫秒级撮合引擎。多空双向交易,灵活捕捉市场机会。</p>
|
||||||
<a href="{% url 'contract_trade' %}" class="btn btn-warning btn-lg px-5 fw-bold">进入合约交易</a>
|
<a href="{% url 'core:contract_trade' %}" class="btn btn-warning btn-lg px-5 fw-bold">进入合约交易</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -98,7 +98,7 @@
|
|||||||
<h2 class="fw-bold">热门行情</h2>
|
<h2 class="fw-bold">热门行情</h2>
|
||||||
<p class="text-secondary mb-0">实时获取全球顶级加密货币价格走势</p>
|
<p class="text-secondary mb-0">实时获取全球顶级加密货币价格走势</p>
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'market_center' %}" class="text-warning text-decoration-none fw-bold">查看更多市场 <i class="bi bi-arrow-right"></i></a>
|
<a href="{% url 'core:market_center' %}" class="text-warning text-decoration-none fw-bold">查看更多市场 <i class="bi bi-arrow-right"></i></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
|
|||||||
@ -41,7 +41,7 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p class="text-secondary small">还没有账户? <a href="{% url 'register' %}" class="text-warning text-decoration-none fw-bold">立即注册</a></p>
|
<p class="text-secondary small">还没有账户? <a href="{% url 'core:register' %}" class="text-warning text-decoration-none fw-bold">立即注册</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-5 pt-4 border-top border-secondary text-center">
|
<div class="mt-5 pt-4 border-top border-secondary text-center">
|
||||||
|
|||||||
107
core/templates/core/market_center.html
Normal file
107
core/templates/core/market_center.html
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="fw-bold text-white mb-0">行情中心</h2>
|
||||||
|
<div class="input-group w-auto">
|
||||||
|
<span class="input-group-text bg-dark border-secondary"><i class="bi bi-search text-secondary"></i></span>
|
||||||
|
<input type="text" id="market-search" class="form-control bg-dark text-white border-secondary" placeholder="搜索币种" onkeyup="filterMarket()">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="glass-card overflow-hidden" style="border-radius: 12px; border: 1px solid #2b3139; background: #161a1e;">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-dark table-hover mb-0 align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr class="text-secondary border-bottom border-secondary">
|
||||||
|
<th class="ps-4 py-3">币种</th>
|
||||||
|
<th class="py-3">最新价</th>
|
||||||
|
<th class="py-3">24h 涨跌</th>
|
||||||
|
<th class="py-3">24h 最高</th>
|
||||||
|
<th class="py-3">24h 最低</th>
|
||||||
|
<th class="py-3">24h 成交额</th>
|
||||||
|
<th class="pe-4 py-3 text-end">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="market-table-body">
|
||||||
|
{% for crypto in cryptos %}
|
||||||
|
<tr class="market-row" data-symbol="{{ crypto.symbol }}">
|
||||||
|
<td class="ps-4 py-3">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<img src="https://static.okx.com/cdn/oksupport/asset/currency/icon/{{ crypto.symbol|lower }}.png"
|
||||||
|
class="rounded-circle me-3" width="32" height="32"
|
||||||
|
onerror="this.src='https://static.okx.com/cdn/oksupport/asset/currency/icon/generic.png'">
|
||||||
|
<div>
|
||||||
|
<div class="fw-bold text-white">{{ crypto.symbol }}</div>
|
||||||
|
<div class="text-secondary small">{{ crypto.name }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="py-3 fw-bold price-val">--</td>
|
||||||
|
<td class="py-3 change-val">--</td>
|
||||||
|
<td class="py-3 high-val text-secondary">--</td>
|
||||||
|
<td class="py-3 low-val text-secondary">--</td>
|
||||||
|
<td class="py-3 vol-val text-secondary">--</td>
|
||||||
|
<td class="pe-4 py-3 text-end">
|
||||||
|
<a href="{% url 'core:trade' 'spot' %}?symbol={{ crypto.symbol }}USDT" class="btn btn-outline-warning btn-sm px-3 me-2">现货</a>
|
||||||
|
<a href="{% url 'core:trade' 'contract' %}?symbol={{ crypto.symbol }}USDT" class="btn btn-warning btn-sm px-3 text-dark">合约</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.glass-card { background: rgba(22, 26, 30, 0.8); backdrop-filter: blur(10px); }
|
||||||
|
.table-dark { --bs-table-bg: transparent; }
|
||||||
|
.table-hover tbody tr:hover { background-color: rgba(255, 255, 255, 0.05) !important; }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
async function updateMarket() {
|
||||||
|
try {
|
||||||
|
const r = await fetch('https://api.binance.com/api/v3/ticker/24hr');
|
||||||
|
const data = await r.json();
|
||||||
|
const tickers = {};
|
||||||
|
data.forEach(t => {
|
||||||
|
if (t.symbol.endsWith('USDT')) {
|
||||||
|
tickers[t.symbol.replace('USDT', '')] = t;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.market-row').forEach(row => {
|
||||||
|
const sym = row.getAttribute('data-symbol');
|
||||||
|
const t = tickers[sym];
|
||||||
|
if (t) {
|
||||||
|
const chg = parseFloat(t.priceChangePercent);
|
||||||
|
row.querySelector('.price-val').textContent = parseFloat(t.lastPrice).toLocaleString();
|
||||||
|
row.querySelector('.price-val').className = 'py-3 fw-bold price-val ' + (chg >= 0 ? 'text-success' : 'text-danger');
|
||||||
|
row.querySelector('.change-val').textContent = (chg >= 0 ? '+' : '') + chg.toFixed(2) + '%';
|
||||||
|
row.querySelector('.change-val').className = 'py-3 change-val ' + (chg >= 0 ? 'text-success' : 'text-danger');
|
||||||
|
row.querySelector('.high-val').textContent = parseFloat(t.highPrice).toLocaleString();
|
||||||
|
row.querySelector('.low-val').textContent = parseFloat(t.lowPrice).toLocaleString();
|
||||||
|
row.querySelector('.vol-val').textContent = (parseFloat(t.quoteVolume) / 1000000).toFixed(2) + 'M';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterMarket() {
|
||||||
|
const query = document.getElementById('market-search').value.toLowerCase();
|
||||||
|
document.querySelectorAll('.market-row').forEach(row => {
|
||||||
|
const sym = row.getAttribute('data-symbol').toLowerCase();
|
||||||
|
row.style.display = sym.includes(query) ? '' : 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMarket();
|
||||||
|
setInterval(updateMarket, 3000);
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@ -23,8 +23,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-lg-auto mt-3 mt-lg-0">
|
<div class="col-lg-auto mt-3 mt-lg-0">
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<a href="{% url 'deposit' %}" class="btn btn-warning fw-bold px-4">充币</a>
|
<a href="{% url 'core:deposit' %}" class="btn btn-warning fw-bold px-4">充币</a>
|
||||||
<a href="{% url 'withdraw' %}" class="btn btn-outline-light px-4">提币</a>
|
<a href="{% url 'core:withdraw' %}" class="btn btn-outline-light px-4">提币</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -122,7 +122,7 @@
|
|||||||
<div class="fw-bold text-white">身份认证 (KYC)</div>
|
<div class="fw-bold text-white">身份认证 (KYC)</div>
|
||||||
<p class="text-secondary x-small mb-0">提升提现额度至 100 BTC</p>
|
<p class="text-secondary x-small mb-0">提升提现额度至 100 BTC</p>
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'verify' %}" class="btn btn-link btn-sm text-warning ms-auto text-decoration-none">立即认证</a>
|
<a href="{% url 'core:verify' %}" class="btn btn-link btn-sm text-warning ms-auto text-decoration-none">立即认证</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -66,7 +66,7 @@
|
|||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input bg-transparent border-secondary" type="checkbox" id="terms" required checked>
|
<input class="form-check-input bg-transparent border-secondary" type="checkbox" id="terms" required checked>
|
||||||
<label class="form-check-label text-secondary small" for="terms">
|
<label class="form-check-label text-secondary small" for="terms">
|
||||||
我已阅读并同意 <a href="{% url 'placeholder' '服务条款' %}" class="text-warning">服务条款</a> 和 <a href="{% url 'placeholder' '隐私政策' %}" class="text-warning">隐私政策</a>
|
我已阅读并同意 <a href="{% url 'core:placeholder' '服务条款' %}" class="text-warning">服务条款</a> 和 <a href="{% url 'core:placeholder' '隐私政策' %}" class="text-warning">隐私政策</a>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -75,7 +75,7 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p class="text-secondary small">已有账户? <a href="{% url 'login' %}" class="text-warning text-decoration-none fw-bold">立即登录</a></p>
|
<p class="text-secondary small">已有账户? <a href="{% url 'core:login' %}" class="text-warning text-decoration-none fw-bold">立即登录</a></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -57,8 +57,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-1 bg-dark p-1" style="border-radius: 4px;">
|
<div class="d-flex gap-1 bg-dark p-1" style="border-radius: 4px;">
|
||||||
<a href="{% url 'trade' 'spot' %}?symbol={{ symbol }}" class="btn btn-sm {% if trade_type == 'SPOT' %}btn-warning text-dark{% else %}btn-transparent text-secondary{% endif %} fw-bold px-3">现货</a>
|
<a href="{% url 'core:trade' 'spot' %}?symbol={{ symbol }}" class="btn btn-sm {% if trade_type == 'SPOT' %}btn-warning text-dark{% else %}btn-transparent text-secondary{% endif %} fw-bold px-3">现货</a>
|
||||||
<a href="{% url 'trade' 'contract' %}?symbol={{ symbol }}" class="btn btn-sm {% if trade_type == 'CONTRACT' %}btn-warning text-dark{% else %}btn-transparent text-secondary{% endif %} fw-bold px-3">合约</a>
|
<a href="{% url 'core:trade' 'contract' %}?symbol={{ symbol }}" class="btn btn-sm {% if trade_type == 'CONTRACT' %}btn-warning text-dark{% else %}btn-transparent text-secondary{% endif %} fw-bold px-3">合约</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -68,7 +68,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Trading Forms -->
|
<!-- Trading Forms -->
|
||||||
<div class="glass-card p-3" style="border-radius: 4px; border: 1px solid #2b3139; background: #161a1e;">
|
<div class="glass-card p-3 mb-2" style="border-radius: 4px; border: 1px solid #2b3139; background: #161a1e;">
|
||||||
{% if trade_type == 'SPOT' %}
|
{% if trade_type == 'SPOT' %}
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
<!-- Buy -->
|
<!-- Buy -->
|
||||||
@ -87,9 +87,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-group input-group-sm mb-3">
|
<div class="input-group input-group-sm mb-3">
|
||||||
<span class="input-group-text bg-dark text-secondary border-secondary" style="width: 70px;">成交额</span>
|
<span class="input-group-text bg-dark text-secondary border-secondary" style="width: 70px;" id="buy-label">数量</span>
|
||||||
<input type="number" id="buy-total" class="form-control bg-transparent text-white border-secondary" placeholder="0.00" oninput="updateSlider('buy')">
|
<input type="number" id="buy-amount" class="form-control bg-transparent text-white border-secondary" placeholder="0.00" oninput="updateSlider('buy')">
|
||||||
<span class="input-group-text bg-dark text-secondary border-secondary">USDT</span>
|
<span class="input-group-text bg-dark text-secondary border-secondary" id="buy-unit">{{ base_symbol }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4 px-1">
|
<div class="mb-4 px-1">
|
||||||
@ -103,7 +103,7 @@
|
|||||||
<span class="text-secondary">可用余额</span>
|
<span class="text-secondary">可用余额</span>
|
||||||
<span class="text-white">{{ account.balance|default:"0.00" }} USDT</span>
|
<span class="text-white">{{ account.balance|default:"0.00" }} USDT</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-success w-100 fw-bold py-2 shadow" onclick="submitOrder('BUY')">买入 <span class="base-asset">{{ symbol|slice:":-4" }}</span></button>
|
<button class="btn btn-success w-100 fw-bold py-2 shadow" onclick="submitOrder('BUY')">买入 <span class="base-asset">{{ base_symbol }}</span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sell -->
|
<!-- Sell -->
|
||||||
@ -124,7 +124,7 @@
|
|||||||
<div class="input-group input-group-sm mb-3">
|
<div class="input-group input-group-sm mb-3">
|
||||||
<span class="input-group-text bg-dark text-secondary border-secondary" style="width: 70px;">数量</span>
|
<span class="input-group-text bg-dark text-secondary border-secondary" style="width: 70px;">数量</span>
|
||||||
<input type="number" id="sell-amount" class="form-control bg-transparent text-white border-secondary" placeholder="0.00" oninput="updateSlider('sell')">
|
<input type="number" id="sell-amount" class="form-control bg-transparent text-white border-secondary" placeholder="0.00" oninput="updateSlider('sell')">
|
||||||
<span class="input-group-text bg-dark text-secondary border-secondary">{{ symbol|slice:":-4" }}</span>
|
<span class="input-group-text bg-dark text-secondary border-secondary">{{ base_symbol }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4 px-1">
|
<div class="mb-4 px-1">
|
||||||
@ -135,10 +135,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between x-small mb-3">
|
<div class="d-flex justify-content-between x-small mb-3">
|
||||||
<span class="text-secondary">可用 <span class="base-asset">{{ symbol|slice:":-4" }}</span></span>
|
<span class="text-secondary">可用 <span class="base-asset">{{ base_symbol }}</span></span>
|
||||||
<span class="text-white" id="sell-available">0.00</span>
|
<span class="text-white" id="sell-available-display">{{ base_asset_balance|default:"0.00" }} {{ base_symbol }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-danger w-100 fw-bold py-2 shadow" onclick="submitOrder('SELL')">卖出 <span class="base-asset">{{ symbol|slice:":-4" }}</span></button>
|
<button class="btn btn-danger w-100 fw-bold py-2 shadow" onclick="submitOrder('SELL')">卖出 <span class="base-asset">{{ base_symbol }}</span></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -147,9 +147,9 @@
|
|||||||
<div class="col-12 mb-2 d-flex gap-3 align-items-center">
|
<div class="col-12 mb-2 d-flex gap-3 align-items-center">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<input type="radio" class="btn-check" name="c-mode" id="c-limit" checked onchange="toggleMode('contract', 'limit')">
|
<input type="radio" class="btn-check" name="c-mode" id="c-limit" checked onchange="toggleMode('contract', 'limit')">
|
||||||
<label class="btn btn-outline-warning btn-sm px-4" for="c-limit">限价</label>
|
<label class="btn btn-outline-warning btn-sm px-4" for="c-limit">限价委托</label>
|
||||||
<input type="radio" class="btn-check" name="c-mode" id="c-market" onchange="toggleMode('contract', 'market')">
|
<input type="radio" class="btn-check" name="c-mode" id="c-market" onchange="toggleMode('contract', 'market')">
|
||||||
<label class="btn btn-outline-warning btn-sm px-4" for="c-market">市价</label>
|
<label class="btn btn-outline-warning btn-sm px-4" for="c-market">市价委托</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button class="btn btn-dark btn-sm border-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" id="lev-btn">杠杆: 20x</button>
|
<button class="btn btn-dark btn-sm border-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" id="lev-btn">杠杆: 20x</button>
|
||||||
@ -169,32 +169,96 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="input-group input-group-sm mb-3">
|
<div class="input-group input-group-sm mb-3">
|
||||||
<span class="input-group-text bg-dark text-secondary border-secondary" style="width: 70px;">手数</span>
|
<span class="input-group-text bg-dark text-secondary border-secondary" style="width: 70px;">手数</span>
|
||||||
<input type="number" id="c-amount" class="form-control bg-transparent text-white border-secondary" placeholder="0">
|
<input type="number" id="c-amount" class="form-control bg-transparent text-white border-secondary" placeholder="0" oninput="updateSlider('contract')">
|
||||||
<span class="input-group-text bg-dark text-secondary border-secondary">张</span>
|
<span class="input-group-text bg-dark text-secondary border-secondary">张</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-4 px-1">
|
||||||
<input type="range" class="form-range custom-range" id="c-slider" min="0" max="100" step="1" value="0" oninput="applySlider('contract')">
|
<input type="range" class="form-range custom-range" id="c-slider" min="0" max="100" step="1" value="0" oninput="applySlider('contract')">
|
||||||
|
<div class="d-flex justify-content-between x-small text-secondary mt-1">
|
||||||
|
<span>0%</span><span>25%</span><span>50%</span><span>75%</span><span>100%</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-between x-small mb-3 text-secondary">
|
<div class="d-flex justify-content-between x-small mb-3 text-secondary">
|
||||||
<span>保证金: <span class="text-white" id="c-margin">0.00</span> USDT</span>
|
<span>所需保证金: <span class="text-white" id="c-margin-display">0.00</span> USDT</span>
|
||||||
<span>可用: <span class="text-white">{{ account.balance|default:"0.00" }} USDT</span></span>
|
<span>可用: <span class="text-white">{{ account.balance|default:"0.00" }} USDT</span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-2">
|
<div class="row g-2">
|
||||||
<div class="col-6"><button class="btn btn-success w-100 fw-bold" onclick="submitOrder('BUY')">买入 (做多)</button></div>
|
<div class="col-6"><button class="btn btn-success w-100 fw-bold shadow" onclick="submitOrder('BUY')">买入 (做多)</button></div>
|
||||||
<div class="col-6"><button class="btn btn-danger w-100 fw-bold" onclick="submitOrder('SELL')">卖出 (做空)</button></div>
|
<div class="col-6"><button class="btn btn-danger w-100 fw-bold shadow" onclick="submitOrder('SELL')">卖出 (做空)</button></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="p-3 bg-dark rounded border border-secondary" style="font-size: 12px;">
|
<div class="p-3 bg-dark rounded border border-secondary" style="font-size: 12px;">
|
||||||
<div class="d-flex justify-content-between mb-2"><span>合约面值</span><span class="text-white">100 USDT</span></div>
|
<div class="d-flex justify-content-between mb-2"><span>合约面值</span><span class="text-white">100 USDT / 张</span></div>
|
||||||
<div class="d-flex justify-content-between mb-2"><span>当前杠杆</span><span class="text-warning" id="lev-val">20x</span></div>
|
<div class="d-flex justify-content-between mb-2"><span>当前杠杆</span><span class="text-warning" id="lev-val">20x</span></div>
|
||||||
<div class="d-flex justify-content-between mb-2"><span>预计手续费</span><span class="text-white">0.05%</span></div>
|
<div class="d-flex justify-content-between mb-2"><span>预计手续费</span><span class="text-white">0.05%</span></div>
|
||||||
<div class="d-flex justify-content-between"><span>维持保证金</span><span class="text-white">0.4%</span></div>
|
<div class="d-flex justify-content-between"><span>维持保证金率</span><span class="text-white">0.4%</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Positions / Orders Tabs -->
|
||||||
|
<div class="glass-card p-3" style="border-radius: 4px; border: 1px solid #2b3139; background: #161a1e;">
|
||||||
|
<ul class="nav nav-tabs border-0 mb-3" id="tradeTabs" role="tablist">
|
||||||
|
<li class="nav-item">
|
||||||
|
<button class="nav-link active bg-transparent text-white border-0 fw-bold border-bottom border-warning border-3" id="positions-tab" data-bs-toggle="tab" data-bs-target="#positions" type="button">当前持仓/订单</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane fade show active" id="positions">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-dark table-sm align-middle mb-0" style="font-size: 12px;">
|
||||||
|
<thead>
|
||||||
|
<tr class="text-secondary">
|
||||||
|
<th>合约/现货</th>
|
||||||
|
<th>方向</th>
|
||||||
|
<th>数量/手数</th>
|
||||||
|
<th>开仓/委托价</th>
|
||||||
|
<th>当前价</th>
|
||||||
|
<th>保证金</th>
|
||||||
|
<th>盈亏</th>
|
||||||
|
<th class="text-end">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% if trade_type == 'CONTRACT' %}
|
||||||
|
{% for p in account.positions.all %}
|
||||||
|
{% if p.is_active %}
|
||||||
|
<tr>
|
||||||
|
<td class="fw-bold">{{ p.symbol }} {{ p.leverage }}x</td>
|
||||||
|
<td class="{% if p.side == 'LONG' %}text-success{% else %}text-danger{% endif %}">{{ p.get_side_display }}</td>
|
||||||
|
<td>{{ p.lots }}</td>
|
||||||
|
<td>{{ p.entry_price }}</td>
|
||||||
|
<td class="current-p-val" data-symbol="{{ p.symbol }}">--</td>
|
||||||
|
<td>{{ p.margin|floatformat:2 }}</td>
|
||||||
|
<td class="upl-val" data-entry="{{ p.entry_price }}" data-side="{{ p.side }}" data-lots="{{ p.lots }}">--</td>
|
||||||
|
<td class="text-end"><button class="btn btn-outline-danger btn-sm py-0" onclick="closePos({{ p.id }})">平仓</button></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% for o in account.orders.all %}
|
||||||
|
{% if o.status == 'PENDING' %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ o.symbol }} ({{ o.get_trade_type_display }})</td>
|
||||||
|
<td class="{% if o.side == 'BUY' %}text-success{% else %}text-danger{% endif %}">{{ o.get_side_display }}</td>
|
||||||
|
<td>{{ o.amount }}</td>
|
||||||
|
<td>{{ o.price|default:"市价" }}</td>
|
||||||
|
<td class="current-p-val" data-symbol="{{ o.symbol }}">--</td>
|
||||||
|
<td>--</td>
|
||||||
|
<td>等待成交</td>
|
||||||
|
<td class="text-end"><button class="btn btn-outline-secondary btn-sm py-0">撤单</button></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right: Order Book -->
|
<!-- Right: Order Book -->
|
||||||
@ -202,7 +266,7 @@
|
|||||||
<div class="glass-card h-100 p-2" style="border-radius: 4px; border: 1px solid #2b3139; background: #161a1e;">
|
<div class="glass-card h-100 p-2" style="border-radius: 4px; border: 1px solid #2b3139; background: #161a1e;">
|
||||||
<div class="d-flex justify-content-between text-secondary x-small mb-2 px-2">
|
<div class="d-flex justify-content-between text-secondary x-small mb-2 px-2">
|
||||||
<span>价格(USDT)</span>
|
<span>价格(USDT)</span>
|
||||||
<span>数量({{ symbol|slice:":-4" }})</span>
|
<span>数量({{ base_symbol }})</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="asks" class="mb-2"></div>
|
<div id="asks" class="mb-2"></div>
|
||||||
<div class="py-2 text-center border-top border-bottom border-secondary my-2">
|
<div class="py-2 text-center border-top border-bottom border-secondary my-2">
|
||||||
@ -216,7 +280,7 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.x-small { font-size: 11px; }
|
.x-small { font-size: 11px; }
|
||||||
.coin-icon-small { width: 18px; height: 18px; margin-right: 6px; }
|
.coin-icon-small { width: 18px; height: 18px; margin-right: 8px; vertical-align: middle; border-radius: 50%; }
|
||||||
.order-book-row { display: flex; justify-content: space-between; font-size: 11px; padding: 2px 4px; position: relative; }
|
.order-book-row { display: flex; justify-content: space-between; font-size: 11px; padding: 2px 4px; position: relative; }
|
||||||
.order-book-row span { position: relative; z-index: 1; }
|
.order-book-row span { position: relative; z-index: 1; }
|
||||||
.ask-bg { position: absolute; right: 0; top: 0; bottom: 0; background: rgba(246, 70, 93, 0.15); transition: width 0.3s; }
|
.ask-bg { position: absolute; right: 0; top: 0; bottom: 0; background: rgba(246, 70, 93, 0.15); transition: width 0.3s; }
|
||||||
@ -225,6 +289,7 @@
|
|||||||
.custom-range-danger::-webkit-slider-thumb { background: #f6465d; cursor: pointer; }
|
.custom-range-danger::-webkit-slider-thumb { background: #f6465d; cursor: pointer; }
|
||||||
.coin-list-container::-webkit-scrollbar { width: 4px; }
|
.coin-list-container::-webkit-scrollbar { width: 4px; }
|
||||||
.coin-list-container::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 2px; }
|
.coin-list-container::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 2px; }
|
||||||
|
.nav-tabs .nav-link:hover { color: #fcd535 !important; }
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -234,6 +299,9 @@
|
|||||||
const symbol = '{{ symbol }}';
|
const symbol = '{{ symbol }}';
|
||||||
const tradeType = '{{ trade_type }}';
|
const tradeType = '{{ trade_type }}';
|
||||||
const balance = parseFloat("{{ account.balance|default:0 }}");
|
const balance = parseFloat("{{ account.balance|default:0 }}");
|
||||||
|
const baseAsset = '{{ base_symbol }}';
|
||||||
|
const assetBalance = parseFloat("{{ base_asset_balance|default:0 }}");
|
||||||
|
|
||||||
let currentPrice = 0;
|
let currentPrice = 0;
|
||||||
let leverage = 20;
|
let leverage = 20;
|
||||||
let allCoins = [];
|
let allCoins = [];
|
||||||
@ -250,7 +318,7 @@
|
|||||||
try {
|
try {
|
||||||
const r = await fetch('https://api.binance.com/api/v3/ticker/24hr');
|
const r = await fetch('https://api.binance.com/api/v3/ticker/24hr');
|
||||||
const data = await r.json();
|
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();
|
renderCoins();
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
@ -264,14 +332,15 @@
|
|||||||
if (filter && !c.symbol.toLowerCase().includes(filter.toLowerCase())) return;
|
if (filter && !c.symbol.toLowerCase().includes(filter.toLowerCase())) return;
|
||||||
const base = c.symbol.replace('USDT', '');
|
const base = c.symbol.replace('USDT', '');
|
||||||
const chg = parseFloat(c.priceChangePercent);
|
const chg = parseFloat(c.priceChangePercent);
|
||||||
|
const iconUrl = `https://static.okx.com/cdn/oksupport/asset/currency/icon/${base.toLowerCase()}.png`;
|
||||||
const row = `
|
const row = `
|
||||||
<tr style="cursor: pointer;" onclick="location.href='?symbol=${c.symbol}'">
|
<tr style="cursor: pointer;" onclick="location.href='?symbol=${c.symbol}'">
|
||||||
<td>${base}</td>
|
<td><img src="${iconUrl}" class="coin-icon-small" onerror="this.src='https://static.okx.com/cdn/oksupport/asset/currency/icon/generic.png'">${base}</td>
|
||||||
<td class="text-end fw-bold">${parseFloat(c.lastPrice).toFixed(2)}</td>
|
<td class="text-end fw-bold">${parseFloat(c.lastPrice).toLocaleString()}</td>
|
||||||
<td class="text-end ${chg>=0?'text-success':'text-danger'}">${chg>=0?'+':''}${chg.toFixed(2)}%</td>
|
<td class="text-end ${chg>=0?'text-success':'text-danger'}">${chg>=0?'+':''}${chg.toFixed(2)}%</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
list.innerHTML += row;
|
list.innerHTML += row;
|
||||||
mobileList.innerHTML += `<li><a class="dropdown-item d-flex justify-content-between" href="?symbol=${c.symbol}"><span>${base}</span><span class="${chg>=0?'text-success':'text-danger'}">${parseFloat(c.lastPrice).toFixed(2)}</span></a></li>`;
|
mobileList.innerHTML += `<li><a class="dropdown-item d-flex justify-content-between" href="?symbol=${c.symbol}"><span><img src="${iconUrl}" class="coin-icon-small" onerror="this.src='https://static.okx.com/cdn/oksupport/asset/currency/icon/generic.png'">${base}</span><span class="${chg>=0?'text-success':'text-danger'}">${parseFloat(c.lastPrice).toLocaleString()}</span></a></li>`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,32 +349,90 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggleMode(side, mode) {
|
function toggleMode(side, mode) {
|
||||||
const pInput = document.getElementById(side === 'contract' ? 'c-price' : side + '-price');
|
if (side === 'buy') {
|
||||||
if (mode === 'market') {
|
const pInput = document.getElementById('buy-price');
|
||||||
pInput.value = '市价价格';
|
const label = document.getElementById('buy-label');
|
||||||
pInput.disabled = true;
|
const unit = document.getElementById('buy-unit');
|
||||||
pInput.style.color = '#f0b90b';
|
if (mode === 'market') {
|
||||||
} else {
|
pInput.value = '市价价格';
|
||||||
pInput.value = currentPrice || '';
|
pInput.disabled = true;
|
||||||
pInput.disabled = false;
|
pInput.style.color = '#f0b90b';
|
||||||
pInput.style.color = 'white';
|
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) {
|
function applySlider(side) {
|
||||||
const val = document.getElementById(side + '-slider').value;
|
const val = document.getElementById(side + '-slider').value;
|
||||||
if (side === 'buy') {
|
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') {
|
} else if (side === 'sell') {
|
||||||
// Simulated 1.5 BTC for testing
|
document.getElementById('sell-amount').value = (assetBalance * (val / 100)).toFixed(4);
|
||||||
document.getElementById('sell-amount').value = (1.5 * (val / 100)).toFixed(4);
|
|
||||||
} else if (side === 'contract') {
|
} else if (side === 'contract') {
|
||||||
const margin = (balance * (val / 100));
|
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);
|
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) {
|
function setLev(l) {
|
||||||
leverage = l;
|
leverage = l;
|
||||||
document.getElementById('lev-btn').textContent = '杠杆: ' + l + 'x';
|
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-change').className = 'fw-bold ' + (chg>=0?'text-success':'text-danger');
|
||||||
document.getElementById('header-volume').textContent = (parseFloat(d.quoteVolume)/1000000).toFixed(2) + 'M';
|
document.getElementById('header-volume').textContent = (parseFloat(d.quoteVolume)/1000000).toFixed(2) + 'M';
|
||||||
|
|
||||||
// Order Book
|
|
||||||
const askD = document.getElementById('asks'); const bidD = document.getElementById('bids');
|
const askD = document.getElementById('asks'); const bidD = document.getElementById('bids');
|
||||||
askD.innerHTML = ''; bidD.innerHTML = '';
|
askD.innerHTML = ''; bidD.innerHTML = '';
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
@ -336,18 +462,72 @@
|
|||||||
bidD.innerHTML += `<div class="order-book-row"><span class="text-success">${(currentPrice-(i+1)*0.1).toFixed(2)}</span><span>${(Math.random()*1.2).toFixed(4)}</span><div class="bid-bg" style="width:${Math.random()*70}%"></div></div>`;
|
bidD.innerHTML += `<div class="order-book-row"><span class="text-success">${(currentPrice-(i+1)*0.1).toFixed(2)}</span><span>${(Math.random()*1.2).toFixed(4)}</span><div class="bid-bg" style="width:${Math.random()*70}%"></div></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync limit inputs if empty
|
|
||||||
if (tradeType === 'SPOT') {
|
if (tradeType === 'SPOT') {
|
||||||
if (!document.getElementById('buy-price').value && !document.getElementById('buy-price').disabled) document.getElementById('buy-price').value = currentPrice;
|
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;
|
if (!document.getElementById('sell-price').value && !document.getElementById('sell-price').disabled) document.getElementById('sell-price').value = currentPrice;
|
||||||
} else {
|
} else {
|
||||||
if (!document.getElementById('c-price').value && !document.getElementById('c-price').disabled) document.getElementById('c-price').value = currentPrice;
|
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) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitOrder(side) {
|
async function submitOrder(side) {
|
||||||
alert('订单已提交,正在匹配中...');
|
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();
|
initTV(); getMarkets(); tick();
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
app_name = 'core'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.index, name='index'),
|
path('', views.index, name='index'),
|
||||||
|
path('trade/<str:trade_type>/', views.trade, name='trade'),
|
||||||
path('spot/', views.trade, {'trade_type': 'spot'}, name='spot_trade'),
|
path('spot/', views.trade, {'trade_type': 'spot'}, name='spot_trade'),
|
||||||
path('contract/', views.trade, {'trade_type': 'contract'}, name='contract_trade'),
|
path('contract/', views.trade, {'trade_type': 'contract'}, name='contract_trade'),
|
||||||
path('markets/', views.market_center, name='market_center'),
|
path('markets/', views.market_center, name='market_center'),
|
||||||
@ -14,6 +17,10 @@ urlpatterns = [
|
|||||||
path('register/', views.register_view, name='register'),
|
path('register/', views.register_view, name='register'),
|
||||||
path('api/market_data/', views.market_data, name='market_data'),
|
path('api/market_data/', views.market_data, name='market_data'),
|
||||||
path('api/submit_order/', views.submit_order, name='submit_order'),
|
path('api/submit_order/', views.submit_order, name='submit_order'),
|
||||||
|
path('api/close_position/<int:position_id>/', views.close_position, name='close_position'),
|
||||||
|
|
||||||
|
# Dynamic Article/Placeholder Route
|
||||||
|
path('article/<str:title>/', views.placeholder_view, name='placeholder'),
|
||||||
|
|
||||||
# Footer links
|
# Footer links
|
||||||
path('help/', views.placeholder_view, {'title': '帮助中心'}, name='help_center'),
|
path('help/', views.placeholder_view, {'title': '帮助中心'}, name='help_center'),
|
||||||
|
|||||||
286
core/views.py
286
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.decorators import login_required
|
||||||
from django.contrib.auth import login, authenticate
|
from django.contrib.auth import login, authenticate
|
||||||
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
|
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib import messages
|
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 random
|
||||||
import decimal
|
import decimal
|
||||||
import json
|
import json
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
return render(request, 'core/index.html')
|
return render(request, 'core/index.html')
|
||||||
|
|
||||||
def trade(request, trade_type='spot'):
|
def trade(request, trade_type='spot'):
|
||||||
symbol = request.GET.get('symbol', 'BTCUSDT')
|
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)
|
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 = {
|
context = {
|
||||||
'symbol': symbol,
|
'symbol': symbol,
|
||||||
|
'base_symbol': base_symbol,
|
||||||
'trade_type': trade_type.upper(),
|
'trade_type': trade_type.upper(),
|
||||||
'cryptos': cryptos,
|
'cryptos': cryptos,
|
||||||
|
'account': account,
|
||||||
|
'assets': assets,
|
||||||
|
'base_asset_balance': base_asset_balance,
|
||||||
}
|
}
|
||||||
return render(request, 'core/trade.html', context)
|
return render(request, 'core/trade.html', context)
|
||||||
|
|
||||||
def market_center(request):
|
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):
|
def placeholder_view(request, title):
|
||||||
settings = SiteSettings.objects.first()
|
settings = SiteSettings.objects.first()
|
||||||
@ -61,10 +78,12 @@ def profile(request):
|
|||||||
account, created = Account.objects.get_or_create(user=request.user)
|
account, created = Account.objects.get_or_create(user=request.user)
|
||||||
recent_transactions = Transaction.objects.filter(account=account).order_by('-timestamp')[:10]
|
recent_transactions = Transaction.objects.filter(account=account).order_by('-timestamp')[:10]
|
||||||
recent_orders = Order.objects.filter(account=account).order_by('-created_at')[:10]
|
recent_orders = Order.objects.filter(account=account).order_by('-created_at')[:10]
|
||||||
|
positions = Position.objects.filter(account=account, is_active=True)
|
||||||
context = {
|
context = {
|
||||||
'account': account,
|
'account': account,
|
||||||
'recent_transactions': recent_transactions,
|
'recent_transactions': recent_transactions,
|
||||||
'recent_orders': recent_orders,
|
'recent_orders': recent_orders,
|
||||||
|
'positions': positions,
|
||||||
}
|
}
|
||||||
return render(request, 'core/profile.html', context)
|
return render(request, 'core/profile.html', context)
|
||||||
|
|
||||||
@ -82,7 +101,7 @@ def deposit(request):
|
|||||||
status='pending',
|
status='pending',
|
||||||
tx_hash=tx_id
|
tx_hash=tx_id
|
||||||
)
|
)
|
||||||
return redirect('profile')
|
return redirect('core:profile')
|
||||||
return render(request, 'core/deposit.html')
|
return render(request, 'core/deposit.html')
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -103,7 +122,7 @@ def withdraw(request):
|
|||||||
status='completed',
|
status='completed',
|
||||||
tx_hash=f"wd_{random.randint(1000, 9999)}"
|
tx_hash=f"wd_{random.randint(1000, 9999)}"
|
||||||
)
|
)
|
||||||
return redirect('profile')
|
return redirect('core:profile')
|
||||||
return render(request, 'core/withdraw.html', {'account': account})
|
return render(request, 'core/withdraw.html', {'account': account})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -112,21 +131,9 @@ def verify(request):
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
account.kyc_status = 'pending'
|
account.kyc_status = 'pending'
|
||||||
account.save()
|
account.save()
|
||||||
return redirect('profile')
|
return redirect('core:profile')
|
||||||
return render(request, 'core/verify.html', {'account': account})
|
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):
|
def register_view(request):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
username = request.POST.get('username')
|
username = request.POST.get('username')
|
||||||
@ -145,9 +152,8 @@ def register_view(request):
|
|||||||
user = User.objects.create_user(username=username, password=password)
|
user = User.objects.create_user(username=username, password=password)
|
||||||
Account.objects.get_or_create(user=user)
|
Account.objects.get_or_create(user=user)
|
||||||
login(request, user)
|
login(request, user)
|
||||||
return redirect('index')
|
return redirect('core:index')
|
||||||
|
|
||||||
# Generate new captcha
|
|
||||||
captcha_text, captcha_result = generate_captcha()
|
captcha_text, captcha_result = generate_captcha()
|
||||||
request.session['captcha_result'] = captcha_result
|
request.session['captcha_result'] = captcha_result
|
||||||
|
|
||||||
@ -155,13 +161,22 @@ def register_view(request):
|
|||||||
'captcha_text': captcha_text
|
'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):
|
def login_view(request):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = AuthenticationForm(data=request.POST)
|
form = AuthenticationForm(data=request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
user = form.get_user()
|
user = form.get_user()
|
||||||
login(request, user)
|
login(request, user)
|
||||||
return redirect('index')
|
return redirect('core:index')
|
||||||
else:
|
else:
|
||||||
form = AuthenticationForm()
|
form = AuthenticationForm()
|
||||||
return render(request, 'core/login.html', {'form': form})
|
return render(request, 'core/login.html', {'form': form})
|
||||||
@ -169,76 +184,181 @@ def login_view(request):
|
|||||||
@login_required
|
@login_required
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def submit_order(request):
|
def submit_order(request):
|
||||||
if request.method == 'POST':
|
if request.method != 'POST':
|
||||||
try:
|
return JsonResponse({'status': 'error', 'message': 'Invalid request'})
|
||||||
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')
|
|
||||||
|
|
||||||
account = request.user.account
|
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))
|
||||||
|
|
||||||
# Fetch current price from DB or simulated
|
account = request.user.account
|
||||||
crypto = Cryptocurrency.objects.filter(symbol=symbol.replace('USDT', '')).first()
|
base_symbol = symbol.replace('USDT', '')
|
||||||
current_price = crypto.current_price if crypto else decimal.Decimal('48000')
|
|
||||||
|
|
||||||
# If market order, use current price. If limit, use provided price.
|
# Get current market price for validations and market orders
|
||||||
exec_price = decimal.Decimal(str(price)) if order_type == 'LIMIT' else current_price
|
crypto = Cryptocurrency.objects.filter(symbol=base_symbol).first()
|
||||||
|
current_price = crypto.current_price if (crypto and crypto.current_price > 0) else decimal.Decimal('48000')
|
||||||
|
|
||||||
if side == 'BUY':
|
with transaction.atomic():
|
||||||
cost = amount * exec_price
|
if trade_type == 'SPOT':
|
||||||
if account.balance >= cost:
|
if order_type == 'MARKET':
|
||||||
account.balance -= cost
|
# 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'
|
||||||
|
)
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
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()
|
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'})
|
|
||||||
|
|
||||||
except Exception as e:
|
Position.objects.create(
|
||||||
return JsonResponse({'status': 'error', 'message': str(e)})
|
account=account, symbol=symbol, side='LONG' if side == 'BUY' else 'SHORT',
|
||||||
return JsonResponse({'status': 'error', 'message': 'Invalid request'})
|
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):
|
def market_data(request):
|
||||||
# This might be used by some local scripts, though index/trade use Binance API directly
|
cryptos = Cryptocurrency.objects.filter(is_active=True)
|
||||||
symbols = ['BTC', 'ETH', 'BNB', 'SOL', 'ADA', 'XRP', 'DOT', 'DOGE']
|
|
||||||
data = []
|
data = []
|
||||||
for s in symbols:
|
for c in cryptos:
|
||||||
price = random.uniform(10, 60000)
|
|
||||||
change = random.uniform(-5, 5)
|
|
||||||
data.append({
|
data.append({
|
||||||
'symbol': s,
|
'symbol': c.symbol,
|
||||||
'price': round(price, 4),
|
'price': float(c.current_price),
|
||||||
'change': round(change, 2)
|
'change': float(c.change_24h)
|
||||||
})
|
})
|
||||||
return JsonResponse(data, safe=False)
|
return JsonResponse(data, safe=False)
|
||||||
Loading…
x
Reference in New Issue
Block a user