364 lines
16 KiB
Python
364 lines
16 KiB
Python
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, 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')
|
|
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):
|
|
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()
|
|
|
|
if title == '服务条款':
|
|
content = settings.terms_content if settings and settings.terms_content else '暂无服务条款内容。'
|
|
elif title == '隐私政策':
|
|
content = settings.privacy_content if settings and settings.privacy_content else '暂无隐私政策内容。'
|
|
else:
|
|
contents = {
|
|
'帮助中心': '欢迎来到 BitCrypto 帮助中心。在这里您可以找到关于账户设置、资产充提、交易指南等所有问题的答案。我们为您准备了详尽的视频教程和图文说明,帮助您快速上手。',
|
|
'技术支持': 'BitCrypto 技术支持团队 24/7 在线。如果您遇到任何 API 对接、系统报错或连接问题,请随时联系我们的工程师。我们承诺在 15 分钟内给予首次回复。',
|
|
'提交请求': '请在下方表单提交您的需求或反馈。无论是工单申请、投诉建议还是商务合作,我们都会认真对待。您的每一份反馈都是我们前进的动力。',
|
|
'公告中心': '查看 BitCrypto 最新动态。包括新币上线通知、系统维护公告、市场活动资讯等。订阅我们的邮件列表,第一时间获取核心商业情报。',
|
|
}
|
|
content = contents.get(title, f'这是{title}的详细内容。BitCrypto为您提供最优质的服务。')
|
|
|
|
faqs = [
|
|
{'q': '如何进行身份认证?', 'a': '登录后在个人中心点击身份认证,上传身份证件并完成人脸识别即可。'},
|
|
{'q': '充值多久能到账?', 'a': '区块链网络确认后自动到账,通常 5-30 分钟。'},
|
|
{'q': '手续费是多少?', 'a': '现货交易基础手续费为 0.1%,使用平台币抵扣可享 7.5 折优惠。'},
|
|
]
|
|
|
|
return render(request, 'core/article_detail.html', {
|
|
'title': title,
|
|
'content': content,
|
|
'faqs': faqs if title == '帮助中心' else None
|
|
})
|
|
|
|
@login_required
|
|
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)
|
|
|
|
@login_required
|
|
def deposit(request):
|
|
if request.method == 'POST':
|
|
amount = request.POST.get('amount')
|
|
tx_id = request.POST.get('tx_id')
|
|
if amount and tx_id:
|
|
account = request.user.account
|
|
Transaction.objects.create(
|
|
account=account,
|
|
transaction_type='deposit',
|
|
amount=decimal.Decimal(amount),
|
|
status='pending',
|
|
tx_hash=tx_id
|
|
)
|
|
return redirect('core:profile')
|
|
return render(request, 'core/deposit.html')
|
|
|
|
@login_required
|
|
def withdraw(request):
|
|
account = request.user.account
|
|
if request.method == 'POST':
|
|
amount = request.POST.get('amount')
|
|
address = request.POST.get('address')
|
|
if amount and address:
|
|
amount_dec = decimal.Decimal(amount)
|
|
if account.balance >= amount_dec:
|
|
account.balance -= amount_dec
|
|
account.save()
|
|
Transaction.objects.create(
|
|
account=account,
|
|
transaction_type='withdraw',
|
|
amount=amount_dec,
|
|
status='completed',
|
|
tx_hash=f"wd_{random.randint(1000, 9999)}"
|
|
)
|
|
return redirect('core:profile')
|
|
return render(request, 'core/withdraw.html', {'account': account})
|
|
|
|
@login_required
|
|
def verify(request):
|
|
account = request.user.account
|
|
if request.method == 'POST':
|
|
account.kyc_status = 'pending'
|
|
account.save()
|
|
return redirect('core:profile')
|
|
return render(request, 'core/verify.html', {'account': account})
|
|
|
|
def register_view(request):
|
|
if request.method == 'POST':
|
|
username = request.POST.get('username')
|
|
password = request.POST.get('password')
|
|
password_confirm = request.POST.get('password_confirm')
|
|
captcha_input = request.POST.get('captcha_input')
|
|
captcha_expected = request.session.get('captcha_result')
|
|
|
|
if password != password_confirm:
|
|
messages.error(request, "两次输入的密码不一致")
|
|
elif str(captcha_input) != str(captcha_expected):
|
|
messages.error(request, "验证码错误")
|
|
elif User.objects.filter(username=username).exists():
|
|
messages.error(request, "用户名已存在")
|
|
else:
|
|
user = User.objects.create_user(username=username, password=password)
|
|
Account.objects.get_or_create(user=user)
|
|
login(request, user)
|
|
return redirect('core:index')
|
|
|
|
captcha_text, captcha_result = generate_captcha()
|
|
request.session['captcha_result'] = captcha_result
|
|
|
|
return render(request, 'core/register.html', {
|
|
'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('core:index')
|
|
else:
|
|
form = AuthenticationForm()
|
|
return render(request, 'core/login.html', {'form': form})
|
|
|
|
@login_required
|
|
@csrf_exempt
|
|
def submit_order(request):
|
|
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'
|
|
)
|
|
|
|
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()
|
|
|
|
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):
|
|
cryptos = Cryptocurrency.objects.filter(is_active=True)
|
|
data = []
|
|
for c in cryptos:
|
|
data.append({
|
|
'symbol': c.symbol,
|
|
'price': float(c.current_price),
|
|
'change': float(c.change_24h)
|
|
})
|
|
return JsonResponse(data, safe=False) |