update app_vx
This commit is contained in:
295
app_vx.py
295
app_vx.py
@@ -16,6 +16,28 @@ import time
|
||||
from sqlalchemy import create_engine, text, func, or_, case, event, desc, asc
|
||||
from flask import Flask, has_request_context, render_template, request, jsonify, redirect, url_for, flash, session, render_template_string, current_app, send_from_directory
|
||||
|
||||
# Flask 3.x 兼容性补丁:flask-sqlalchemy 旧版本需要 _app_ctx_stack
|
||||
try:
|
||||
from flask import _app_ctx_stack
|
||||
except ImportError:
|
||||
import flask
|
||||
from werkzeug.local import LocalStack
|
||||
import threading
|
||||
|
||||
# 创建一个兼容的 LocalStack 子类
|
||||
class CompatLocalStack(LocalStack):
|
||||
@property
|
||||
def __ident_func__(self):
|
||||
# 返回当前线程的标识函数
|
||||
# 优先使用 greenlet(协程),否则使用 threading
|
||||
try:
|
||||
from greenlet import getcurrent
|
||||
return getcurrent
|
||||
except ImportError:
|
||||
return threading.get_ident
|
||||
|
||||
flask._app_ctx_stack = CompatLocalStack()
|
||||
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||
from flask_mail import Mail, Message
|
||||
@@ -1518,9 +1540,6 @@ def like_post(post_id):
|
||||
post.likes_count += 1
|
||||
message = '已点赞'
|
||||
|
||||
# 可以在这里添加点赞通知
|
||||
if post.user_id != request.user.id:
|
||||
notify_user_post_liked(post)
|
||||
|
||||
db.session.commit()
|
||||
return jsonify({
|
||||
@@ -1597,15 +1616,6 @@ def add_comment(post_id):
|
||||
db.session.add(comment)
|
||||
post.comments_count += 1
|
||||
|
||||
# 如果是回复评论,可以添加通知
|
||||
if parent_id:
|
||||
parent_comment = Comment.query.get(parent_id)
|
||||
if parent_comment and parent_comment.user_id != request.user.id:
|
||||
notify_user_comment_replied(parent_comment)
|
||||
|
||||
# 如果是评论帖子,通知帖子作者
|
||||
elif post.user_id != request.user.id:
|
||||
notify_user_post_commented(post)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
@@ -3853,17 +3863,171 @@ def api_event_related_stocks(event_id):
|
||||
print(f"Error fetching minute data for {stock_code}: {e}")
|
||||
return []
|
||||
|
||||
# ==================== 性能优化:批量查询所有股票数据 ====================
|
||||
# 1. 收集所有股票代码
|
||||
stock_codes = [stock.stock_code for stock in related_stocks]
|
||||
|
||||
# 2. 批量查询股票基本信息
|
||||
stock_info_map = {}
|
||||
if stock_codes:
|
||||
stock_infos = StockBasicInfo.query.filter(StockBasicInfo.SECCODE.in_(stock_codes)).all()
|
||||
for info in stock_infos:
|
||||
stock_info_map[info.SECCODE] = info
|
||||
|
||||
# 处理不带后缀的股票代码
|
||||
base_codes = [code.split('.')[0] for code in stock_codes if '.' in code and code not in stock_info_map]
|
||||
if base_codes:
|
||||
base_infos = StockBasicInfo.query.filter(StockBasicInfo.SECCODE.in_(base_codes)).all()
|
||||
for info in base_infos:
|
||||
# 将不带后缀的信息映射到带后缀的代码
|
||||
for code in stock_codes:
|
||||
if code.split('.')[0] == info.SECCODE and code not in stock_info_map:
|
||||
stock_info_map[code] = info
|
||||
|
||||
# 3. 批量查询 ClickHouse 数据(价格、涨跌幅、分时图数据)
|
||||
price_data_map = {} # 存储价格和涨跌幅数据
|
||||
minute_chart_map = {} # 存储分时图数据
|
||||
|
||||
try:
|
||||
if stock_codes:
|
||||
print(f"批量查询 {len(stock_codes)} 只股票的价格数据...")
|
||||
|
||||
# 3.1 批量查询价格和涨跌幅数据(使用子查询方式,避免窗口函数与 GROUP BY 冲突)
|
||||
batch_price_query = """
|
||||
WITH first_prices AS (
|
||||
SELECT
|
||||
code,
|
||||
close as first_price,
|
||||
ROW_NUMBER() OVER (PARTITION BY code ORDER BY timestamp ASC) as rn
|
||||
FROM stock_minute
|
||||
WHERE code IN %(codes)s
|
||||
AND timestamp >= %(start)s
|
||||
AND timestamp <= %(end)s
|
||||
),
|
||||
last_prices AS (
|
||||
SELECT
|
||||
code,
|
||||
close as last_price,
|
||||
open as open_price,
|
||||
high as high_price,
|
||||
low as low_price,
|
||||
volume,
|
||||
amt as amount,
|
||||
ROW_NUMBER() OVER (PARTITION BY code ORDER BY timestamp DESC) as rn
|
||||
FROM stock_minute
|
||||
WHERE code IN %(codes)s
|
||||
AND timestamp >= %(start)s
|
||||
AND timestamp <= %(end)s
|
||||
)
|
||||
SELECT
|
||||
fp.code,
|
||||
fp.first_price,
|
||||
lp.last_price,
|
||||
(lp.last_price - fp.first_price) / fp.first_price * 100 as change_pct,
|
||||
lp.open_price,
|
||||
lp.high_price,
|
||||
lp.low_price,
|
||||
lp.volume,
|
||||
lp.amount
|
||||
FROM first_prices fp
|
||||
INNER JOIN last_prices lp ON fp.code = lp.code
|
||||
WHERE fp.rn = 1 AND lp.rn = 1
|
||||
"""
|
||||
|
||||
price_data = client.execute(batch_price_query, {
|
||||
'codes': stock_codes,
|
||||
'start': start_datetime,
|
||||
'end': end_datetime
|
||||
})
|
||||
|
||||
print(f"批量查询返回 {len(price_data)} 条价格数据")
|
||||
|
||||
# 解析批量查询结果
|
||||
for row in price_data:
|
||||
code = row[0]
|
||||
first_price = float(row[1]) if row[1] is not None else None
|
||||
last_price = float(row[2]) if row[2] is not None else None
|
||||
change_pct = float(row[3]) if row[3] is not None else None
|
||||
open_price = float(row[4]) if row[4] is not None else None
|
||||
high_price = float(row[5]) if row[5] is not None else None
|
||||
low_price = float(row[6]) if row[6] is not None else None
|
||||
volume = int(row[7]) if row[7] is not None else None
|
||||
amount = float(row[8]) if row[8] is not None else None
|
||||
|
||||
change_amount = None
|
||||
if last_price is not None and first_price is not None:
|
||||
change_amount = last_price - first_price
|
||||
|
||||
price_data_map[code] = {
|
||||
'latest_price': last_price,
|
||||
'first_price': first_price,
|
||||
'change_pct': change_pct,
|
||||
'change_amount': change_amount,
|
||||
'open_price': open_price,
|
||||
'high_price': high_price,
|
||||
'low_price': low_price,
|
||||
'volume': volume,
|
||||
'amount': amount,
|
||||
}
|
||||
|
||||
# 3.2 批量查询分时图数据
|
||||
print(f"批量查询分时图数据...")
|
||||
minute_chart_query = """
|
||||
SELECT
|
||||
code,
|
||||
timestamp,
|
||||
open,
|
||||
high,
|
||||
low,
|
||||
close,
|
||||
volume,
|
||||
amt
|
||||
FROM stock_minute
|
||||
WHERE code IN %(codes)s
|
||||
AND timestamp >= %(start)s
|
||||
AND timestamp <= %(end)s
|
||||
ORDER BY code, timestamp
|
||||
"""
|
||||
|
||||
minute_data = client.execute(minute_chart_query, {
|
||||
'codes': stock_codes,
|
||||
'start': start_datetime,
|
||||
'end': end_datetime
|
||||
})
|
||||
|
||||
print(f"批量查询返回 {len(minute_data)} 条分时数据")
|
||||
|
||||
# 按股票代码分组分时数据
|
||||
for row in minute_data:
|
||||
code = row[0]
|
||||
if code not in minute_chart_map:
|
||||
minute_chart_map[code] = []
|
||||
|
||||
minute_chart_map[code].append({
|
||||
'time': row[1].strftime('%H:%M'),
|
||||
'open': float(row[2]) if row[2] else None,
|
||||
'high': float(row[3]) if row[3] else None,
|
||||
'low': float(row[4]) if row[4] else None,
|
||||
'close': float(row[5]) if row[5] else None,
|
||||
'volume': float(row[6]) if row[6] else None,
|
||||
'amount': float(row[7]) if row[7] else None
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"批量查询 ClickHouse 失败: {e}")
|
||||
# 如果批量查询失败,price_data_map 和 minute_chart_map 为空,后续会使用降级方案
|
||||
|
||||
# 4. 组装每个股票的数据(从批量查询结果中获取)
|
||||
stocks_data = []
|
||||
for stock in related_stocks:
|
||||
print(f"正在处理股票 {stock.stock_code} 的价格数据...")
|
||||
print(f"正在组装股票 {stock.stock_code} 的数据...")
|
||||
|
||||
# 获取股票基本信息
|
||||
stock_info = StockBasicInfo.query.filter_by(SECCODE=stock.stock_code).first()
|
||||
if not stock_info:
|
||||
base_code = stock.stock_code.split('.')[0]
|
||||
stock_info = StockBasicInfo.query.filter_by(SECCODE=base_code).first()
|
||||
# 从批量查询结果中获取股票基本信息
|
||||
stock_info = stock_info_map.get(stock.stock_code)
|
||||
|
||||
# 从批量查询结果中获取价格数据
|
||||
price_info = price_data_map.get(stock.stock_code)
|
||||
|
||||
# 使用与 get_stock_quotes 完全相同的逻辑计算涨跌幅
|
||||
latest_price = None
|
||||
first_price = None
|
||||
change_pct = None
|
||||
@@ -3875,79 +4039,20 @@ def api_event_related_stocks(event_id):
|
||||
amount = None
|
||||
trade_date = trading_day
|
||||
|
||||
try:
|
||||
# 使用与 get_stock_quotes 完全相同的 SQL 查询
|
||||
# 获取事件时间点的第一个价格 (first_price) 和当前时间的最后一个价格 (last_price)
|
||||
data = client.execute("""
|
||||
WITH first_price AS (
|
||||
SELECT close
|
||||
FROM stock_minute
|
||||
WHERE code = %(code)s
|
||||
AND timestamp >= %(start)s
|
||||
AND timestamp <= %(end)s
|
||||
ORDER BY timestamp
|
||||
LIMIT 1
|
||||
),
|
||||
last_price AS (
|
||||
SELECT close
|
||||
FROM stock_minute
|
||||
WHERE code = %(code)s
|
||||
AND timestamp >= %(start)s
|
||||
AND timestamp <= %(end)s
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 1
|
||||
)
|
||||
SELECT
|
||||
last_price.close as last_price,
|
||||
(last_price.close - first_price.close) / first_price.close * 100 as change,
|
||||
first_price.close as first_price
|
||||
FROM last_price
|
||||
CROSS JOIN first_price
|
||||
WHERE EXISTS (SELECT 1 FROM first_price)
|
||||
AND EXISTS (SELECT 1 FROM last_price)
|
||||
""", {
|
||||
'code': stock.stock_code,
|
||||
'start': start_datetime,
|
||||
'end': end_datetime
|
||||
})
|
||||
|
||||
print(f"股票 {stock.stock_code} 查询结果: {data}")
|
||||
|
||||
if data and data[0] and data[0][0] is not None:
|
||||
latest_price = float(data[0][0])
|
||||
change_pct = float(data[0][1]) if data[0][1] is not None else None
|
||||
first_price = float(data[0][2]) if len(data[0]) > 2 and data[0][2] is not None else None
|
||||
|
||||
# 计算涨跌额
|
||||
if latest_price is not None and first_price is not None:
|
||||
change_amount = latest_price - first_price
|
||||
|
||||
# 获取额外的价格信息(开盘价、最高价、最低价等)
|
||||
extra_data = client.execute("""
|
||||
SELECT
|
||||
open, high, low, volume, amt
|
||||
FROM stock_minute
|
||||
WHERE code = %(code)s
|
||||
AND timestamp >= %(start)s
|
||||
AND timestamp <= %(end)s
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 1
|
||||
""", {
|
||||
'code': stock.stock_code,
|
||||
'start': start_datetime,
|
||||
'end': end_datetime
|
||||
})
|
||||
|
||||
if extra_data and extra_data[0]:
|
||||
open_price = float(extra_data[0][0]) if extra_data[0][0] else None
|
||||
high_price = float(extra_data[0][1]) if extra_data[0][1] else None
|
||||
low_price = float(extra_data[0][2]) if extra_data[0][2] else None
|
||||
volume = int(extra_data[0][3]) if extra_data[0][3] else None
|
||||
amount = float(extra_data[0][4]) if extra_data[0][4] else None
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error fetching price data for {stock.stock_code}: {e}")
|
||||
# 如果 ClickHouse 查询失败,尝试使用 TradeData 作为降级方案
|
||||
if price_info:
|
||||
# 使用批量查询的结果
|
||||
latest_price = price_info['latest_price']
|
||||
first_price = price_info['first_price']
|
||||
change_pct = price_info['change_pct']
|
||||
change_amount = price_info['change_amount']
|
||||
open_price = price_info['open_price']
|
||||
high_price = price_info['high_price']
|
||||
low_price = price_info['low_price']
|
||||
volume = price_info['volume']
|
||||
amount = price_info['amount']
|
||||
else:
|
||||
# 如果批量查询没有返回数据,使用降级方案(TradeData)
|
||||
print(f"股票 {stock.stock_code} 批量查询无数据,使用降级方案...")
|
||||
try:
|
||||
latest_trade = None
|
||||
search_codes = [stock.stock_code, stock.stock_code.split('.')[0]]
|
||||
@@ -3974,10 +4079,10 @@ def api_event_related_stocks(event_id):
|
||||
if latest_trade.F009N:
|
||||
change_amount = float(latest_trade.F009N)
|
||||
except Exception as fallback_error:
|
||||
print(f"Fallback query also failed for {stock.stock_code}: {fallback_error}")
|
||||
print(f"降级查询也失败 {stock.stock_code}: {fallback_error}")
|
||||
|
||||
# 获取分时图数据
|
||||
minute_chart_data = get_minute_chart_data(stock.stock_code)
|
||||
# 从批量查询结果中获取分时图数据
|
||||
minute_chart_data = minute_chart_map.get(stock.stock_code, [])
|
||||
|
||||
stock_data = {
|
||||
'id': stock.id,
|
||||
|
||||
Reference in New Issue
Block a user