You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在TradingView Pine Script v5中制作类Excel簇状柱形图?

在TradingView Pine Script v5中实现时段/星期涨跌幅簇状柱形图

问题描述

我开发了一款TradingView Pine Script v5指标,用于小时时间框架下计算外汇品种按交易时段(东京、伦敦、纽约)和星期几划分的涨跌幅百分比(%chg)及基点变动(bps chg)。希望将数据展示为类Excel的簇状柱形图:每周一至周五,每天对应3个柱形(分属3个交易时段),总计15个柱形。

当前版本代码(可能存在错误):

//@version=5
indicator("BPS Day of Week", overlay=false)

// Input for the number of weeks to average
numWeeks = input.int(defval=1, title="Number of Weeks to Average", minval=1, maxval=100000)

// Input to toggle between percentage or basis point display
displayMode = input.string(defval="Percentage", title="Display Mode", options=["Percentage", "Basis Points"])

// Get the hour for New York and London based on their respective timezones (handles DST automatically)
nyHour = hour(time, "America/New_York")
londonHour = hour(time, "Europe/London")
utcHour = hour(time, "Etc/UTC")  // UTC doesn't use DST

// Tokyo session (no DST adjustments needed)
tokyoSessionStart = 21  // Starts at 21:00 UTC
tokyoSessionEnd = 8     // Ends at 08:00 UTC

// Define the trading sessions using DST-aware logic
isTokyoSession = (utcHour >= tokyoSessionStart or utcHour < tokyoSessionEnd)  // 21:00 PM - 08:00     AM UTC
isLondonSession = (londonHour >= 7 and londonHour < 17)  // London: 7:00 AM - 5:00 PM local time
isNewYorkSession = (nyHour >= 7 and nyHour < 17)  // New York: 7:00 AM - 5:00 PM local time

// Define variables to store session open prices
var float sessionOpenPriceTokyo = na
var float sessionOpenPriceLondon = na
var float sessionOpenPriceNewYork = na

// Calculate percentage or basis point change
percentChange(openPrice, closePrice) =>
    (closePrice / openPrice - 1) * 100

basisPointChange(openPrice, closePrice) =>
    (closePrice - openPrice) * 10000

// Initialize arrays for each session and day of the week
var float[] tokyoChangesMonday = array.new_float(0)
var float[] londonChangesMonday = array.new_float(0)
var float[] newYorkChangesMonday = array.new_float(0)
var float[] tokyoChangesTuesday = array.new_float(0)
var float[] londonChangesTuesday = array.new_float(0)
var float[] newYorkChangesTuesday = array.new_float(0)
var float[] tokyoChangesWednesday = array.new_float(0)
var float[] londonChangesWednesday = array.new_float(0)
var float[] newYorkChangesWednesday = array.new_float(0)
var float[] tokyoChangesThursday = array.new_float(0)
var float[] londonChangesThursday = array.new_float(0)
var float[] newYorkChangesThursday = array.new_float(0)
var float[] tokyoChangesFriday = array.new_float(0)
var float[] londonChangesFriday = array.new_float(0)
var float[] newYorkChangesFriday = array.new_float(0)

// Store percentage or basis point change for each session by day of the week
storeSessionChange(dayArray, openPrice, closePrice) =>
    currentChange = displayMode == "Basis Points" ? basisPointChange(openPrice, closePrice) :  percentChange(openPrice, closePrice)
    if array.size(dayArray) >= numWeeks
        array.shift(dayArray)  // Remove oldest element
    array.push(dayArray, currentChange)

// Handle open price resets
if (isTokyoSession and not isTokyoSession[1])
    sessionOpenPriceTokyo := open
if (isLondonSession and not isLondonSession[1])
    sessionOpenPriceLondon := open
if (isNewYorkSession and not isNewYorkSession[1])
     sessionOpenPriceNewYork := open

