如何在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
相关产品推荐
相关产品推荐

