|
|
@@ -281,6 +281,146 @@ def get_symbol_stats(symbol):
|
|
|
logger.error(f"获取{symbol}统计信息失败: {str(e)}")
|
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
|
|
+@app.route('/api/thumbnail/<symbol>', methods=['GET'])
|
|
|
+def get_thumbnail_data(symbol):
|
|
|
+ """获取24小时缩略数据(每分钟采样一次)"""
|
|
|
+ try:
|
|
|
+ conn = get_db_connection()
|
|
|
+ if not conn:
|
|
|
+ return jsonify({"error": "数据库连接失败"}), 500
|
|
|
+
|
|
|
+ cursor = conn.cursor()
|
|
|
+ table_name = f"{QUESTDB_TABLE_PREFIX}_{symbol}"
|
|
|
+
|
|
|
+ # 使用SAMPLE BY语法按分钟采样
|
|
|
+ query = f"""
|
|
|
+ SELECT
|
|
|
+ timestamp,
|
|
|
+ first(lighter_mark_price) as lighter_mark_price,
|
|
|
+ first(binance_mark_price) as binance_mark_price
|
|
|
+ FROM {table_name}
|
|
|
+ WHERE timestamp >= dateadd('h', -24, now())
|
|
|
+ SAMPLE BY 1m ALIGN TO CALENDAR
|
|
|
+ ORDER BY timestamp ASC
|
|
|
+ """
|
|
|
+
|
|
|
+ cursor.execute(query)
|
|
|
+ rows = cursor.fetchall()
|
|
|
+
|
|
|
+ # 转换为JSON格式
|
|
|
+ data = []
|
|
|
+ shanghai_tz = pytz.timezone('Asia/Shanghai')
|
|
|
+ for row in rows:
|
|
|
+ timestamp = row["timestamp"]
|
|
|
+ if timestamp and timestamp.tzinfo is None:
|
|
|
+ timestamp = pytz.utc.localize(timestamp)
|
|
|
+ if timestamp:
|
|
|
+ timestamp = timestamp.astimezone(shanghai_tz)
|
|
|
+ data.append({
|
|
|
+ "timestamp": timestamp.isoformat() if timestamp else None,
|
|
|
+ "lighter_mark_price": float(row["lighter_mark_price"]) if row["lighter_mark_price"] else None,
|
|
|
+ "binance_mark_price": float(row["binance_mark_price"]) if row["binance_mark_price"] else None
|
|
|
+ })
|
|
|
+
|
|
|
+ cursor.close()
|
|
|
+ conn.close()
|
|
|
+
|
|
|
+ return jsonify({
|
|
|
+ "symbol": symbol,
|
|
|
+ "data": data,
|
|
|
+ "count": len(data)
|
|
|
+ })
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"获取{symbol}缩略数据失败: {str(e)}")
|
|
|
+ return jsonify({"error": str(e)}), 500
|
|
|
+
|
|
|
+@app.route('/api/range/<symbol>', methods=['GET'])
|
|
|
+def get_range_data(symbol):
|
|
|
+ """获取指定时间点前后各1800条数据(约1小时)"""
|
|
|
+ try:
|
|
|
+ # 获取中心时间戳参数
|
|
|
+ center_time = request.args.get('timestamp') # ISO格式时间字符串
|
|
|
+ if not center_time:
|
|
|
+ return jsonify({"error": "缺少timestamp参数"}), 400
|
|
|
+
|
|
|
+ conn = get_db_connection()
|
|
|
+ if not conn:
|
|
|
+ return jsonify({"error": "数据库连接失败"}), 500
|
|
|
+
|
|
|
+ cursor = conn.cursor()
|
|
|
+ table_name = f"{QUESTDB_TABLE_PREFIX}_{symbol}"
|
|
|
+
|
|
|
+ # 获取中心时间前后各1800条数据
|
|
|
+ query = f"""
|
|
|
+ WITH center_data AS (
|
|
|
+ SELECT * FROM {table_name}
|
|
|
+ WHERE timestamp <= '{center_time}'
|
|
|
+ ORDER BY timestamp DESC
|
|
|
+ LIMIT 1800
|
|
|
+ ),
|
|
|
+ after_data AS (
|
|
|
+ SELECT * FROM {table_name}
|
|
|
+ WHERE timestamp > '{center_time}'
|
|
|
+ ORDER BY timestamp ASC
|
|
|
+ LIMIT 1800
|
|
|
+ )
|
|
|
+ SELECT
|
|
|
+ timestamp,
|
|
|
+ binance_mark_price,
|
|
|
+ binance_price,
|
|
|
+ lighter_mark_price,
|
|
|
+ lighter_price,
|
|
|
+ (lighter_mark_price - binance_mark_price) as mark_price_diff,
|
|
|
+ (lighter_price - binance_price) as price_diff,
|
|
|
+ ((lighter_mark_price - binance_mark_price) / binance_mark_price * 100) as mark_price_diff_pct,
|
|
|
+ ((lighter_price - binance_price) / binance_price * 100) as price_diff_pct
|
|
|
+ FROM (
|
|
|
+ SELECT * FROM center_data
|
|
|
+ UNION ALL
|
|
|
+ SELECT * FROM after_data
|
|
|
+ )
|
|
|
+ ORDER BY timestamp ASC
|
|
|
+ """
|
|
|
+
|
|
|
+ cursor.execute(query)
|
|
|
+ rows = cursor.fetchall()
|
|
|
+
|
|
|
+ # 转换为JSON格式
|
|
|
+ data = []
|
|
|
+ shanghai_tz = pytz.timezone('Asia/Shanghai')
|
|
|
+ for row in rows:
|
|
|
+ timestamp = row["timestamp"]
|
|
|
+ if timestamp and timestamp.tzinfo is None:
|
|
|
+ timestamp = pytz.utc.localize(timestamp)
|
|
|
+ if timestamp:
|
|
|
+ timestamp = timestamp.astimezone(shanghai_tz)
|
|
|
+ data.append({
|
|
|
+ "timestamp": timestamp.isoformat() if timestamp else None,
|
|
|
+ "binance_mark_price": float(row["binance_mark_price"]) if row["binance_mark_price"] else None,
|
|
|
+ "binance_price": float(row["binance_price"]) if row["binance_price"] else None,
|
|
|
+ "lighter_mark_price": float(row["lighter_mark_price"]) if row["lighter_mark_price"] else None,
|
|
|
+ "lighter_price": float(row["lighter_price"]) if row["lighter_price"] else None,
|
|
|
+ "mark_price_diff": float(row["mark_price_diff"]) if row["mark_price_diff"] else None,
|
|
|
+ "price_diff": float(row["price_diff"]) if row["price_diff"] else None,
|
|
|
+ "mark_price_diff_pct": float(row["mark_price_diff_pct"]) if row["mark_price_diff_pct"] else None,
|
|
|
+ "price_diff_pct": float(row["price_diff_pct"]) if row["price_diff_pct"] else None
|
|
|
+ })
|
|
|
+
|
|
|
+ cursor.close()
|
|
|
+ conn.close()
|
|
|
+
|
|
|
+ return jsonify({
|
|
|
+ "symbol": symbol,
|
|
|
+ "center_time": center_time,
|
|
|
+ "data": data,
|
|
|
+ "count": len(data)
|
|
|
+ })
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"获取{symbol}范围数据失败: {str(e)}")
|
|
|
+ return jsonify({"error": str(e)}), 500
|
|
|
+
|
|
|
@app.route('/health', methods=['GET'])
|
|
|
def health_check():
|
|
|
"""健康检查接口"""
|