基于时间、价格和成交量创建 3D 柱状图引入波动率测量·综合运用
🧊

基于时间、价格和成交量创建 3D 柱状图引入波动率测量·综合运用

(3/3)·二维图表漏掉的市场真实结构,用体积柱从第一笔 tick 起捕捉动量失衡

实战向 第 3/3 篇
二维图上看似相同的两根走势,成交量支撑可能天差地别。传统指标把价格投影到平面,往往等信号出现时行情已跑过半程。用体积结构看盘,能在常规 K 线成形前感知真实动能。

◍ 把量价三维图与二维图拼进同一张画布

这段 Python(Plotly)代码负责把 3D 量价曲面和 2D 价格走势并排渲染,核心是把 MA100 线以蓝色 1 像素宽画在右下子图(row=1, col=2)。 3D 场景坐标轴被显式命名为 Time / Volume / Price,相机初始视角 eye 设为 (1.5, 1.5, 1.5),也就是从正斜上方 45 度看进去,能同时看清时间轴铺展和价格-成交量曲面起伏。 整体画布宽 1500、高 750,刚好是单图两倍宽,用来塞下左右两个 plot;最后用 fig.write_html 导出为 {symbol}_combined_view.html,MT5 导出的 CSV 经小布管道清洗后可直接喂进去生成交互页。 第二段 fig2 用 2×2 子图展示平滑数据:左上平滑收盘价(close_smooth)、右上平滑tick量(tick_volume_smooth),各自套 MA100,命名 'Price MA100' / 'Volume MA100',方便对比原始图里的噪声是否被均线吃掉。外汇与贵金属波动剧烈,平滑参数不当会掩盖真实流动性断层,回测前务必先肉眼核对平滑前后形态差异。

MQL5 / C++
line=dict(class="type">class="kw">color=&class="macro">#x27;blue&class="macro">#x27;, width=class="num">1),
name=&class="macro">#x27;MA100&class="macro">#x27;
),
row=class="num">1, col=class="num">2

# Update 3D layout
fig.update_scenes(
    xaxis_title=&class="macro">#x27;Time&class="macro">#x27;,
    yaxis_title=&class="macro">#x27;Volume&class="macro">#x27;,
    zaxis_title=&class="macro">#x27;Price&class="macro">#x27;,
    camera=dict(
        up=dict(x=class="num">0, y=class="num">0, z=class="num">1),
        center=dict(x=class="num">0, y=class="num">0, z=class="num">0),
        eye=dict(x=class="num">1.5, y=class="num">1.5, z=class="num">1.5)
    )
)

# Update 2D layout
fig.update_xaxes(title_text="Time", row=class="num">1, col=class="num">2)
fig.update_yaxes(title_text="Price", row=class="num">1, col=class="num">2)

# Update overall layout
fig.update_layout(
    width=class="num">1500,  # Double width to accommodate both plots
    height=class="num">750,
    showlegend=True,
    title_text=f"{symbol} Combined Analysis"
)

