添加k线动能理论
This commit is contained in:
+245
-3
@@ -267,6 +267,18 @@ def add_indicators(df):
|
||||
df['ma10'] = (ta.MA(df, timeperiod=10)).fillna(0)
|
||||
df['ma30'] = (ta.EMA(df, timeperiod=30)).fillna(0)
|
||||
df['ma250'] = (ta.MA(df, timeperiod=250)).fillna(0)
|
||||
# 新增 EMA 指标
|
||||
df['ema5'] = (ta.EMA(df, timeperiod=5)).fillna(0)
|
||||
df['ema10'] = (ta.EMA(df, timeperiod=10)).fillna(0)
|
||||
df['ema24'] = (ta.EMA(df, timeperiod=24)).fillna(0)
|
||||
df['ema52'] = (ta.EMA(df, timeperiod=52)).fillna(0)
|
||||
# 常用SMA 24/52
|
||||
try:
|
||||
df['sma24'] = (ta.SMA(df, timeperiod=24)).fillna(0)
|
||||
df['sma52'] = (ta.SMA(df, timeperiod=52)).fillna(0)
|
||||
except Exception:
|
||||
df['sma24'] = 0
|
||||
df['sma52'] = 0
|
||||
df['rsi'] = ta.RSI(df, timeperiod=14)
|
||||
|
||||
# 计算布林带 (当前周期 - 20周期,2标准差)
|
||||
@@ -275,9 +287,11 @@ def add_indicators(df):
|
||||
df['bb_middle'] = bb['middleband'].fillna(0)
|
||||
df['bb_lower'] = bb['lowerband'].fillna(0)
|
||||
bb30 = ta.BBANDS(df, timeperiod=41, nbdevup=2.3, nbdevdn=2.3, matype=0)
|
||||
bb30 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0)
|
||||
df['bbup30'] = bb30['upperband'].fillna(0)
|
||||
df['bblow30'] = bb30['lowerband'].fillna(0)
|
||||
bb302 = ta.BBANDS(df, timeperiod=41, nbdevup=2.0, nbdevdn=2.0, matype=0)
|
||||
bb302 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0)
|
||||
df['bbup302'] = bb302['upperband'].fillna(0)
|
||||
df['bblow302'] = bb302['lowerband'].fillna(0)
|
||||
# 计算次周期布林带 (14周期,2标准差)
|
||||
@@ -293,6 +307,12 @@ def add_indicators(df):
|
||||
df['ma10'] = df['ma10'].fillna(0)
|
||||
df['ma30'] = df['ma30'].fillna(0)
|
||||
df['ma250'] = df['ma250'].fillna(0)
|
||||
df['ema5'] = df['ema5'].fillna(0)
|
||||
df['ema10'] = df['ema10'].fillna(0)
|
||||
df['ema24'] = df['ema24'].fillna(0)
|
||||
df['ema52'] = df['ema52'].fillna(0)
|
||||
df['sma24'] = df['sma24'].fillna(0)
|
||||
df['sma52'] = df['sma52'].fillna(0)
|
||||
df['rsi'] = df['rsi'].fillna(0)
|
||||
df['avg_volume'] = df['volume'].rolling(10).mean()
|
||||
# 计算量比,避免产生Infinity值
|
||||
@@ -312,10 +332,10 @@ def add_indicators(df):
|
||||
|
||||
def calculate_macd(df):
|
||||
"""计算MACD指标"""
|
||||
exp1 = df['close'].ewm(span=24, adjust=False).mean()
|
||||
exp2 = df['close'].ewm(span=52, adjust=False).mean()
|
||||
exp1 = df['close'].ewm(span=12, adjust=False).mean()
|
||||
exp2 = df['close'].ewm(span=26, adjust=False).mean()
|
||||
macd = exp1 - exp2
|
||||
signal = macd.ewm(span=18, adjust=False).mean()
|
||||
signal = macd.ewm(span=9, adjust=False).mean()
|
||||
histogram = macd - signal
|
||||
|
||||
return {
|
||||
@@ -1035,6 +1055,228 @@ def clean_dataframe_for_json(df):
|
||||
|
||||
return clean_df
|
||||
|
||||
# ====== 趋势判定与趋势筛选(币对) ======
|
||||
|
||||
def classify_trend_stage(df):
|
||||
"""根据 EMA 斜率与多空排列判断趋势方向与阶段
|
||||
返回: direction in {"bull","bear","sideways"}, stage in {"early","mid","late"}, strength_score (0-100)
|
||||
"""
|
||||
if df is None or len(df) < 60:
|
||||
return "sideways", "early", 0
|
||||
|
||||
# 使用 EMA5/10/24/52
|
||||
closes = df['close'].values
|
||||
ema5 = df['ema5'].values if 'ema5' in df else ta.EMA(df, timeperiod=5)
|
||||
ema10 = df['ema10'].values if 'ema10' in df else ta.EMA(df, timeperiod=10)
|
||||
ema24 = df['ema24'].values if 'ema24' in df else ta.EMA(df, timeperiod=24)
|
||||
ema52 = df['ema52'].values if 'ema52' in df else ta.EMA(df, timeperiod=52)
|
||||
|
||||
# 最近N根用于斜率与排列判定
|
||||
lookback = min(30, len(df) - 1)
|
||||
if lookback <= 5:
|
||||
return "sideways", "early", 0
|
||||
|
||||
# 简单斜率: 最近k根的线性变化率近似
|
||||
def slope(arr, k=10):
|
||||
k = min(k, len(arr) - 1)
|
||||
if k < 2:
|
||||
return 0.0
|
||||
y = arr[-k:]
|
||||
x = np.arange(k)
|
||||
# 最小二乘拟合斜率
|
||||
denom = np.dot(x - x.mean(), x - x.mean())
|
||||
if denom == 0:
|
||||
return 0.0
|
||||
m = np.dot(y - y.mean(), x - x.mean()) / denom
|
||||
return float(m)
|
||||
|
||||
k_slope = 12 # 斜率窗口
|
||||
s5 = slope(ema5, k_slope)
|
||||
s10 = slope(ema10, k_slope)
|
||||
s24 = slope(ema24, k_slope)
|
||||
s52 = slope(ema52, k_slope)
|
||||
|
||||
# 多空排列
|
||||
last5, last10, last24, last52 = ema5[-1], ema10[-1], ema24[-1], ema52[-1]
|
||||
bull_stack = last5 > last10 > last24 > last52
|
||||
bear_stack = last5 < last10 < last24 < last52
|
||||
|
||||
# 波动性与动量增强: MACD 柱体最近均值
|
||||
macdhist = df['macdhist'].values if 'macdhist' in df else calculate_macd(df)['histogram']
|
||||
hist_recent = macdhist[-lookback:]
|
||||
hist_power = float(np.mean(np.abs(hist_recent))) if len(hist_recent) else 0.0
|
||||
|
||||
# 方向
|
||||
if bull_stack and s24 > 0 and s52 > 0:
|
||||
direction = "bull"
|
||||
elif bear_stack and s24 < 0 and s52 < 0:
|
||||
direction = "bear"
|
||||
else:
|
||||
# 用价格相对 EMA52 辅助
|
||||
if closes[-1] > last52 and (s24 + s52) > 0:
|
||||
direction = "bull"
|
||||
elif closes[-1] < last52 and (s24 + s52) < 0:
|
||||
direction = "bear"
|
||||
else:
|
||||
direction = "sideways"
|
||||
|
||||
# 阶段: 依据(斜率大小、与EMA52距离、MACD柱体扩张/收敛)
|
||||
dist52 = float((closes[-1] - last52) / last52) if last52 else 0.0
|
||||
slope_score = max(0.0, (abs(s24) + abs(s52)) * 1000.0) # 归一化
|
||||
dist_score = min(50.0, abs(dist52) * 200.0)
|
||||
hist_score = min(30.0, hist_power * 10.0)
|
||||
strength = float(min(100.0, slope_score + dist_score + hist_score))
|
||||
|
||||
# 简单阶段判定
|
||||
if direction == "sideways":
|
||||
stage = "early"
|
||||
strength = min(strength, 30.0)
|
||||
else:
|
||||
# 查看最近 hist 是否在扩大或收敛
|
||||
if len(hist_recent) >= 6:
|
||||
recent_growth = np.mean(np.abs(hist_recent[-3:])) - np.mean(np.abs(hist_recent[-6:-3]))
|
||||
else:
|
||||
recent_growth = 0.0
|
||||
|
||||
if recent_growth > 0 and abs(dist52) < 0.05:
|
||||
stage = "early"
|
||||
elif recent_growth > 0 and abs(dist52) >= 0.05:
|
||||
stage = "mid"
|
||||
else:
|
||||
stage = "late"
|
||||
|
||||
return direction, stage, strength
|
||||
|
||||
|
||||
def load_crypto_symbols(limit=200):
|
||||
"""加载常见USDT永续合约交易对,返回列表"""
|
||||
try:
|
||||
markets = exchange.load_markets()
|
||||
symbols = [s for s in markets.keys() if '/USDT' in s and ':USDT' in s]
|
||||
return symbols[:limit]
|
||||
except Exception:
|
||||
return SYMBOLS
|
||||
|
||||
|
||||
@app.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
|
||||
})
|
||||
|
||||
|
||||
@app.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)
|
||||
}
|
||||
})
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""主页"""
|
||||
|
||||
Reference in New Issue
Block a user