// Handle the end of sessions (store session changes)
if (not isTokyoSession and isTokyoSession[1]) 
    if dayofweek == dayofweek.monday
        storeSessionChange(tokyoChangesMonday, sessionOpenPriceTokyo, close)
    else if dayofweek == dayofweek.tuesday
        storeSessionChange(tokyoChangesTuesday, sessionOpenPriceTokyo, close)
    else if dayofweek == dayofweek.wednesday
        storeSessionChange(tokyoChangesWednesday, sessionOpenPriceTokyo, close)
    else if dayofweek == dayofweek.thursday
        storeSessionChange(tokyoChangesThursday, sessionOpenPriceTokyo, close)
    else if dayofweek == dayofweek.friday
        storeSessionChange(tokyoChangesFriday, sessionOpenPriceTokyo, close)


if (not isLondonSession and isLondonSession[1]) 
    if dayofweek == dayofweek.monday
        storeSessionChange(londonChangesMonday, sessionOpenPriceLondon, close)
    else if dayofweek == dayofweek.tuesday
        storeSessionChange(londonChangesTuesday, sessionOpenPriceLondon, close)
    else if dayofweek == dayofweek.wednesday
        storeSessionChange(londonChangesWednesday, sessionOpenPriceLondon, close)
    else if dayofweek == dayofweek.thursday
        storeSessionChange(londonChangesThursday, sessionOpenPriceLondon, close)
    else if dayofweek == dayofweek.friday
        storeSessionChange(londonChangesFriday, sessionOpenPriceLondon, close)


if (not isNewYorkSession and isNewYorkSession[1]) 
    if dayofweek == dayofweek.monday
        storeSessionChange(newYorkChangesMonday, sessionOpenPriceNewYork, close)
    else if dayofweek == dayofweek.tuesday
        storeSessionChange(newYorkChangesTuesday, sessionOpenPriceNewYork, close)
    else if dayofweek == dayofweek.wednesday
        storeSessionChange(newYorkChangesWednesday, sessionOpenPriceNewYork, close)
    else if dayofweek == dayofweek.thursday
        storeSessionChange(newYorkChangesThursday, sessionOpenPriceNewYork, close)
    else if dayofweek == dayofweek.friday
        storeSessionChange(newYorkChangesFriday, sessionOpenPriceNewYork, close)


// Function to calculate the average change per session for each day of the week
averageChange(changesArray) =>
    total = 0.0
    count = array.size(changesArray)
    if count > 0
        for i = 0 to count - 1
            total := total + array.get(changesArray, i)
        total / count
    else
        na

// Calculate averages for each day of the week
mondayTokyoAvg = averageChange(tokyoChangesMonday)
mondayLondonAvg = averageChange(londonChangesMonday)
mondayNewYorkAvg = averageChange(newYorkChangesMonday)
tuesdayTokyoAvg = averageChange(tokyoChangesTuesday)
tuesdayLondonAvg = averageChange(londonChangesTuesday)
tuesdayNewYorkAvg = averageChange(newYorkChangesTuesday)
wednesdayTokyoAvg = averageChange(tokyoChangesWednesday)
wednesdayLondonAvg = averageChange(londonChangesWednesday)
wednesdayNewYorkAvg = averageChange(newYorkChangesWednesday)
thursdayTokyoAvg = averageChange(tokyoChangesThursday)
thursdayLondonAvg = averageChange(londonChangesThursday)
thursdayNewYorkAvg = averageChange(newYorkChangesThursday)
fridayTokyoAvg = averageChange(tokyoChangesFriday)
fridayLondonAvg = averageChange(londonChangesFriday)
fridayNewYorkAvg = averageChange(newYorkChangesFriday)

// Use table.new to manually display averages in a table
var table columnTable = table.new(position.top_right, 4, 6, border_width=1)

