Initial commit

This commit is contained in:
freedakgmail
2026-07-17 18:49:07 +08:00
commit 9c7d7abdd4
100 changed files with 41337 additions and 0 deletions
+313
View File
@@ -0,0 +1,313 @@
"""
交易记录 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