734 lines
28 KiB
Python
734 lines
28 KiB
Python
"""
|
|
模拟交易 API 路由
|
|
每个交易日10点根据推荐率最高的买入卖出方案进行自动操作
|
|
"""
|
|
from flask import Blueprint, request, jsonify
|
|
from datetime import datetime, date, time
|
|
from db import get_db, login_required, get_current_user_id
|
|
from psycopg2.extras import RealDictCursor
|
|
|
|
bp = Blueprint('sim_trade', __name__, url_prefix='/api/sim')
|
|
|
|
|
|
def init_user_config(user_id):
|
|
"""初始化用户模拟交易配置"""
|
|
conn = get_db()
|
|
if not conn:
|
|
return None
|
|
try:
|
|
cur = conn.cursor(cursor_factory=RealDictCursor)
|
|
cur.execute("""
|
|
INSERT INTO sim_config (user_id)
|
|
VALUES (%s)
|
|
ON CONFLICT (user_id) DO NOTHING
|
|
RETURNING *
|
|
""", (user_id,))
|
|
conn.commit()
|
|
|
|
# 获取配置
|
|
cur.execute("SELECT * FROM sim_config WHERE user_id = %s", (user_id,))
|
|
return cur.fetchone()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
@bp.route('/config', methods=['GET'])
|
|
@login_required
|
|
def get_config():
|
|
"""获取模拟交易配置"""
|
|
user_id = get_current_user_id()
|
|
config = init_user_config(user_id)
|
|
|
|
if config:
|
|
return jsonify({
|
|
'success': True,
|
|
'config': {
|
|
'initial_capital': float(config['initial_capital']),
|
|
'trade_quantity': config['trade_quantity'],
|
|
'auto_trade_enabled': config['auto_trade_enabled'],
|
|
'auto_trade_time': str(config['auto_trade_time']) if config['auto_trade_time'] else '10:00:00'
|
|
}
|
|
})
|
|
return jsonify({'success': False, 'error': '获取配置失败'}), 500
|
|
|
|
|
|
@bp.route('/config', methods=['POST'])
|
|
@login_required
|
|
def update_config():
|
|
"""更新模拟交易配置"""
|
|
user_id = get_current_user_id()
|
|
data = request.get_json()
|
|
|
|
conn = get_db()
|
|
if not conn:
|
|
return jsonify({'success': False, 'error': '数据库连接失败'}), 500
|
|
|
|
try:
|
|
cur = conn.cursor()
|
|
cur.execute("""
|
|
UPDATE sim_config SET
|
|
trade_quantity = COALESCE(%s, trade_quantity),
|
|
auto_trade_enabled = COALESCE(%s, auto_trade_enabled),
|
|
updated_at = NOW()
|
|
WHERE user_id = %s
|
|
""", (
|
|
data.get('trade_quantity'),
|
|
data.get('auto_trade_enabled'),
|
|
user_id
|
|
))
|
|
conn.commit()
|
|
return jsonify({'success': True})
|
|
except Exception as e:
|
|
conn.rollback()
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
@bp.route('/trades', methods=['GET'])
|
|
@login_required
|
|
def get_trades():
|
|
"""获取模拟交易记录"""
|
|
user_id = get_current_user_id()
|
|
limit = request.args.get('limit', 100, type=int)
|
|
|
|
conn = get_db()
|
|
if not conn:
|
|
return jsonify({'success': False, 'error': '数据库连接失败'}), 500
|
|
|
|
try:
|
|
cur = conn.cursor(cursor_factory=RealDictCursor)
|
|
cur.execute("""
|
|
SELECT id, stock_code, stock_name, trade_type,
|
|
price::float, quantity, trade_date::text,
|
|
trade_time::text, recommend_rate::float, signal_reason,
|
|
COALESCE(commission, 0)::float as commission,
|
|
COALESCE(stamp_tax, 0)::float as stamp_tax,
|
|
COALESCE(total_fee, 0)::float as total_fee,
|
|
created_at::text
|
|
FROM sim_trades
|
|
WHERE user_id = %s
|
|
ORDER BY trade_date DESC, trade_time DESC
|
|
LIMIT %s
|
|
""", (user_id, limit))
|
|
trades = cur.fetchall()
|
|
return jsonify({'success': True, 'trades': trades})
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
@bp.route('/positions', methods=['GET'])
|
|
@login_required
|
|
def get_positions():
|
|
"""获取模拟持仓"""
|
|
user_id = get_current_user_id()
|
|
|
|
conn = get_db()
|
|
if not conn:
|
|
return jsonify({'success': False, 'error': '数据库连接失败'}), 500
|
|
|
|
try:
|
|
cur = conn.cursor(cursor_factory=RealDictCursor)
|
|
cur.execute("""
|
|
SELECT stock_code, stock_name, quantity,
|
|
avg_cost::float, total_cost::float,
|
|
current_price::float, updated_at::text
|
|
FROM sim_positions
|
|
WHERE user_id = %s AND quantity > 0
|
|
ORDER BY total_cost DESC
|
|
""", (user_id,))
|
|
positions = cur.fetchall()
|
|
return jsonify({'success': True, 'positions': positions})
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
@bp.route('/stats', methods=['GET'])
|
|
@login_required
|
|
def get_stats():
|
|
"""获取模拟交易统计"""
|
|
user_id = get_current_user_id()
|
|
|
|
conn = get_db()
|
|
if not conn:
|
|
return jsonify({'success': False, 'error': '数据库连接失败'}), 500
|
|
|
|
try:
|
|
cur = conn.cursor(cursor_factory=RealDictCursor)
|
|
|
|
# 获取配置 — 优先使用 sim_algo_config.total_capital(智能交易配置)
|
|
initial_capital = 200000 # 默认值
|
|
cur.execute("SELECT total_capital::float FROM sim_algo_config WHERE user_id = %s AND is_active = TRUE", (user_id,))
|
|
algo_cfg = cur.fetchone()
|
|
if algo_cfg and algo_cfg['total_capital']:
|
|
initial_capital = algo_cfg['total_capital']
|
|
else:
|
|
cur.execute("SELECT initial_capital::float FROM sim_config WHERE user_id = %s", (user_id,))
|
|
config = cur.fetchone()
|
|
if config and config['initial_capital']:
|
|
initial_capital = config['initial_capital']
|
|
|
|
# 获取持仓统计
|
|
cur.execute("""
|
|
SELECT
|
|
COALESCE(SUM(quantity * current_price), 0)::float as total_market_value,
|
|
COALESCE(SUM(total_cost), 0)::float as total_cost,
|
|
COALESCE(SUM(quantity * current_price - total_cost), 0)::float as unrealized_profit
|
|
FROM sim_positions
|
|
WHERE user_id = %s AND quantity > 0
|
|
""", (user_id,))
|
|
position_stats = cur.fetchone()
|
|
|
|
# 获取已实现盈亏(卖出交易)
|
|
cur.execute("""
|
|
SELECT COALESCE(SUM(
|
|
CASE WHEN trade_type = 'sell' THEN price * quantity ELSE 0 END
|
|
), 0)::float as total_sell,
|
|
COUNT(DISTINCT trade_date) as trade_days,
|
|
COUNT(*) as total_trades,
|
|
COALESCE(SUM(COALESCE(commission, 0)), 0)::float as total_commission,
|
|
COALESCE(SUM(COALESCE(stamp_tax, 0)), 0)::float as total_stamp_tax,
|
|
COALESCE(SUM(COALESCE(total_fee, 0)), 0)::float as total_fees
|
|
FROM sim_trades
|
|
WHERE user_id = %s
|
|
""", (user_id,))
|
|
trade_stats = cur.fetchone()
|
|
|
|
# 计算已实现盈亏(需要更复杂的计算,这里简化处理)
|
|
# 从每日统计表获取最新的已实现盈亏
|
|
cur.execute("""
|
|
SELECT realized_profit::float
|
|
FROM sim_daily_stats
|
|
WHERE user_id = %s
|
|
ORDER BY stat_date DESC
|
|
LIMIT 1
|
|
""", (user_id,))
|
|
daily_stat = cur.fetchone()
|
|
realized_profit = daily_stat['realized_profit'] if daily_stat else 0
|
|
|
|
# 获取历史统计(用于图表)
|
|
cur.execute("""
|
|
SELECT stat_date::text, total_profit::float,
|
|
total_market_value::float, realized_profit::float
|
|
FROM sim_daily_stats
|
|
WHERE user_id = %s
|
|
ORDER BY stat_date DESC
|
|
LIMIT 30
|
|
""", (user_id,))
|
|
history = cur.fetchall()
|
|
|
|
total_market_value = position_stats['total_market_value'] or 0
|
|
total_cost = position_stats['total_cost'] or 0
|
|
unrealized_profit = position_stats['unrealized_profit'] or 0
|
|
total_fees = trade_stats['total_fees'] or 0
|
|
total_commission = trade_stats['total_commission'] or 0
|
|
total_stamp_tax = trade_stats['total_stamp_tax'] or 0
|
|
|
|
# 毛利润(不含手续费的计算)
|
|
gross_profit = unrealized_profit + realized_profit + total_fees # 加回手续费 = 毛收益
|
|
# 净利润(含手续费)
|
|
net_profit = unrealized_profit + realized_profit # realized_profit 已扣除卖出手续费
|
|
total_profit = net_profit
|
|
|
|
# 计算收益率
|
|
gross_rate = (gross_profit / initial_capital * 100) if initial_capital > 0 else 0
|
|
net_rate = (net_profit / initial_capital * 100) if initial_capital > 0 else 0
|
|
profit_rate = net_rate # 默认显示净收益率
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'stats': {
|
|
'initial_capital': initial_capital,
|
|
'total_market_value': total_market_value,
|
|
'total_cost': total_cost,
|
|
'cash': initial_capital - total_cost + realized_profit,
|
|
'unrealized_profit': unrealized_profit,
|
|
'realized_profit': realized_profit,
|
|
'total_profit': total_profit,
|
|
'profit_rate': profit_rate,
|
|
'trade_days': trade_stats['trade_days'] or 0,
|
|
'total_trades': trade_stats['total_trades'] or 0,
|
|
# 手续费明细
|
|
'total_fees': total_fees,
|
|
'total_commission': total_commission,
|
|
'total_stamp_tax': total_stamp_tax,
|
|
# 对比数据: 毛收益 vs 净收益
|
|
'gross_profit': gross_profit,
|
|
'gross_rate': gross_rate,
|
|
'net_profit': net_profit,
|
|
'net_rate': net_rate,
|
|
},
|
|
'history': list(reversed(history)) if history else []
|
|
})
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
@bp.route('/execute', methods=['POST'])
|
|
@login_required
|
|
def execute_trade():
|
|
"""执行模拟交易(手动或自动)"""
|
|
user_id = get_current_user_id()
|
|
data = request.get_json()
|
|
|
|
stock_code = data.get('stock_code')
|
|
stock_name = data.get('stock_name', '')
|
|
trade_type = data.get('trade_type') # 'buy' or 'sell'
|
|
price = data.get('price')
|
|
quantity = data.get('quantity', 1000)
|
|
recommend_rate = data.get('recommend_rate')
|
|
signal_reason = data.get('signal_reason', '')
|
|
|
|
if not stock_code or not trade_type or not price:
|
|
return jsonify({'success': False, 'error': '参数不完整'}), 400
|
|
|
|
conn = get_db()
|
|
if not conn:
|
|
return jsonify({'success': False, 'error': '数据库连接失败'}), 500
|
|
|
|
try:
|
|
cur = conn.cursor(cursor_factory=RealDictCursor)
|
|
today = date.today()
|
|
now = datetime.now().time()
|
|
|
|
# 1. 记录交易
|
|
cur.execute("""
|
|
INSERT INTO sim_trades
|
|
(user_id, stock_code, stock_name, trade_type, price, quantity,
|
|
trade_date, trade_time, recommend_rate, signal_reason)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
RETURNING id
|
|
""", (user_id, stock_code, stock_name, trade_type, price, quantity,
|
|
today, now, recommend_rate, signal_reason))
|
|
trade_id = cur.fetchone()['id']
|
|
|
|
# 2. 更新持仓
|
|
if trade_type == 'buy':
|
|
# 买入:增加持仓
|
|
cur.execute("""
|
|
INSERT INTO sim_positions
|
|
(user_id, stock_code, stock_name, quantity, avg_cost, total_cost, current_price)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
|
ON CONFLICT (user_id, stock_code) DO UPDATE SET
|
|
quantity = sim_positions.quantity + EXCLUDED.quantity,
|
|
total_cost = sim_positions.total_cost + EXCLUDED.total_cost,
|
|
avg_cost = (sim_positions.total_cost + EXCLUDED.total_cost) /
|
|
(sim_positions.quantity + EXCLUDED.quantity),
|
|
current_price = EXCLUDED.current_price,
|
|
stock_name = COALESCE(EXCLUDED.stock_name, sim_positions.stock_name),
|
|
updated_at = NOW()
|
|
""", (user_id, stock_code, stock_name, quantity, price,
|
|
price * quantity, price))
|
|
else:
|
|
# 卖出:减少持仓,计算已实现盈亏
|
|
cur.execute("""
|
|
SELECT quantity, avg_cost::float, total_cost::float
|
|
FROM sim_positions
|
|
WHERE user_id = %s AND stock_code = %s
|
|
""", (user_id, stock_code))
|
|
position = cur.fetchone()
|
|
|
|
if not position or position['quantity'] < quantity:
|
|
conn.rollback()
|
|
return jsonify({'success': False, 'error': '持仓不足'}), 400
|
|
|
|
# 计算已实现盈亏
|
|
avg_cost = position['avg_cost']
|
|
realized_pnl = (price - avg_cost) * quantity
|
|
|
|
# 更新持仓
|
|
new_quantity = position['quantity'] - quantity
|
|
new_total_cost = position['total_cost'] - (avg_cost * quantity)
|
|
|
|
if new_quantity > 0:
|
|
cur.execute("""
|
|
UPDATE sim_positions SET
|
|
quantity = %s,
|
|
total_cost = %s,
|
|
current_price = %s,
|
|
updated_at = NOW()
|
|
WHERE user_id = %s AND stock_code = %s
|
|
""", (new_quantity, new_total_cost, price, user_id, stock_code))
|
|
else:
|
|
# 清仓
|
|
cur.execute("""
|
|
UPDATE sim_positions SET
|
|
quantity = 0,
|
|
total_cost = 0,
|
|
current_price = %s,
|
|
updated_at = NOW()
|
|
WHERE user_id = %s AND stock_code = %s
|
|
""", (price, user_id, stock_code))
|
|
|
|
# 更新每日统计中的已实现盈亏
|
|
cur.execute("""
|
|
INSERT INTO sim_daily_stats (user_id, stat_date, realized_profit, trade_count)
|
|
VALUES (%s, %s, %s, 1)
|
|
ON CONFLICT (user_id, stat_date) DO UPDATE SET
|
|
realized_profit = sim_daily_stats.realized_profit + %s,
|
|
trade_count = sim_daily_stats.trade_count + 1
|
|
""", (user_id, today, realized_pnl, realized_pnl))
|
|
|
|
conn.commit()
|
|
return jsonify({
|
|
'success': True,
|
|
'trade_id': trade_id,
|
|
'message': f'{"买入" if trade_type == "buy" else "卖出"} {stock_name or stock_code} {quantity}股 成功'
|
|
})
|
|
except Exception as e:
|
|
conn.rollback()
|
|
import traceback
|
|
traceback.print_exc()
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
@bp.route('/auto_execute', methods=['POST'])
|
|
@login_required
|
|
def auto_execute():
|
|
"""根据推荐率自动执行交易(每日10点调用)"""
|
|
user_id = get_current_user_id()
|
|
data = request.get_json()
|
|
|
|
# 获取推荐的买入和卖出信号
|
|
buy_signals = data.get('buy_signals', []) # 按推荐率排序的买入信号
|
|
sell_signals = data.get('sell_signals', []) # 按推荐率排序的卖出信号
|
|
|
|
conn = get_db()
|
|
if not conn:
|
|
return jsonify({'success': False, 'error': '数据库连接失败'}), 500
|
|
|
|
try:
|
|
cur = conn.cursor(cursor_factory=RealDictCursor)
|
|
|
|
# 获取配置
|
|
cur.execute("SELECT trade_quantity FROM sim_config WHERE user_id = %s", (user_id,))
|
|
config = cur.fetchone()
|
|
quantity = config['trade_quantity'] if config else 1000
|
|
|
|
today = date.today()
|
|
now = datetime.now().time()
|
|
results = []
|
|
|
|
# 1. 先处理卖出信号(释放资金)
|
|
for signal in sell_signals:
|
|
stock_code = signal.get('code')
|
|
|
|
# 检查是否有持仓
|
|
cur.execute("""
|
|
SELECT quantity FROM sim_positions
|
|
WHERE user_id = %s AND stock_code = %s AND quantity > 0
|
|
""", (user_id, stock_code))
|
|
position = cur.fetchone()
|
|
|
|
if position and position['quantity'] >= quantity:
|
|
# 执行卖出
|
|
price = signal.get('price', 0)
|
|
if price > 0:
|
|
# 获取持仓成本
|
|
cur.execute("""
|
|
SELECT avg_cost::float FROM sim_positions
|
|
WHERE user_id = %s AND stock_code = %s
|
|
""", (user_id, stock_code))
|
|
pos_info = cur.fetchone()
|
|
avg_cost = pos_info['avg_cost'] if pos_info else price
|
|
realized_pnl = (price - avg_cost) * quantity
|
|
|
|
# 记录交易
|
|
cur.execute("""
|
|
INSERT INTO sim_trades
|
|
(user_id, stock_code, stock_name, trade_type, price, quantity,
|
|
trade_date, trade_time, recommend_rate, signal_reason)
|
|
VALUES (%s, %s, %s, 'sell', %s, %s, %s, %s, %s, %s)
|
|
""", (user_id, stock_code, signal.get('name', ''), price, quantity,
|
|
today, now, signal.get('recommendRate'), signal.get('reason', '')))
|
|
|
|
# 更新持仓
|
|
cur.execute("""
|
|
UPDATE sim_positions SET
|
|
quantity = quantity - %s,
|
|
total_cost = total_cost - (avg_cost * %s),
|
|
current_price = %s,
|
|
updated_at = NOW()
|
|
WHERE user_id = %s AND stock_code = %s
|
|
""", (quantity, quantity, price, user_id, stock_code))
|
|
|
|
# 更新已实现盈亏
|
|
cur.execute("""
|
|
INSERT INTO sim_daily_stats (user_id, stat_date, realized_profit, trade_count)
|
|
VALUES (%s, %s, %s, 1)
|
|
ON CONFLICT (user_id, stat_date) DO UPDATE SET
|
|
realized_profit = sim_daily_stats.realized_profit + %s,
|
|
trade_count = sim_daily_stats.trade_count + 1
|
|
""", (user_id, today, realized_pnl, realized_pnl))
|
|
|
|
results.append({
|
|
'type': 'sell',
|
|
'code': stock_code,
|
|
'name': signal.get('name', ''),
|
|
'price': price,
|
|
'quantity': quantity,
|
|
'pnl': realized_pnl
|
|
})
|
|
|
|
# 2. 处理买入信号(取推荐率最高的)
|
|
for signal in buy_signals[:3]: # 最多买入3只
|
|
stock_code = signal.get('code')
|
|
price = signal.get('price', 0)
|
|
|
|
if price > 0:
|
|
# 检查今日是否已买入该股票
|
|
cur.execute("""
|
|
SELECT COUNT(*) as cnt FROM sim_trades
|
|
WHERE user_id = %s AND stock_code = %s
|
|
AND trade_date = %s AND trade_type = 'buy'
|
|
""", (user_id, stock_code, today))
|
|
if cur.fetchone()['cnt'] > 0:
|
|
continue # 今日已买入,跳过
|
|
|
|
# 记录交易
|
|
cur.execute("""
|
|
INSERT INTO sim_trades
|
|
(user_id, stock_code, stock_name, trade_type, price, quantity,
|
|
trade_date, trade_time, recommend_rate, signal_reason)
|
|
VALUES (%s, %s, %s, 'buy', %s, %s, %s, %s, %s, %s)
|
|
""", (user_id, stock_code, signal.get('name', ''), price, quantity,
|
|
today, now, signal.get('recommendRate'), signal.get('reason', '')))
|
|
|
|
# 更新持仓
|
|
cur.execute("""
|
|
INSERT INTO sim_positions
|
|
(user_id, stock_code, stock_name, quantity, avg_cost, total_cost, current_price)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
|
ON CONFLICT (user_id, stock_code) DO UPDATE SET
|
|
quantity = sim_positions.quantity + EXCLUDED.quantity,
|
|
total_cost = sim_positions.total_cost + EXCLUDED.total_cost,
|
|
avg_cost = (sim_positions.total_cost + EXCLUDED.total_cost) /
|
|
(sim_positions.quantity + EXCLUDED.quantity),
|
|
current_price = EXCLUDED.current_price,
|
|
stock_name = COALESCE(EXCLUDED.stock_name, sim_positions.stock_name),
|
|
updated_at = NOW()
|
|
""", (user_id, stock_code, signal.get('name', ''), quantity, price,
|
|
price * quantity, price))
|
|
|
|
results.append({
|
|
'type': 'buy',
|
|
'code': stock_code,
|
|
'name': signal.get('name', ''),
|
|
'price': price,
|
|
'quantity': quantity
|
|
})
|
|
|
|
conn.commit()
|
|
return jsonify({
|
|
'success': True,
|
|
'results': results,
|
|
'message': f'自动交易完成: 买入{len([r for r in results if r["type"]=="buy"])}笔, 卖出{len([r for r in results if r["type"]=="sell"])}笔'
|
|
})
|
|
except Exception as e:
|
|
conn.rollback()
|
|
import traceback
|
|
traceback.print_exc()
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
@bp.route('/update_prices', methods=['POST'])
|
|
@login_required
|
|
def update_prices():
|
|
"""更新持仓的当前价格"""
|
|
user_id = get_current_user_id()
|
|
data = request.get_json()
|
|
prices = data.get('prices', {}) # {stock_code: price}
|
|
|
|
if not prices:
|
|
return jsonify({'success': True, 'message': '无需更新'})
|
|
|
|
conn = get_db()
|
|
if not conn:
|
|
return jsonify({'success': False, 'error': '数据库连接失败'}), 500
|
|
|
|
try:
|
|
cur = conn.cursor()
|
|
for code, price in prices.items():
|
|
cur.execute("""
|
|
UPDATE sim_positions SET
|
|
current_price = %s,
|
|
updated_at = NOW()
|
|
WHERE user_id = %s AND stock_code = %s
|
|
""", (price, user_id, code))
|
|
|
|
# 更新每日统计
|
|
today = date.today()
|
|
cur.execute("""
|
|
SELECT
|
|
COALESCE(SUM(quantity * current_price), 0) as market_value,
|
|
COALESCE(SUM(total_cost), 0) as total_cost,
|
|
COALESCE(SUM(quantity * current_price - total_cost), 0) as unrealized
|
|
FROM sim_positions
|
|
WHERE user_id = %s AND quantity > 0
|
|
""", (user_id,))
|
|
stats = cur.fetchone()
|
|
|
|
cur.execute("""
|
|
INSERT INTO sim_daily_stats
|
|
(user_id, stat_date, total_market_value, total_cost, unrealized_profit)
|
|
VALUES (%s, %s, %s, %s, %s)
|
|
ON CONFLICT (user_id, stat_date) DO UPDATE SET
|
|
total_market_value = EXCLUDED.total_market_value,
|
|
total_cost = EXCLUDED.total_cost,
|
|
unrealized_profit = EXCLUDED.unrealized_profit
|
|
""", (user_id, today, stats[0], stats[1], stats[2]))
|
|
|
|
conn.commit()
|
|
return jsonify({'success': True})
|
|
except Exception as e:
|
|
conn.rollback()
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
@bp.route('/reset', methods=['POST'])
|
|
@login_required
|
|
def reset_simulation():
|
|
"""重置模拟交易(清空所有数据)"""
|
|
user_id = get_current_user_id()
|
|
|
|
conn = get_db()
|
|
if not conn:
|
|
return jsonify({'success': False, 'error': '数据库连接失败'}), 500
|
|
|
|
try:
|
|
cur = conn.cursor()
|
|
cur.execute("DELETE FROM sim_trades WHERE user_id = %s", (user_id,))
|
|
cur.execute("DELETE FROM sim_positions WHERE user_id = %s", (user_id,))
|
|
cur.execute("DELETE FROM sim_daily_stats WHERE user_id = %s", (user_id,))
|
|
conn.commit()
|
|
return jsonify({'success': True, 'message': '模拟交易已重置'})
|
|
except Exception as e:
|
|
conn.rollback()
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
@bp.route('/trigger_trade', methods=['POST'])
|
|
@login_required
|
|
def trigger_trade():
|
|
"""手动触发交易 — 优先使用智能引擎,降级到旧引擎"""
|
|
user_id = get_current_user_id()
|
|
|
|
try:
|
|
# 优先使用智能引擎
|
|
try:
|
|
from services.smart_trade_engine import execute_smart_trade
|
|
conn = get_db()
|
|
if conn:
|
|
result = execute_smart_trade(conn, user_id, scan_date=None)
|
|
conn.close()
|
|
if result.get('success'):
|
|
results = result.get('results', [])
|
|
buy_count = len([r for r in results if r['type'] == 'buy'])
|
|
sell_count = len([r for r in results if r['type'] in ('sell', 'partial_sell')])
|
|
return jsonify({
|
|
'success': True,
|
|
'results': results,
|
|
'algo': result.get('algo', 'unknown'),
|
|
'message': f"智能引擎[{result.get('algo','?')}]: 买入{buy_count}笔, 卖出{sell_count}笔"
|
|
})
|
|
except Exception as e:
|
|
print(f"[trigger_trade] 智能引擎异常,降级: {e}")
|
|
|
|
# 降级: 使用旧引擎
|
|
from services.scheduler import execute_auto_trade_for_user
|
|
|
|
conn = get_db()
|
|
if conn:
|
|
cur = conn.cursor(cursor_factory=RealDictCursor)
|
|
cur.execute("SELECT trade_quantity FROM sim_config WHERE user_id = %s", (user_id,))
|
|
config = cur.fetchone()
|
|
trade_quantity = config['trade_quantity'] if config else 1000
|
|
conn.close()
|
|
else:
|
|
trade_quantity = 1000
|
|
|
|
result = execute_auto_trade_for_user(user_id, trade_quantity)
|
|
|
|
if 'error' in result:
|
|
return jsonify({'success': False, 'error': result['error']}), 500
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'results': result.get('results', []),
|
|
'message': f"自动交易完成: 买入{len([r for r in result.get('results', []) if r['type']=='buy'])}笔, 卖出{len([r for r in result.get('results', []) if r['type']=='sell'])}笔"
|
|
})
|
|
except Exception as e:
|
|
import traceback
|
|
traceback.print_exc()
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
@bp.route('/trigger_update', methods=['POST'])
|
|
@login_required
|
|
def trigger_update():
|
|
"""手动触发价格更新(用于测试)"""
|
|
user_id = get_current_user_id()
|
|
|
|
try:
|
|
from services.scheduler import update_positions_price_for_user
|
|
update_positions_price_for_user(user_id)
|
|
return jsonify({'success': True, 'message': '持仓价格已更新'})
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
@bp.route('/today_trades', methods=['GET'])
|
|
@login_required
|
|
def get_today_trades():
|
|
"""获取今日交易记录"""
|
|
user_id = get_current_user_id()
|
|
|
|
conn = get_db()
|
|
if not conn:
|
|
return jsonify({'success': False, 'error': '数据库连接失败'}), 500
|
|
|
|
try:
|
|
cur = conn.cursor(cursor_factory=RealDictCursor)
|
|
today = date.today()
|
|
|
|
cur.execute("""
|
|
SELECT id, stock_code, stock_name, trade_type,
|
|
price::float, quantity, trade_date::text,
|
|
trade_time::text, recommend_rate::float, signal_reason,
|
|
COALESCE(commission, 0)::float as commission,
|
|
COALESCE(stamp_tax, 0)::float as stamp_tax,
|
|
COALESCE(total_fee, 0)::float as total_fee,
|
|
created_at::text
|
|
FROM sim_trades
|
|
WHERE user_id = %s AND trade_date = %s
|
|
ORDER BY trade_time DESC
|
|
""", (user_id, today))
|
|
trades = cur.fetchall()
|
|
|
|
# 计算今日盈亏
|
|
cur.execute("""
|
|
SELECT realized_profit::float, trade_count
|
|
FROM sim_daily_stats
|
|
WHERE user_id = %s AND stat_date = %s
|
|
""", (user_id, today))
|
|
stats = cur.fetchone()
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'trades': trades,
|
|
'today_stats': {
|
|
'realized_profit': stats['realized_profit'] if stats else 0,
|
|
'trade_count': stats['trade_count'] if stats else 0
|
|
}
|
|
})
|
|
finally:
|
|
conn.close()
|