// Update the table periodically
if bar_index % 1 == 0
    table.cell(columnTable, 0, 0, "Session", bgcolor=color.black, text_color=color.yellow)
    table.cell(columnTable, 1, 0, "Tokyo", bgcolor=color.black, text_color=color.red)
    table.cell(columnTable, 2, 0, "London", bgcolor=color.black, text_color=color.blue)
    table.cell(columnTable, 3, 0, "New York", bgcolor=color.black, text_color=color.green)

    table.cell(columnTable, 0, 1, "Monday Avg", bgcolor=color.black, text_color=color.white)
    table.cell(columnTable, 1, 1, str.tostring(mondayTokyoAvg, "#.##"), bgcolor=color.black, text_color=color.red)
    table.cell(columnTable, 2, 1, str.tostring(mondayLondonAvg, "#.##"), bgcolor=color.black, text_color=color.blue)
    table.cell(columnTable, 3, 1, str.tostring(mondayNewYorkAvg, "#.##"), bgcolor=color.black, text_color=color.green)

    table.cell(columnTable, 0, 2, "Tuesday Avg", bgcolor=color.black, text_color=color.white)
    table.cell(columnTable, 1, 2, str.tostring(tuesdayTokyoAvg, "#.##"), bgcolor=color.black, text_color=color.red)
    table.cell(columnTable, 2, 2, str.tostring(tuesdayLondonAvg, "#.##"), bgcolor=color.black, text_color=color.blue)
    table.cell(columnTable, 3, 2, str.tostring(tuesdayNewYorkAvg, "#.##"), bgcolor=color.black, text_color=color.green)

    table.cell(columnTable, 0, 3, "Wednesday Avg", bgcolor=color.black, text_color=color.white)
    table.cell(columnTable, 1, 3, str.tostring(wednesdayTokyoAvg, "#.##"), bgcolor=color.black, text_color=color.red)
    table.cell(columnTable, 2, 3, str.tostring(wednesdayLondonAvg, "#.##"), bgcolor=color.black, text_color=color.blue)
    table.cell(columnTable, 3, 3, str.tostring(wednesdayNewYorkAvg, "#.##"), bgcolor=color.black, text_color=color.green)

    table.cell(columnTable, 0, 4, "Thursday Avg", bgcolor=color.black, text_color=color.white)
    table.cell(columnTable, 1, 4, str.tostring(thursdayTokyoAvg, "#.##"), bgcolor=color.black, text_color=color.red)
    table.cell(columnTable, 2, 4, str.tostring(thursdayLondonAvg, "#.##"), bgcolor=color.black, text_color=color.blue)
    table.cell(columnTable, 3, 4, str.tostring(thursdayNewYorkAvg, "#.##"), bgcolor=color.black, text_color=color.green)

    table.cell(columnTable, 0, 5, "Friday Avg", bgcolor=color.black, text_color=color.white)
    table.cell(columnTable, 1, 5, str.tostring(fridayTokyoAvg, "#.##"), bgcolor=color.black, text_color=color.red)
    table.cell(columnTable, 2, 5, str.tostring(fridayLondonAvg, "#.##"), bgcolor=color.black, text_color=color.blue)
    table.cell(columnTable, 3, 5, str.tostring(fridayNewYorkAvg, "#.##"), bgcolor=color.black, text_color=color.green)

// Display labels for each session and day
displayLabel(labelText, avgValue, colorValue) =>
    if not na(avgValue)  // Ensure the average value is valid
        label.new(time, avgValue, labelText, xloc=xloc.bar_time, style=label.style_label_down,          color=colorValue, textcolor=color.white)

// Day offsets for proper label positioning
dayOffset(day) =>
    if (day == dayofweek.monday)
        1
    else if (day == dayofweek.tuesday)
        2
    else if (day == dayofweek.wednesday)
        3
    else if (day == dayofweek.thursday)
        4
    else
        5  // Friday

// Display average values for each day of the week (one label per session per day)
if dayofweek == dayofweek.monday
    displayLabel("Tokyo (Mon)", mondayTokyoAvg, color.red)
    displayLabel("London (Mon)", mondayLondonAvg, color.blue)
    displayLabel("New York (Mon)", mondayNewYorkAvg, color.green)

if dayofweek == dayofweek.tuesday
    displayLabel("Tokyo (Tue)", tuesdayTokyoAvg, color.red)
    displayLabel("London (Tue)", tuesdayLondonAvg, color.blue)
    displayLabel("New York (Tue)", tuesdayNewYorkAvg, color.green)

