refactor: 缠论引擎包化与 Web 分层(ECR-001)
将根目录引擎迁入 chanlun/ 并保留兼容 shim;拆分 TF_DF 与 web 服务; 前端模块化;strategies 改用 chanlun 导入;补充 ESS 文档与 golden 回归。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
"""趋势相关 API。"""
|
||||
from flask import Blueprint, jsonify, request
|
||||
from services.runtime import * # noqa: F403
|
||||
from services import runtime as R
|
||||
|
||||
bp = Blueprint("trend", __name__)
|
||||
|
||||
@bp.route('/api/trend_filter', methods=['GET'])
|
||||
def trend_filter():
|
||||
"""趋势筛选接口(币对)
|
||||
参数:
|
||||
timeframe: K线周期
|
||||
start_time, end_time: 毫秒时间戳,可选
|
||||
direction: bull/bear/sideways 可选
|
||||
stage: early/mid/late 可选
|
||||
min_strength: 0-100 可选
|
||||
symbols: 逗号分隔列表,可选;不传则自动加载部分USDT币对
|
||||
返回符合条件的币对与简要统计
|
||||
"""
|
||||
timeframe = request.args.get('timeframe', '1h')
|
||||
start_time = request.args.get('start_time')
|
||||
end_time = request.args.get('end_time')
|
||||
want_direction = request.args.get('direction') # 可为 None
|
||||
want_stage = request.args.get('stage') # 可为 None
|
||||
try:
|
||||
min_strength = float(request.args.get('min_strength', '0'))
|
||||
except ValueError:
|
||||
min_strength = 0.0
|
||||
|
||||
symbols_param = request.args.get('symbols')
|
||||
if symbols_param:
|
||||
symbols_list = [s.strip() for s in symbols_param.split(',') if s.strip()]
|
||||
else:
|
||||
symbols_list = load_crypto_symbols(limit=150)
|
||||
|
||||
results = []
|
||||
for sym in symbols_list:
|
||||
try:
|
||||
df = get_crypto_kl_data(sym, timeframe, start_time=start_time, end_time=end_time)
|
||||
if df is None or len(df) < 60:
|
||||
continue
|
||||
df = add_indicators(df)
|
||||
direction, stage, strength = classify_trend_stage(df)
|
||||
|
||||
if want_direction and direction != want_direction:
|
||||
continue
|
||||
if want_stage and stage != want_stage:
|
||||
continue
|
||||
if strength < min_strength:
|
||||
continue
|
||||
|
||||
last_row = df.iloc[-1]
|
||||
results.append({
|
||||
'symbol': sym,
|
||||
'time': int(last_row['timestamp']),
|
||||
'close': float(last_row['close']),
|
||||
'direction': direction,
|
||||
'stage': stage,
|
||||
'strength': float(round(strength, 2)),
|
||||
'ema5': float(last_row['ema5']),
|
||||
'ema10': float(last_row['ema10']),
|
||||
'ema24': float(last_row['ema24']),
|
||||
'ema52': float(last_row['ema52'])
|
||||
})
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# 按强度降序
|
||||
results.sort(key=lambda x: x['strength'], reverse=True)
|
||||
return jsonify({
|
||||
'count': len(results),
|
||||
'results': results
|
||||
})
|
||||
|
||||
|
||||
@bp.route('/api/trend_detail', methods=['GET'])
|
||||
def trend_detail():
|
||||
"""返回单个币对的K线与EMA、用于前端绘制趋势线
|
||||
参数: symbol, timeframe, start_time, end_time
|
||||
"""
|
||||
symbol = request.args.get('symbol')
|
||||
timeframe = request.args.get('timeframe', '1h')
|
||||
start_time = request.args.get('start_time')
|
||||
end_time = request.args.get('end_time')
|
||||
timezone_name = request.args.get('timezone', 'Asia/Shanghai')
|
||||
|
||||
if not symbol:
|
||||
return jsonify({'error': 'symbol不能为空'})
|
||||
|
||||
df = get_crypto_kl_data(symbol, timeframe, start_time=start_time, end_time=end_time)
|
||||
if df is None or len(df) == 0:
|
||||
return jsonify({'error': '获取数据失败'})
|
||||
|
||||
df = add_indicators(df)
|
||||
direction, stage, strength = classify_trend_stage(df)
|
||||
|
||||
# 简单趋势线: 用最近N根收盘价做线性拟合
|
||||
N = min(80, len(df))
|
||||
sub = df.tail(N)
|
||||
y = sub['close'].values
|
||||
x = np.arange(len(y))
|
||||
denom = np.dot(x - x.mean(), x - x.mean())
|
||||
if denom != 0:
|
||||
m = float(np.dot(y - y.mean(), x - x.mean()) / denom)
|
||||
b = float(y.mean() - m * x.mean())
|
||||
else:
|
||||
m, b = 0.0, float(y[-1])
|
||||
|
||||
client_tz = timezone(timezone_name)
|
||||
|
||||
return jsonify({
|
||||
'symbol': symbol,
|
||||
'timeframe': timeframe,
|
||||
'timezone': timezone_name,
|
||||
'direction': direction,
|
||||
'stage': stage,
|
||||
'strength': float(round(strength, 2)),
|
||||
'kline_data': clean_dataframe_for_json(df)[['timestamp','open','high','low','close','volume','ema5','ema10','ema24','ema52']].to_dict('records'),
|
||||
'trend_line': {
|
||||
'offset': int(df.index[-N]),
|
||||
'slope': m,
|
||||
'intercept': b,
|
||||
'length': int(N)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user