""" 交易记录 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/', 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/', 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