if dayofweek == dayofweek.wednesday
    displayLabel("Tokyo (Wed)", wednesdayTokyoAvg, color.red)
    displayLabel("London (Wed)", wednesdayLondonAvg, color.blue)
    displayLabel("New York (Wed)", wednesdayNewYorkAvg, color.green)

if dayofweek == dayofweek.thursday
    displayLabel("Tokyo (Thu)", thursdayTokyoAvg, color.red)
    displayLabel("London (Thu)", thursdayLondonAvg, color.blue)
    displayLabel("New York (Thu)", thursdayNewYorkAvg, color.green)

if dayofweek == dayofweek.friday
    displayLabel("Tokyo (Fri)", fridayTokyoAvg, color.red)
    displayLabel("London (Fri)", fridayLondonAvg, color.blue)
    displayLabel("New York (Fri)", fridayNewYorkAvg, color.green)

已尝试的方法及问题

  • 直方图(Histogram Bars):用plot.style_histogram绘制时,每小时K线都会生成柱形,远超预期的15个,因为Pine Script逐K线更新绘制。
  • 标签(Label):生成的标签数量过多,每小时重复绘制,位置计算混乱。
  • 星期几手动偏移:基于dayofweek设置偏移量,但受限于Pine Script仅允许在当前K线前后500根范围内绘制对象的限制,无法正确定位。
  • 基于time定位标签:仍存在对象超出绘制范围的问题,且标签重复生成。

核心矛盾:Pine Script逐K线重绘的机制,与仅按时段/星期绘制一次的需求冲突;同时label.new的位置限制进一步限制了布局灵活性。

解决方案:自定义形状实现簇状柱形图

自定义形状(shape.new)可以实现需求,但需要解决重复绘制和位置定位两个核心问题,具体思路如下:

1. 限制绘制触发时机

仅在最后一根K线(barstate.islast)触发绘制逻辑,避免逐K线重复生成形状。如果需要每周更新一次,可以结合dayofweek == dayofweek.friday判断:

if barstate.islast
    // 在此执行所有形状绘制逻辑

2. 固定X轴位置,模拟簇状布局

用xloc.bar_index替代时间定位,在图表右侧固定区域绘制15个柱形:

  • 给周一到周五分配基础X坐标(比如周一=bar_index-100,周二=bar_index-90,以此类推)
  • 同天内的三个时段柱形在基础X坐标上偏移±1,实现簇状排列(东京=X-1,伦敦=X,纽约=X+1)

3. 用矩形形状模拟柱形

使用shape.rect绘制填充矩形作为柱形,根据平均涨跌幅计算柱形高度,以0为基准区分正负:

// 示例:绘制周一东京时段柱形
if not na(mondayTokyoAvg)
    // 柱形X范围
    x1 = bar_index - 100
    x2 = x1 + 1
    // 柱形Y范围(正数值向上,负数值向下)
    yTop = mondayTokyoAvg > 0 ? mondayTokyoAvg : 0
    yBottom = mondayTokyoAvg < 0 ? mondayTokyoAvg : 0
    // 绘制填充矩形
    shape.new(xloc.bar_index, yTop, xloc.bar_index, yBottom,
              style=shape.style_rectangle_filled, color=color.red,
              x1=x1, x2=x2)

4. 优化后的核心绘制代码片段

//@version=5
indicator("Session Day-of-Week Cluster Chart", overlay=false)

// 省略原有输入、时段判断、数据存储及平均值计算逻辑...

// 仅在最后一根K线绘制簇状柱形
if barstate.islast
    // 布局参数
    startX = bar_index - 100  // 柱形起始位置(当前K线左侧100根)
    dayGap = 10               // 每天柱形组的间距
    sessionGap = 2            // 同天内时段柱形的间距
    barWidth = 1              // 单根柱形宽度
    
    // 配置时段颜色与名称
    sessionColors = [color.red, color.blue, color.green]
    sessionNames = ["Tokyo", "London", "NY"]
    dayNames = ["Mon", "Tue", "Wed", "Thu", "Fri
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 12:19:55