# Save interactive HTML
fig.write_html(save_dir / f&class="macro">#x27;{symbol}_combined_view.html&class="macro">#x27;)

# Create additional plots with smoothed data(unchanged)
fig2 = make_subplots(rows=class="num">2, cols=class="num">2,
                    subplot_titles=(&class="macro">#x27;Smoothed Price&class="macro">#x27;, &class="macro">#x27;Smoothed Volume&class="macro">#x27;,
                                   &class="macro">#x27;Smoothed Price Volatility&class="macro">#x27;, &class="macro">#x27;Smoothed Volume Volatility&class="macro">#x27;))

fig2.add_trace(
    go.Scatter(x=np.arange(len(df_smooth)), y=df_smooth[&class="macro">#x27;close_smooth&class="macro">#x27;],
               name=&class="macro">#x27;Price MA100&class="macro">#x27;),
    row=class="num">1, col=class="num">1
)

fig2.add_trace(
    go.Scatter(x=np.arange(len(df_smooth)), y=df_smooth[&class="macro">#x27;tick_volume_smooth&class="macro">#x27;],
               name=&class="macro">#x27;Volume MA100&class="macro">#x27;),

「用 Plotly 把波动平滑结果铺成四宫格」

这段脚本把价格与成交量的平滑序列拆成 2×2 子图,方便横向比对原始扰动被 MA100 滤掉多少。row=1,col=2 放的是价格类平滑轨,row=2 两格分别塞 price_volatility_smooth 与 volume_volatility_smooth,坐标直接用 np.arange(len(df_smooth)) 按行索引对齐,避免时间轴错位。 布局写死 height=750、width=750,showlegend 开true,标题用 f"{symbol} Smoothed Data Analysis" 动态带入品种名。最后 fig2.write_html 落盘成 {symbol}_smoothed_analysis.html,你在浏览器打开就能拖拽缩放看局部。 整段包在 try/except 里,任何绘图异常会 print 错误并 raise,不会静默产出空图。外汇与贵金属波动受杠杆放大,这类可视化只帮你看结构,不代表方向判断,实盘仍属高风险。

MQL5 / C++
    row=class="num">1, col=class="num">2
    )
    
    fig2.add_trace(
        go.Scatter(x=np.arange(len(df_smooth)), y=df_smooth[&class="macro">#x27;price_volatility_smooth&class="macro">#x27;],
                   name=&class="macro">#x27;Price Vol MA100&class="macro">#x27;),
        row=class="num">2, col=class="num">1
    )
    
    fig2.add_trace(
        go.Scatter(x=np.arange(len(df_smooth)), y=df_smooth[&class="macro">#x27;volume_volatility_smooth&class="macro">#x27;],
                   name=&class="macro">#x27;Volume Vol MA100&class="macro">#x27;),
        row=class="num">2, col=class="num">2
    )
    
    fig2.update_layout(
        height=class="num">750,
        width=class="num">750,
        showlegend=True,
        title_text=f"{symbol} Smoothed Data Analysis"
    )
    
    fig2.write_html(save_dir / f&class="macro">#x27;{symbol}_smoothed_analysis.html&class="macro">#x27;)
    
    print(f"Interactive visualizations saved in {save_dir}")
    
    except Exception as e:
        print(f"Error creating interactive visualization: {e}")
        raise

把行情塞进 3D 砖块里看密度

做价格行为分析时,二维 K 线容易让人忽略「点密度」这个维度。把 EURUSD 的报价按日期区间拉出来,重建成 3D 砖块图,往往能直观看到趋势起点和终点的空间分布差异。 我们先取 EURUSD 从 2017-01-01 到 2018-02-01 的数据,这段是强看涨趋势;再取 2018-02-01 到 2020-03-20 的段落,对应一段看跌趋势。在 3D 渲染下,两类趋势不管多空,起步都出现在点密度偏下方的稀疏区,而收尾段普遍被亮黄色配色标记——这是一个可复现的视觉现象,不是指标信号。 下面这段 Python 函数(接 MT5 终端数据)就是生成这类 3D 静态特征的核心。它不再依赖单根柱索引,而是用 copy_rates_range 按起止日期捞全段数据,再用价差倍数定砖块最小价格步长。 别把正态当圣经 代码里 min_spread_multiplier=45、volume_brick=500 只是作者当时用的默认值;点差大的贵金属(如 XAUUSD)直接套用可能砖块过大、样本被吞,建议先打印 symbol_info.spread 再手调倍数。外汇和贵金属杠杆高,视觉规律只辅助判结构,不预示方向。

MQL5 / C++
def create_true_3d_renko(symbol, timeframe, start_date, end_date, min_spread_multiplier=class="num">45, volume_brick=class="num">500):
    """
    Creates 4D stationary features with same interface as 3D Renko
    """
    rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date)
    if rates is None:
        print(f"Error getting data for {symbol}")
        class="kw">return None, None
        
    df = pd.DataFrame(rates)
    df[&class="macro">#x27;time&class="macro">#x27;] = pd.to_datetime(df[&class="macro">#x27;time&class="macro">#x27;], unit=&class="macro">#x27;s&class="macro">#x27;)

    if df.isnull().any().any():
        print("Missing values detected, cleaning...")
        df = df.dropna()
        if len(df) == class="num">0:
            print("No data for analysis after cleaning")
            class="kw">return None, None

    symbol_info = mt5.symbol_info(symbol)
    if symbol_info is None:
        print(f"Failed to get symbol info for {symbol}")
        class="kw">return None, None

    try:
        min_price_brick = symbol_info.spread * min_spread_multiplier * symbol_info.point
        if min_price_brick <= class="num">0:
            print("Invalid block size")
            class="kw">return None, None
    except AttributeError as e:
        print(f"Error getting symbol parameters: {e}")
        class="kw">return None, None

    scaler = MinMaxScaler(feature_range=(class="num">3, class="num">9))
    df_blocks = []

    try:
        # Time dimension
        df[&class="macro">#x27;time_sin&class="macro">#x27;] = np.sin(class="num">2 * np.pi * df[&class="macro">#x27;time&class="macro">#x27;].dt.hour / class="num">24)
        df[&class="macro">#x27;time_cos&class="macro">#x27;] = np.cos(class="num">2 * np.pi * df[&class="macro">#x27;time&class="macro">#x27;].dt.hour / class="num">24)
        df[&class="macro">#x27;time_numeric&class="macro">#x27;] = (df[&class="macro">#x27;time&class="macro">#x27;] - df[&class="macro">#x27;time&class="macro">#x27;].min()).dt.total_seconds()
        
        # Price dimension
        df[&class="macro">#x27;typical_price&class="macro">#x27;] = (df[&class="macro">#x27;high&class="macro">#x27;] + df[&class="macro">#x27;low&class="macro">#x27;] + df[&class="macro">#x27;close&class="macro">#x27;]) / class="num">3
        df[&class="macro">#x27;price_return&class="macro">#x27;] = df[&class="macro">#x27;typical_price&class="macro">#x27;].pct_change()
        df[&class="macro">#x27;price_acceleration&class="macro">#x27;] = df[&class="macro">#x27;price_return&class="macro">#x27;].diff()

◍ 把量价波动压成可喂模型的区块

这段处理逻辑干的事,是把逐根 K 线的量、价、波动率分别做成变化率与加速度,再滑窗拼成一个固定长度的样本块。注意它用的窗口是 20 根:从 idx-20 到 idx 闭区间,意味着每个 block 都带 21 行上下文,trend_count 字段直接写成 len(window) 也就是 21。 成交量维度先算 tick_volume 的 pct_change 得 volume_change,再对 volume_change 做 diff 得 volume_acceleration;波动率维度则用 price_return 的 20 周期滚动 std 算 volatility,再取它的 pct_change。这两组派生量后面都被塞进 block 的 open/high/low/close/tick_volume 里,名字虽沿用了 OHLCV 外壳,实际装的是加速度和变化率。 循环从 range(20, len(df)) 起步,跳过前 20 根没法定窗的样本。block 里 time_numeric 过了一次 scaler.fit_transform,把单值压成标量;direction 用 np.sign 取价格收益方向,非 +1 即 -1(或 0)。spread 和 type 字段其实塞的是 time_sin / time_cos,属于把周期时间编码混进了价差位,跑实盘前最好确认这俩字段不会被下游误读成真实点差。 异常分支里若 df_blocks 为空就直接返回 None, None,调用方得自己处理空样本。外汇与贵金属波动剧烈,这种滑窗重构在跳空时段可能让 volume_acceleration 出现极端值,上 MT5 导 tick 数据复算前建议先截断异常 bar。

MQL5 / C++
df[&class="macro">#x27;volume_change&class="macro">#x27;] = df[&class="macro">#x27;tick_volume&class="macro">#x27;].pct_change()
df[&class="macro">#x27;volume_acceleration&class="macro">#x27;] = df[&class="macro">#x27;volume_change&class="macro">#x27;].diff()

df[&class="macro">#x27;volatility&class="macro">#x27;] = df[&class="macro">#x27;price_return&class="macro">#x27;].rolling(class="num">20).std()
df[&class="macro">#x27;volatility_change&class="macro">#x27;] = df[&class="macro">#x27;volatility&class="macro">#x27;].pct_change()

for idx in range(class="num">20, len(df)):
    window = df.iloc[idx-class="num">20:idx+class="num">1]
    
    block = {
        &class="macro">#x27;time&class="macro">#x27;: df.iloc[idx][&class="macro">#x27;time&class="macro">#x27;],
        &class="macro">#x27;time_numeric&class="macro">#x27;: scaler.fit_transform([[class="type">class="kw">float(df.iloc[idx][&class="macro">#x27;time_numeric&class="macro">#x27;])]]).item(),
        &class="macro">#x27;open&class="macro">#x27;: class="type">class="kw">float(window[&class="macro">#x27;price_return&class="macro">#x27;].iloc[-class="num">1]),
        &class="macro">#x27;high&class="macro">#x27;: class="type">class="kw">float(window[&class="macro">#x27;price_acceleration&class="macro">#x27;].iloc[-class="num">1]),
        &class="macro">#x27;low&class="macro">#x27;: class="type">class="kw">float(window[&class="macro">#x27;volume_change&class="macro">#x27;].iloc[-class="num">1]),
        &class="macro">#x27;close&class="macro">#x27;: class="type">class="kw">float(window[&class="macro">#x27;volatility_change&class="macro">#x27;].iloc[-class="num">1]),
        &class="macro">#x27;tick_volume&class="macro">#x27;: class="type">class="kw">float(window[&class="macro">#x27;volume_acceleration&class="macro">#x27;].iloc[-class="num">1]),
        &class="macro">#x27;direction&class="macro">#x27;: np.sign(window[&class="macro">#x27;price_return&class="macro">#x27;].iloc[-class="num">1]),
        &class="macro">#x27;spread&class="macro">#x27;: class="type">class="kw">float(df.iloc[idx][&class="macro">#x27;time_sin&class="macro">#x27;]),
        &class="macro">#x27;type&class="macro">#x27;: class="type">class="kw">float(df.iloc[idx][&class="macro">#x27;time_cos&class="macro">#x27;]),
        &class="macro">#x27;trend_count&class="macro">#x27;: len(window),
        &class="macro">#x27;price_change&class="macro">#x27;: class="type">class="kw">float(window[&class="macro">#x27;price_return&class="macro">#x27;].mean()),
        &class="macro">#x27;volume_intensity&class="macro">#x27;: class="type">class="kw">float(window[&class="macro">#x27;volume_change&class="macro">#x27;].mean()),
        &class="macro">#x27;price_velocity&class="macro">#x27;: class="type">class="kw">float(window[&class="macro">#x27;price_acceleration&class="macro">#x27;].mean())
    }
    df_blocks.append(block)

except Exception as e:
    print(f"Error processing data: {e}")
    if len(df_blocks) == class="num">0:
        class="kw">return None, None

if len(df_blocks) == class="num">0:

「特征缩放与统计指标拼装」

砖块 DataFrame 成型后,先剥离 time 与 direction 两列,对其余所有特征列做标准化。scaler.fit_transform 会按列重定中心并压缩量纲,避免后续模型被 close 的绝对值带偏。 紧接着补一组与原函数同构的分析量:ma_5 用 close 的 5 周期滚动均值,ma_20 取 20 周期;volume_ma_5 基于 tick_volume 的 5 周期均值。price_volatility 与 volume_volatility 分别是 price_change、tick_volume 的 10 周期滚动标准差,trend_strength 则由 trend_count 乘 direction 得到方向加权趋势强度。 这批衍生列再走一遍 fit_transform 缩放。随后用 stats.zscore 对 close、tick_volume 算标准分(nan_policy='omit' 跳过缺失),zscore 两列同样归一化后随表返回,同时吐出 min_price_brick。 main 里先 mt5.initialize() 失败就打印错误退出;实盘符号锁 EURUSD、GBPUSD,周期只取 M15。回测窗口写死 2017-01-01 至 2018-02-01,砖块参数 min_spread_multiplier=45、volume_brick=500——外汇与贵金属杠杆高,这套参数直接上实盘前务必在 MT5 策略测试器重跑确认。

MQL5 / C++
    result_df = pd.DataFrame(df_blocks)
    
    # Scale all features
    features_to_scale = [col for col in result_df.columns if col != &class="macro">#x27;time&class="macro">#x27; and col != &class="macro">#x27;direction&class="macro">#x27;]
    result_df[features_to_scale] = scaler.fit_transform(result_df[features_to_scale])
    
    # Add same analytical metrics as in original function
    result_df[&class="macro">#x27;ma_5&class="macro">#x27;] = result_df[&class="macro">#x27;close&class="macro">#x27;].rolling(class="num">5).mean()
    result_df[&class="macro">#x27;ma_20&class="macro">#x27;] = result_df[&class="macro">#x27;close&class="macro">#x27;].rolling(class="num">20).mean()
    result_df[&class="macro">#x27;volume_ma_5&class="macro">#x27;] = result_df[&class="macro">#x27;tick_volume&class="macro">#x27;].rolling(class="num">5).mean()
    result_df[&class="macro">#x27;price_volatility&class="macro">#x27;] = result_df[&class="macro">#x27;price_change&class="macro">#x27;].rolling(class="num">10).std()
    result_df[&class="macro">#x27;volume_volatility&class="macro">#x27;] = result_df[&class="macro">#x27;tick_volume&class="macro">#x27;].rolling(class="num">10).std()
    result_df[&class="macro">#x27;trend_strength&class="macro">#x27;] = result_df[&class="macro">#x27;trend_count&class="macro">#x27;] * result_df[&class="macro">#x27;direction&class="macro">#x27;]
    
    # Scale moving averages and volatility
    ma_columns = [&class="macro">#x27;ma_5&class="macro">#x27;, &class="macro">#x27;ma_20&class="macro">#x27;, &class="macro">#x27;volume_ma_5&class="macro">#x27;, &class="macro">#x27;price_volatility&class="macro">#x27;, &class="macro">#x27;volume_volatility&class="macro">#x27;, &class="macro">#x27;trend_strength&class="macro">#x27;]
    result_df[ma_columns] = scaler.fit_transform(result_df[ma_columns])
    
    # Add statistical metrics and scale them
    result_df[&class="macro">#x27;zscore_price&class="macro">#x27;] = stats.zscore(result_df[&class="macro">#x27;close&class="macro">#x27;], nan_policy=&class="macro">#x27;omit&class="macro">#x27;)
    result_df[&class="macro">#x27;zscore_volume&class="macro">#x27;] = stats.zscore(result_df[&class="macro">#x27;tick_volume&class="macro">#x27;], nan_policy=&class="macro">#x27;omit&class="macro">#x27;)
    zscore_columns = [&class="macro">#x27;zscore_price&class="macro">#x27;, &class="macro">#x27;zscore_volume&class="macro">#x27;]
    result_df[zscore_columns] = scaler.fit_transform(result_df[zscore_columns])
    
    class="kw">return result_df, min_price_brick

def main():
    try:
        # Initialize MT5
        if not mt5.initialize():
            print("MetaTrader5 initialization error")
            class="kw">return
        # Analysis parameters
        symbols = ["EURUSD", "GBPUSD"]
        timeframes = {
            "M15": mt5.TIMEFRAME_M15
        }
        
        # 7D analysis parameters
        params = {
            "min_spread_multiplier": class="num">45,
            "volume_brick": class="num">500
        }
        # Date range for data fetching
        start_date = class="type">class="kw">datetime(class="num">2017, class="num">1, class="num">1)
        end_date = class="type">class="kw">datetime(class="num">2018, class="num">2, class="num">1)
        # Analysis for each symbol and timeframe
        for symbol in symbols:
            print(f"\nAnalyzing symbol {symbol}")

按品种和周期拆 Renko 数据的目录与取样逻辑

这段 Python 片段干的事很直接:为每一个交易品种建独立的 charts/ 子目录,再往下按周期建二级目录,把后续生成的 7D Renko 数据分门别类丢进去。 symbol_dir = Path('charts') / symbol symbol_dir.mkdir(parents=True, exist_ok=True) 上面两行先拼路径再建目录,parents=True 保证多级路径一次成型,exist_ok=True 避免重复跑脚本时报错中断。 取符号信息时若 symbol_info 返回 None,说明 MT5 终端没加载该品种或连接断了,脚本会打印失败并 continue 跳过,不会让整个循环崩掉。 symbol_info = mt5.symbol_info(symbol) if symbol_info is None: print(f"Failed to get symbol info for {symbol}") continue print(f"Spread: {symbol_info.spread} points") print(f"Tick: {symbol_info.point}") 实跑时你能直接看到点差(points)和最小变动单位(point),这两个数是后面算砖块尺寸的底料。 随后按 timeframes 字典逐个周期处理,每个周期建 tf_dir,调用 create_true_3d_renko 拉数据。若返回 df 非空且 brick_size 非空,会打印如「Created 412 7D bars」「Block size: 0.00015」这类结果——这就是可验证的数据点,你在终端里改 min_spread_multiplier 或 volume_brick 后,砖块数和尺寸会随之变化。 for tf_name, tf in timeframes.items(): tf_dir = symbol_dir / tf_name tf_dir.mkdir(exist_ok=True) df, brick_size = create_true_3d_renko( symbol=symbol, timeframe=tf, start_date=start_date, end_date=end_date, min_spread_multiplier=params["min_spread_multiplier"], volume_brick=params["volume_brick"]) if df is not None and brick_size is not None: print(f"Created {len(df)} 7D bars") print(f"Block size: {brick_size}") 外汇与贵金属波动受杠杆和消息面影响大,点差跳变会直接改砖块粒度,复盘时务必用同一时段的实时点差校准参数。

MQL5 / C++
symbol_dir = Path(&class="macro">#x27;charts&class="macro">#x27;) / symbol
symbol_dir.mkdir(parents=True, exist_ok=True)

symbol_info = mt5.symbol_info(symbol)
if symbol_info is None:
    print(f"Failed to get symbol info for {symbol}")
    class="kw">continue
print(f"Spread: {symbol_info.spread} points")
print(f"Tick: {symbol_info.point}")

for tf_name, tf in timeframes.items():
    print(f"\nAnalyzing timeframe {tf_name}")

    tf_dir = symbol_dir / tf_name
    tf_dir.mkdir(exist_ok=True)

    print("Getting data...")
    df, brick_size = create_true_3d_renko(
        symbol=symbol,
        timeframe=tf,
        start_date=start_date,
        end_date=end_date,
        min_spread_multiplier=params["min_spread_multiplier"],
        volume_brick=params["volume_brick"]
    )

    if df is not None and brick_size is not None:
        print(f"Created {len(df)} 7D bars")
        print(f"Block size: {brick_size}")

        print("\nBasic statistics:")

◍ 把行情统计结果落盘并抓异常

这段脚本负责把 7 天 tick 数据跑完后的关键统计打印出来并写文件。平均 tick_volume、平均趋势长度、最大单向趋势长度都会直接输出,方便你快速判断该周期是否处于低波动磨蹭段还是单边延展段。 顺手做了多空比例与量价相关:trend_ratio 用上涨 bar 数除以下跌 bar 数,volume_corr 是 tick_volume 与价格绝对变动的皮尔逊系数。若 ratio 明显偏离 1.0 或 corr 接近 0,说明当前品种在该周期量价驱动逻辑可能偏弱。 数据会存成 {symbol}_{tf_name}_7d_data.csv,后续可用 Excel 或 MT5 外的 Python 环境复算。若 price_volatility 峰值超过均值 3 倍、或 volume_volatility 同样破 3 倍阈值,脚本会打印 WARNING——这类时段外汇与贵金属跳空概率偏高,实盘需降杠杆或回避。 别把 3 倍阈值当铁律 不同品种流动性结构差异大,XAUUSD 的 volume_volatility 尖峰往往比 EURUSD 更频繁,建议先对自己常做的品种跑一周再定阈值。

MQL5 / C++
print(f"Average volume: {df[&class="macro">#x27;tick_volume&class="macro">#x27;].mean():.2f}")
print(f"Average trend length: {df[&class="macro">#x27;trend_count&class="macro">#x27;].mean():.2f}")
print(f"Max uptrend length: {df[df[&class="macro">#x27;direction&class="macro">#x27;] > class="num">0][&class="macro">#x27;trend_count&class="macro">#x27;].max()}")
print(f"Max downtrend length: {df[df[&class="macro">#x27;direction&class="macro">#x27;] < class="num">0][&class="macro">#x27;trend_count&class="macro">#x27;].max()}")

# Create visualizations
print("\nCreating visualizations...")
create_visualizations(df, symbol, tf_dir)

# Save data
csv_file = tf_dir / f"{symbol}_{tf_name}_7d_data.csv"
df.to_csv(csv_file)
print(f"Data saved to {csv_file}")

# Results analysis
trend_ratio = len(df[df[&class="macro">#x27;direction&class="macro">#x27;] > class="num">0]) / len(df[df[&class="macro">#x27;direction&class="macro">#x27;] < class="num">0])
print(f"\nUp/Down bars ratio: {trend_ratio:.2f}")

volume_corr = df[&class="macro">#x27;tick_volume&class="macro">#x27;].corr(df[&class="macro">#x27;price_change&class="macro">#x27;].abs())
print(f"Volume-Price change correlation: {volume_corr:.2f}")

# Print warnings if anomalies detected
if df[&class="macro">#x27;price_volatility&class="macro">#x27;].max() > df[&class="macro">#x27;price_volatility&class="macro">#x27;].mean() * class="num">3:
    print("\nWARNING: High volatility periods detected!")

if df[&class="macro">#x27;volume_volatility&class="macro">#x27;].max() > df[&class="macro">#x27;volume_volatility&class="macro">#x27;].mean() * class="num">3:

「异常量能报警与脚本收尾的容错写法」

这段 MQL5 联动 Python 的收尾代码,核心是在 3D 量柱生成后做异常量能预警,并保证终端一定被关闭。 当检测到异常成交量尖峰时,脚本会直接打印 WARNING 提示,方便你在盯盘时快速定位非常规流动性涌入的时段;若 3D 柱体构建失败,则输出具体品种与周期名称,便于排查是品种不可交易还是周期参数错误。 整个逻辑包在 try-except-finally 里:任何异常都会打印错误栈并调用 traceback,而 finally 中的 mt5.shutdown() 不论成功失败都执行,避免 MT5 终端残留连接占用端口。外汇与贵金属市场高杠杆、流动性突变频繁,这类异常量能提示仅作概率性参考,不代表后续方向。 把这段接在你自己的 3D 量柱脚本末尾,跑一遍 XAUUSD 的 M15,看能否稳定触发 shutdown 且不报端口占用,就能验证容错是否生效。

MQL5 / C++
            print("WARNING: Abnormal volume spikes detected!")
            else:
                print(f"Failed to create 3D bars for {symbol} on {tf_name}")
            
            print("\nAnalysis completed successfully!")
            
    except Exception as e:
        print(f"An error occurred: {e}")
        class="kw">import traceback
        print(traceback.format_exc())
    finally:
        mt5.shutdown()

把价格拆成趋势、量与噪声的三维积分

这个公式把任一时刻 t 的货币价格 P(t) 写成初始价 P_0 加上一个指数加权积分,再叠一层随机噪声 N(t)。积分核 A·e^{k(t-u)}·V(u) 里,A 是趋势振幅,k 控制指数速率:k>0 偏多头扩散,k<0 偏空头衰减,V(u) 是历史成交量在 u 时刻的权重。 实际盯盘时,可以把 V(u) 想成「市场愿不愿意为这段趋势买单」。同样大小的 A 和 k,若 V(u) 在近期放大,积分贡献会被显著抬高,价格偏离 P_0 的速度更快;若 V(u) 萎缩,趋势即使存在也像在虚推。

把时间、价格、成交量丢进 3D 空间后,曲面的亮度其实在编码价格导数与成交量的联合强度。亮带往往对应dP/dt与 V 同时走高,意味着一段有量确认的运动正在发生;暗区则多是噪声主导。外汇与贵金属杠杆高,这类亮度信号只提示概率倾斜,不等于方向锁死,开 MT5 用分时成交量叠加权均线可自行验证亮度带与突破的重合度。

◍ 反转时刻的黄色累积带

把 2023 年 4 月 21 日到 8 月 10 日的周线丢进 3D 归一化坐标,看涨趋势在 8 月 10 日终结,坐标底部出现熟悉的黄色累积带。这和 2024 年 9 月 13 日至 10 月 10 日那段日线很像——反转发生在区间中段,黄色沉到下方而非上方。

  • 年 8 月 19 日至 30 日仅 12 根日线,趋势精确在中间反身,坐标里黄带依旧贴着底部。7 月 17 日至 8 月 8 日那段,模型在末端提前铺出黄迹,倾向提示反转概率上升。
  • 月 14 日至 28 日这段,反转约落在第 8 日前后,黄带回到归一化价格上升侧。多个样本一致显示:黄带位置从顶移底,往往对应下跌转上涨的临界点;外汇与贵金属杠杆高,此类信号仅作概率参考,须结合实时波动验证。

「黄色聚类是反转前的高频信号」

在 3D 柱状图里,黄色成交量-波动率聚类是个值得单独拎出来的现象。我翻了 2022–2024 年超过 40 万根柱子的历史数据,其中约 10 万根是黄色柱,结果 97% 都落在价格反转的正负三根柱范围内。 反转总共约 16.9 万次,但只有 40% 伴随黄色柱出现。也就是说,黄色聚类几乎总在反转旁,但反转不一定非得有它——它更像高概率预警而非充要条件。 趋势运行阶段几乎看不到黄色,只有密集的普通 3D 柱;反转前它才成片冒出来。EURUSD 2017 初涨到 2018 年 2 月、再跌到 2020 年 3 月,两段长期行情的反转前都有黄色聚类抢先站位。 短期也成立。抽 2024 年中几个 2–3 周片段看,每次反转前黄色柱都准时出现。外汇和贵金属波动大、杠杆高,看到聚类先减仓或缩周期验证,别直接反手重仓。

三维视角才刚掀开一角

把价格、成交量、时间叠成 3D 柱状图后,最实用的收获是趋势反转前能看到成交量在柱体里的特征性堆积,以及颜色随动能切换——这在纯 K 线平面里容易被压平看不见。作者用大量历史实例做过验证,这类堆积对即将到来的反转有偏高概率的指示性,但不是确定性信号。 真正落地价值在定量:配套数学模型不止画图,还能给每个三维结构算权重,MT5 配合外部 Python 脚本(文末 ZIP 里的 3D_Bars_Visual.py 约 19.48 KB)就能复现。外汇与贵金属杠杆高、滑点跳空频繁,三维信号也得先上历史数据回测再上实盘。 下一步值得试的是把机器学习接进来自动认 3D 形态,或基于多变量直接出策略。眼下你开 MT5 把成交量维度调出来,对照前几波反转找堆积规律,比空谈多因子更靠前一步。

让小布替你跑这套
这些 3D 柱的成交量分布与买卖失衡诊断,小布盯盘的 AIGC 模块已内置,打开对应品种页即可看到实时体积快照,你只管判方向。

常见问题

体积结构从首笔 tick 就开始累积,能比二维信号更早暴露强劲走势,且直接呈现成交量与价格区间的耦合关系,降低滞后误判概率。
可以,小布盯盘对应品种页内置了 AIGC 驱动的微观结构视图,自动绘制体积柱并标记聚类反转,省去自行搭 Python 链路的重复劳动。
黄色聚类通常指向特定价格区间内成交量异常集中的区域,可能暗示头寸积累或派发,结合深度分布可辅助判断支撑阻力有效性。
N(t) 一般建模随机噪声或微观扰动,让价格演化贴合真实 tick 流而非纯确定性积分,实战中用于过滤低置信度波动。
外汇贵金属高杠杆高风险,3D 柱仅提供概率倾向而非方向保证,需配合仓位控制与多周期印证,避免单维度信号直接下单。