Files
stock/stock-html/routes/trades.py
T
freedakgmail 9c7d7abdd4 Initial commit
2026-07-17 18:49:35 +08:00

314 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
交易记录 API 路由(纯数据库版)
"""
from flask import Blueprint, request, jsonify, session
from db import (
login_required, get_current_user_id,
db_get_trades, db_get_trade, db_add_trade, db_update_trade, db_delete_trade,
db_get_available_cash, db_update_available_cash
)
bp = Blueprint('trades', __name__, url_prefix='/api')
@bp.route('/trades', methods=['GET'])
@login_required
def get_trades():
"""获取交易记录"""
user_id = get_current_user_id()
trades = db_get_trades(user_id)
return jsonify({'success': True, 'trades': trades})
@bp.route('/trades', methods=['POST'])
@login_required
def add_trade():
"""添加交易记录"""
try:
user_id = get_current_user_id()
data = request.get_json()
# 处理数值(空字符串转为None
def parse_float(val):
if val is None or val == '':
return None
try:
return round(float(val), 4)
except:
return None
def parse_int(val):
if val is None or val == '':
return None
try:
return int(val)
except:
return None
data['price'] = parse_float(data.get('price'))
data['quantity'] = parse_int(data.get('quantity'))
data['profit_amount'] = parse_float(data.get('profit_amount'))
data['stop_loss_price'] = parse_float(data.get('stop_loss_price'))
trade, error = db_add_trade(user_id, data)
if error:
return jsonify({'success': False, 'error': error}), 400
# 根据交易类型自动更新可用资金
trade_type = (data.get('trade_type') or '').lower()
price = data.get('price')
quantity = data.get('quantity')
if trade_type in ('buy', 'sell') and price is not None and quantity is not None:
amount = round(float(price) * int(quantity), 2)
current = db_get_available_cash(user_id)
if trade_type == 'buy':
new_cash = round(current - amount, 2)
else:
new_cash = round(current + amount, 2)
if new_cash < 0:
new_cash = 0
ok, _ = db_update_available_cash(user_id, new_cash)
if ok:
return jsonify({'success': True, 'trade': trade, 'available_cash': new_cash})
return jsonify({'success': True, 'trade': trade})
except Exception as e:
return jsonify({'error': str(e)}), 400
@bp.route('/trades/<int:trade_id>', methods=['PUT'])
@login_required
def update_trade(trade_id):
"""更新交易记录"""
try:
user_id = get_current_user_id()
data = request.get_json()
# 处理数值(空字符串转为None
def parse_float(val):
if val is None or val == '':
return None
try:
return round(float(val), 4)
except:
return None
def parse_int(val):
if val is None or val == '':
return None
try:
return int(val)
except:
return None
if 'price' in data:
data['price'] = parse_float(data.get('price'))
if 'quantity' in data:
data['quantity'] = parse_int(data.get('quantity'))
if 'profit_amount' in data:
data['profit_amount'] = parse_float(data.get('profit_amount'))
if 'stop_loss_price' in data:
data['stop_loss_price'] = parse_float(data.get('stop_loss_price'))
old_trade = db_get_trade(user_id, trade_id)
if not old_trade:
return jsonify({'error': '交易记录不存在'}), 404
trade, error = db_update_trade(user_id, trade_id, data)
if error:
return jsonify({'success': False, 'error': error}), 400
if not trade:
return jsonify({'error': '交易记录不存在'}), 404
# 根据修改同步调整可用资金:先回滚旧交易,再应用新交易
def trade_amount(t):
p, q = (t.get('price') or 0), (t.get('quantity') or 0)
return round(float(p) * int(q), 2) if p and q else 0
old_amt = trade_amount(old_trade)
new_amt = trade_amount(trade)
old_type = (old_trade.get('trade_type') or '').lower()
new_type = (trade.get('trade_type') or '').lower()
delta = 0
if old_type == 'buy':
delta += old_amt
elif old_type == 'sell':
delta -= old_amt
if new_type == 'buy':
delta -= new_amt
elif new_type == 'sell':
delta += new_amt
if delta != 0:
current = db_get_available_cash(user_id)
new_cash = max(0, round(current + delta, 2))
ok, _ = db_update_available_cash(user_id, new_cash)
if ok:
return jsonify({'success': True, 'trade': trade, 'available_cash': new_cash})
return jsonify({'success': True, 'trade': trade})
except Exception as e:
return jsonify({'error': str(e)}), 400
@bp.route('/trades/<int:trade_id>', methods=['DELETE'])
@login_required
def delete_trade(trade_id):
"""删除交易记录"""
user_id = get_current_user_id()
old_trade = db_get_trade(user_id, trade_id)
if not old_trade:
return jsonify({'success': False, 'error': '交易记录不存在'}), 404
success = db_delete_trade(user_id, trade_id)
if not success:
return jsonify({'success': False}), 400
# 回滚该交易对可用资金的影响
t_type = (old_trade.get('trade_type') or '').lower()
amount = round(float(old_trade.get('price') or 0) * int(old_trade.get('quantity') or 0), 2)
delta = amount if t_type == 'buy' else -amount
if delta != 0:
current = db_get_available_cash(user_id)
new_cash = max(0, round(current + delta, 2))
db_update_available_cash(user_id, new_cash)
return jsonify({'success': True, 'available_cash': new_cash})
return jsonify({'success': True})
@bp.route('/available_cash', methods=['GET'])
@login_required
def get_available_cash():
"""获取可用资金"""
user_id = get_current_user_id()
cash = db_get_available_cash(user_id)
return jsonify({'success': True, 'available_cash': cash})
@bp.route('/available_cash', methods=['PUT'])
@login_required
def update_available_cash():
"""更新可用资金"""
try:
user_id = get_current_user_id()
data = request.get_json()
amount = data.get('amount')
if amount is None:
return jsonify({'success': False, 'error': '金额不能为空'}), 400
try:
amount = round(float(amount), 2)
except:
return jsonify({'success': False, 'error': '金额格式错误'}), 400
success, error = db_update_available_cash(user_id, amount)
if error:
return jsonify({'success': False, 'error': error}), 400
return jsonify({'success': True, 'available_cash': amount})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 400
@bp.route('/stoploss_check', methods=['GET'])
@login_required
def check_stoploss():
"""检查止损线触发情况"""
try:
user_id = get_current_user_id()
trades = db_get_trades(user_id)
# 计算每只股票的持仓情况
holdings = {}
for trade in trades:
code = trade.get('stock_code')
if not code:
continue
if code not in holdings:
holdings[code] = {
'code': code,
'name': trade.get('stock_name', code),
'total_cost': 0,
'total_quantity': 0,
'stop_loss_price': trade.get('stop_loss_price'),
}
trade_type = trade.get('trade_type')
quantity = int(trade.get('quantity', 0) or 0)
price = float(trade.get('price', 0) or 0)
if trade_type == 'buy':
holdings[code]['total_cost'] += price * quantity
holdings[code]['total_quantity'] += quantity
elif trade_type == 'sell':
holdings[code]['total_quantity'] -= quantity
if holdings[code]['total_quantity'] > 0:
cost_per_share = holdings[code]['total_cost'] / (holdings[code]['total_quantity'] + quantity)
holdings[code]['total_cost'] -= cost_per_share * quantity
if trade.get('stop_loss_price'):
holdings[code]['stop_loss_price'] = trade.get('stop_loss_price')
# 只保留有持仓的股票
active_holdings = {k: v for k, v in holdings.items() if v['total_quantity'] > 0}
# 获取实时价格并检查止损
alerts = []
try:
for code, holding in active_holdings.items():
try:
# 优先使用腾讯财经API(兼容腾讯云)
current_price = 0
try:
import requests as _rq
_tc = ('sh' if code.startswith('6') else 'sz') + code
_rr = _rq.get(f'http://qt.gtimg.cn/q={_tc}', timeout=5,
headers={'Referer': 'https://finance.qq.com'})
if _rr.status_code == 200 and '\"' in _rr.text:
_ff = _rr.text.split('\"')[1].split('~')
if len(_ff) > 3 and _ff[3]:
current_price = float(_ff[3])
except Exception:
pass
if current_price > 0:
stop_loss_price = holding.get('stop_loss_price')
avg_cost = float(holding['total_cost']) / holding['total_quantity'] if holding['total_quantity'] > 0 else 0.0
profit_loss = (current_price - avg_cost) * holding['total_quantity']
profit_percent = ((current_price - avg_cost) / avg_cost * 100) if avg_cost > 0 else 0
alert_data = {
'code': code,
'name': holding['name'],
'current_price': current_price,
'avg_cost': round(avg_cost, 4),
'quantity': holding['total_quantity'],
'profit_loss': round(profit_loss, 2),
'profit_percent': round(profit_percent, 2),
'stop_loss_price': stop_loss_price,
'triggered': False
}
if stop_loss_price and current_price <= stop_loss_price:
alert_data['triggered'] = True
alert_data['alert_type'] = 'stop_loss'
alert_data['message'] = f"⚠️ {holding['name']} 触发止损!"
elif profit_percent <= -5:
alert_data['triggered'] = True
alert_data['alert_type'] = 'default_stop'
alert_data['message'] = f"⚠️ {holding['name']} 跌破成本5%"
alerts.append(alert_data)
except Exception as e:
print(f"获取 {code} 价格失败: {e}")
except Exception as e:
print(f"获取实时行情失败: {e}")
return jsonify({
'success': True,
'holdings': list(active_holdings.values()),
'alerts': [a for a in alerts if a.get('triggered')],
'all_positions': alerts
})
except Exception as e:
print(f"止损检查错误: {e}")
return jsonify({'error': str(e)}), 500