- Add scripts/scoring/ module with normalizer, sensitivity analysis, and config - Enhance stock_viewer.html with standardized scoring display - Add integration tests and normalization verification scripts - Add documentation for standardization implementation and usage guides - Add data distribution analysis reports for strength scoring dimensions - Update discussion documents with algorithm optimization plans
471 lines
15 KiB
Python
471 lines
15 KiB
Python
"""
|
||
强度分配置管理模块
|
||
|
||
提供可配置的权重、阈值和预设模式,支持:
|
||
1. 等权模式(默认)
|
||
2. 激进模式(重视突破和成交量)
|
||
3. 保守模式(重视形态质量)
|
||
4. 放量模式(重视成交量确认)
|
||
"""
|
||
|
||
from dataclasses import dataclass, field
|
||
from typing import Literal, Optional
|
||
import pandas as pd
|
||
|
||
|
||
@dataclass
|
||
class StrengthConfig:
|
||
"""
|
||
强度分配置类
|
||
|
||
属性:
|
||
w_price: 突破幅度分权重
|
||
w_convergence: 收敛度分权重
|
||
w_volume: 成交量分权重
|
||
w_geometry: 形态规则度权重
|
||
w_activity: 价格活跃度权重
|
||
w_tilt: 倾斜度分权重
|
||
|
||
threshold_price: 突破幅度分阈值(标准化后)
|
||
threshold_convergence: 收敛度分阈值
|
||
threshold_volume: 成交量分阈值
|
||
threshold_geometry: 形态规则度阈值
|
||
threshold_activity: 价格活跃度阈值
|
||
|
||
direction: 突破方向 ('up', 'down', 'both')
|
||
filter_mode: 筛选模式 ('and', 'or')
|
||
"""
|
||
|
||
# 权重配置(默认等权)
|
||
w_price: float = 1/6
|
||
w_convergence: float = 1/6
|
||
w_volume: float = 1/6
|
||
w_geometry: float = 1/6
|
||
w_activity: float = 1/6
|
||
w_tilt: float = 1/6
|
||
|
||
# 阈值配置(标准化后的值,范围[0, 1])
|
||
threshold_price: float = 0.60 # 突破幅度阈值
|
||
threshold_convergence: float = 0.50 # 收敛度阈值(中性)
|
||
threshold_volume: float = 0.50 # 成交量阈值(中性=不筛选)
|
||
threshold_geometry: float = 0.50 # 形态规则度阈值(中性)
|
||
threshold_activity: float = 0.30 # 价格活跃度阈值
|
||
|
||
# 方向与模式
|
||
direction: Literal['up', 'down', 'both'] = 'up'
|
||
filter_mode: Literal['and', 'or'] = 'and'
|
||
|
||
# 配置名称(用于显示)
|
||
name: str = "自定义配置"
|
||
|
||
def validate(self) -> bool:
|
||
"""验证配置的有效性"""
|
||
# 检查权重和是否为1
|
||
total_weight = (self.w_price + self.w_convergence + self.w_volume +
|
||
self.w_geometry + self.w_activity + self.w_tilt)
|
||
|
||
if abs(total_weight - 1.0) > 0.001:
|
||
raise ValueError(f"权重和必须为1.0,当前为{total_weight:.6f}")
|
||
|
||
# 检查权重范围
|
||
for name, weight in [
|
||
('price', self.w_price), ('convergence', self.w_convergence),
|
||
('volume', self.w_volume), ('geometry', self.w_geometry),
|
||
('activity', self.w_activity), ('tilt', self.w_tilt)
|
||
]:
|
||
if not 0 <= weight <= 1:
|
||
raise ValueError(f"{name}权重{weight}超出[0, 1]范围")
|
||
|
||
# 检查阈值范围
|
||
for name, threshold in [
|
||
('price', self.threshold_price), ('convergence', self.threshold_convergence),
|
||
('volume', self.threshold_volume), ('geometry', self.threshold_geometry),
|
||
('activity', self.threshold_activity)
|
||
]:
|
||
if not 0 <= threshold <= 1:
|
||
raise ValueError(f"{name}阈值{threshold}超出[0, 1]范围")
|
||
|
||
return True
|
||
|
||
def summary(self) -> str:
|
||
"""返回配置摘要"""
|
||
lines = [
|
||
f"配置名称: {self.name}",
|
||
f"\n权重分配:",
|
||
f" 突破幅度分: {self.w_price:.2%}",
|
||
f" 收敛度分: {self.w_convergence:.2%}",
|
||
f" 成交量分: {self.w_volume:.2%}",
|
||
f" 形态规则度: {self.w_geometry:.2%}",
|
||
f" 价格活跃度: {self.w_activity:.2%}",
|
||
f" 倾斜度分: {self.w_tilt:.2%}",
|
||
f"\n筛选阈值:",
|
||
f" 突破幅度分: ≥{self.threshold_price:.2f}",
|
||
f" 收敛度分: ≥{self.threshold_convergence:.2f}",
|
||
f" 成交量分: ≥{self.threshold_volume:.2f}",
|
||
f" 价格活跃度: ≥{self.threshold_activity:.2f}",
|
||
f"\n其他:",
|
||
f" 方向: {self.direction}",
|
||
f" 筛选模式: {self.filter_mode}",
|
||
]
|
||
return '\n'.join(lines)
|
||
|
||
|
||
# ============================================================================
|
||
# 预设配置
|
||
# ============================================================================
|
||
|
||
# 等权模式(默认)
|
||
CONFIG_EQUAL = StrengthConfig(
|
||
name="等权模式",
|
||
w_price=1/6,
|
||
w_convergence=1/6,
|
||
w_volume=1/6,
|
||
w_geometry=1/6,
|
||
w_activity=1/6,
|
||
w_tilt=1/6,
|
||
threshold_price=0.60,
|
||
threshold_convergence=0.50,
|
||
threshold_volume=0.50,
|
||
)
|
||
|
||
# 激进模式(重视突破和成交量,适合趋势行情)
|
||
CONFIG_AGGRESSIVE = StrengthConfig(
|
||
name="激进模式",
|
||
w_price=0.35, # 突破最重要
|
||
w_volume=0.25, # 成交量确认
|
||
w_convergence=0.15, # 收敛度
|
||
w_geometry=0.10, # 形态
|
||
w_activity=0.10, # 活跃度
|
||
w_tilt=0.05, # 倾斜度
|
||
threshold_price=0.55, # 较低阈值,捕获更多信号
|
||
threshold_volume=0.60, # 要求一定放量
|
||
direction='up',
|
||
)
|
||
|
||
# 保守模式(重视形态质量,适合震荡市)
|
||
CONFIG_CONSERVATIVE = StrengthConfig(
|
||
name="保守模式",
|
||
w_price=0.15, # 突破不是最重要
|
||
w_convergence=0.30, # 收敛度最重要
|
||
w_volume=0.10, # 成交量
|
||
w_geometry=0.15, # 形态质量
|
||
w_activity=0.25, # 价格活跃度重要
|
||
w_tilt=0.05, # 倾斜度
|
||
threshold_price=0.70, # 较高阈值,筛选强信号
|
||
threshold_convergence=0.65, # 要求高质量收敛
|
||
threshold_activity=0.50, # 要求活跃度正常
|
||
)
|
||
|
||
# 放量模式(重视成交量确认,捕获主力异动)
|
||
CONFIG_VOLUME_FOCUS = StrengthConfig(
|
||
name="放量模式",
|
||
w_price=0.25, # 突破
|
||
w_volume=0.35, # 成交量最重要
|
||
w_convergence=0.15, # 收敛度
|
||
w_geometry=0.10, # 形态
|
||
w_activity=0.10, # 活跃度
|
||
w_tilt=0.05, # 倾斜度
|
||
threshold_price=0.60, # 中等突破要求
|
||
threshold_volume=0.70, # 高放量要求
|
||
threshold_convergence=0.50,
|
||
)
|
||
|
||
|
||
# ============================================================================
|
||
# 单维度测试模式(每个维度50%,其余各10%)
|
||
# ============================================================================
|
||
|
||
# 突破幅度主导
|
||
CONFIG_TEST_PRICE = StrengthConfig(
|
||
name="突破主导",
|
||
w_price=0.50, # 主导维度
|
||
w_convergence=0.10,
|
||
w_volume=0.10,
|
||
w_geometry=0.10,
|
||
w_activity=0.10,
|
||
w_tilt=0.10,
|
||
)
|
||
|
||
# 收敛度主导
|
||
CONFIG_TEST_CONVERGENCE = StrengthConfig(
|
||
name="收敛主导",
|
||
w_price=0.10,
|
||
w_convergence=0.50, # 主导维度
|
||
w_volume=0.10,
|
||
w_geometry=0.10,
|
||
w_activity=0.10,
|
||
w_tilt=0.10,
|
||
)
|
||
|
||
# 成交量主导
|
||
CONFIG_TEST_VOLUME = StrengthConfig(
|
||
name="成交量主导",
|
||
w_price=0.10,
|
||
w_convergence=0.10,
|
||
w_volume=0.50, # 主导维度
|
||
w_geometry=0.10,
|
||
w_activity=0.10,
|
||
w_tilt=0.10,
|
||
)
|
||
|
||
# 形态规则主导
|
||
CONFIG_TEST_GEOMETRY = StrengthConfig(
|
||
name="形态主导",
|
||
w_price=0.10,
|
||
w_convergence=0.10,
|
||
w_volume=0.10,
|
||
w_geometry=0.50, # 主导维度
|
||
w_activity=0.10,
|
||
w_tilt=0.10,
|
||
)
|
||
|
||
# 活跃度主导
|
||
CONFIG_TEST_ACTIVITY = StrengthConfig(
|
||
name="活跃主导",
|
||
w_price=0.10,
|
||
w_convergence=0.10,
|
||
w_volume=0.10,
|
||
w_geometry=0.10,
|
||
w_activity=0.50, # 主导维度
|
||
w_tilt=0.10,
|
||
)
|
||
|
||
# 倾斜度主导
|
||
CONFIG_TEST_TILT = StrengthConfig(
|
||
name="倾斜主导",
|
||
w_price=0.10,
|
||
w_convergence=0.10,
|
||
w_volume=0.10,
|
||
w_geometry=0.10,
|
||
w_activity=0.10,
|
||
w_tilt=0.50, # 主导维度
|
||
)
|
||
|
||
|
||
# ============================================================================
|
||
# 筛选和计算函数
|
||
# ============================================================================
|
||
|
||
def calculate_strength(
|
||
df_normalized: pd.DataFrame,
|
||
config: StrengthConfig
|
||
) -> pd.Series:
|
||
"""
|
||
根据配置计算综合强度分
|
||
|
||
Args:
|
||
df_normalized: 标准化后的DataFrame(需包含*_norm字段)
|
||
config: 配置对象
|
||
|
||
Returns:
|
||
综合强度分序列
|
||
"""
|
||
config.validate()
|
||
|
||
# 选择方向
|
||
if config.direction == 'up':
|
||
price_col = 'price_score_up_norm'
|
||
elif config.direction == 'down':
|
||
price_col = 'price_score_down_norm'
|
||
else: # 'both'
|
||
# 取向上和向下的最大值
|
||
price_col = None
|
||
price_scores = df_normalized[['price_score_up_norm', 'price_score_down_norm']].max(axis=1)
|
||
|
||
# 加权计算
|
||
if price_col:
|
||
strength = (
|
||
config.w_price * df_normalized[price_col] +
|
||
config.w_convergence * df_normalized['convergence_score_norm'] +
|
||
config.w_volume * df_normalized['volume_score_norm'] +
|
||
config.w_geometry * df_normalized['geometry_score_norm'] +
|
||
config.w_activity * df_normalized['activity_score_norm'] +
|
||
config.w_tilt * df_normalized['tilt_score_norm']
|
||
)
|
||
else:
|
||
strength = (
|
||
config.w_price * price_scores +
|
||
config.w_convergence * df_normalized['convergence_score_norm'] +
|
||
config.w_volume * df_normalized['volume_score_norm'] +
|
||
config.w_geometry * df_normalized['geometry_score_norm'] +
|
||
config.w_activity * df_normalized['activity_score_norm'] +
|
||
config.w_tilt * df_normalized['tilt_score_norm']
|
||
)
|
||
|
||
return strength
|
||
|
||
|
||
def filter_signals(
|
||
df_normalized: pd.DataFrame,
|
||
config: StrengthConfig,
|
||
return_strength: bool = False
|
||
) -> pd.DataFrame:
|
||
"""
|
||
根据配置筛选信号
|
||
|
||
Args:
|
||
df_normalized: 标准化后的DataFrame
|
||
config: 配置对象
|
||
return_strength: 是否在结果中添加强度分列
|
||
|
||
Returns:
|
||
筛选后的DataFrame
|
||
"""
|
||
config.validate()
|
||
|
||
# 构建筛选条件
|
||
conditions = []
|
||
|
||
# 1. 突破幅度条件
|
||
if config.direction in ['up', 'both']:
|
||
conditions.append(
|
||
df_normalized['price_score_up_norm'] >= config.threshold_price
|
||
)
|
||
if config.direction in ['down', 'both']:
|
||
conditions.append(
|
||
df_normalized['price_score_down_norm'] >= config.threshold_price
|
||
)
|
||
|
||
# 2. 收敛度条件
|
||
if config.threshold_convergence > 0:
|
||
conditions.append(
|
||
df_normalized['convergence_score_norm'] >= config.threshold_convergence
|
||
)
|
||
|
||
# 3. 成交量条件(只有阈值>0.5时才启用,否则是放松条件)
|
||
if config.threshold_volume > 0.5:
|
||
conditions.append(
|
||
df_normalized['volume_score_norm'] >= config.threshold_volume
|
||
)
|
||
|
||
# 4. 形态规则度条件
|
||
if config.threshold_geometry > 0:
|
||
conditions.append(
|
||
df_normalized['geometry_score_norm'] >= config.threshold_geometry
|
||
)
|
||
|
||
# 5. 价格活跃度条件
|
||
if config.threshold_activity > 0:
|
||
conditions.append(
|
||
df_normalized['activity_score_norm'] >= config.threshold_activity
|
||
)
|
||
|
||
# 组合条件
|
||
if len(conditions) == 0:
|
||
# 没有任何筛选条件,返回全部
|
||
result = df_normalized
|
||
elif config.filter_mode == 'and':
|
||
# AND: 所有条件都满足
|
||
final_condition = conditions[0]
|
||
for cond in conditions[1:]:
|
||
final_condition = final_condition & cond
|
||
result = df_normalized[final_condition]
|
||
else: # 'or'
|
||
# OR: 任一条件满足
|
||
final_condition = conditions[0]
|
||
for cond in conditions[1:]:
|
||
final_condition = final_condition | cond
|
||
result = df_normalized[final_condition]
|
||
|
||
# 添加强度分
|
||
if return_strength:
|
||
result = result.copy()
|
||
result['strength'] = calculate_strength(result, config)
|
||
result = result.sort_values('strength', ascending=False)
|
||
|
||
return result
|
||
|
||
|
||
def filter_top_n(
|
||
df_normalized: pd.DataFrame,
|
||
config: StrengthConfig,
|
||
n: int = 100
|
||
) -> pd.DataFrame:
|
||
"""
|
||
筛选强度分Top N的信号
|
||
|
||
Args:
|
||
df_normalized: 标准化后的DataFrame
|
||
config: 配置对象
|
||
n: 返回前N个信号
|
||
|
||
Returns:
|
||
Top N的DataFrame,包含strength列
|
||
"""
|
||
# 计算强度分
|
||
df_with_strength = df_normalized.copy()
|
||
df_with_strength['strength'] = calculate_strength(df_normalized, config)
|
||
|
||
# 排序并取Top N
|
||
result = df_with_strength.nlargest(n, 'strength')
|
||
|
||
return result
|
||
|
||
|
||
# ============================================================================
|
||
# 使用示例
|
||
# ============================================================================
|
||
|
||
if __name__ == "__main__":
|
||
import sys
|
||
import os
|
||
|
||
# 添加路径
|
||
script_dir = os.path.dirname(__file__)
|
||
sys.path.insert(0, script_dir)
|
||
|
||
from normalizer import normalize_all
|
||
|
||
# 加载数据
|
||
data_path = os.path.join(
|
||
os.path.dirname(__file__),
|
||
"..", "..", "outputs", "converging_triangles", "all_results_normalized.csv"
|
||
)
|
||
|
||
if os.path.exists(data_path):
|
||
print("=" * 80)
|
||
print("强度分配置模块测试")
|
||
print("=" * 80)
|
||
|
||
df = pd.read_csv(data_path)
|
||
print(f"\n加载数据: {len(df)} 条记录")
|
||
|
||
# 测试各种配置
|
||
configs = [
|
||
CONFIG_EQUAL,
|
||
CONFIG_AGGRESSIVE,
|
||
CONFIG_CONSERVATIVE,
|
||
CONFIG_VOLUME_FOCUS,
|
||
]
|
||
|
||
print("\n" + "=" * 80)
|
||
print("各配置筛选结果对比")
|
||
print("=" * 80)
|
||
|
||
for config in configs:
|
||
filtered = filter_signals(df, config, return_strength=False)
|
||
print(f"\n{config.name}:")
|
||
print(f" 信号数量: {len(filtered)} ({len(filtered)/len(df)*100:.1f}%)")
|
||
print(f" 权重: P{config.w_price:.0%}/C{config.w_convergence:.0%}/V{config.w_volume:.0%}")
|
||
print(f" 阈值: price≥{config.threshold_price:.2f}, vol≥{config.threshold_volume:.2f}")
|
||
|
||
# 测试Top N
|
||
print("\n" + "=" * 80)
|
||
print("Top 10 信号(等权模式)")
|
||
print("=" * 80)
|
||
|
||
top10 = filter_top_n(df, CONFIG_EQUAL, n=10)
|
||
print("\nstock_code | date | strength | price_up | convergence | volume")
|
||
print("-" * 80)
|
||
for _, row in top10.iterrows():
|
||
print(f"{row['stock_code']:10s} | {int(row['date'])} | "
|
||
f"{row['strength']:.4f} | "
|
||
f"{row['price_score_up_norm']:.4f} | "
|
||
f"{row['convergence_score_norm']:.4f} | "
|
||
f"{row['volume_score_norm']:.4f}")
|
||
|
||
print("\n测试通过!")
|
||
else:
|
||
print(f"数据文件不存在: {data_path}")
|
||
print("请先运行 verify_normalization.py 生成标准化数据")
|