一位内测用户希望 WOWS 支持 Bar Replay(K 线回放)。这个看似简单的功能,最终涉及历史数据、实时订阅、K 线逐步生成、多图表隔离,以及可复用的指标配置。
标签
一位内测用户想要 K 线回放,于是我把它做进了 WOWS
把尚未完善的产品交给真实用户试用,有一点特别让我喜欢:他们很少在意你心里规划了什么路线图。
他们会直接开始用。
然后提出那些在他们看来显然缺少的功能。
测试我的市场分析与图表平台 WOWS 时,就发生了这样的事。
一位内测用户希望回到图表上的某个历史时点,再从那里逐步回放后续行情。
他的意思很直接:
“能不能把行情回放一遍,而不是只能看已经画好的图?”
这个问题,让我开始了项目的新一轮开发。
有趣的是,Bar Replay(K 线回放)听起来很简单。
隐藏右侧的 K 线,加一个播放按钮,再一根根显示出来。
从最基本的定义看,这确实就是 K 线回放。
但要让它自然融入一个真正可用的行情终端,就有趣得多。
第一版能用,但体验并不理想
最初的实现思路很直接:
Choose historical candle
↓
Hide everything after it
↓
Press Play
↓
Send the next historical bar
↓
Repeat技术上来说,这行得通。
视觉上,感觉糟透了。
图表会突然出现一根已经完成的 K 线,开盘价、最高价、最低价、收盘价与成交量一次性全部显示出来。
接着又出现一根。
接着又出现一根。
看起来更像逐页翻演示文稿,缺少观察市场变化的感觉。
这让我确定了第一个设计原则:
回放不应只是把历史显示出来,而应让人感觉行情正在逐步发生。
因此,我不再把历史 K 线当作直接显示的完整对象,而是将当前回放中的 K 线视为一根正在形成的实时 K 线。
K 线形成期间,系统会持续更新同一时间戳对应的数据。
当前 K 线完成后,才推进到下一个时间戳。
这一小小的区别改变了整个功能。
与现有图表协作,而非另起一套系统
WOWS 使用 TradingView Advanced Charts,但周边的市场数据接入与应用状态由我自己管理。
我不希望为了回放,再单独维护另一套图表实现。
否则,实时图表与历史模拟就会变成两套需要分别维护的系统。
我选择围绕现有数据源,为每个图表窗格建立独立的数据源适配器。
适配器位于图表与 WOWS 常规数据源之间:
TradingView Chart
↓
PaneDatafeedAdapter
↓
WOWS Datafeed正常使用情况下,它主要负责转发请求。
进入回放模式后,数据访问规则才会改变。
历史数据请求只能访问当前回放时间戳及之前的数据。
当前回放时点之后的 K 线不会被返回。
该窗格中的实时市场更新暂时被禁用。
如果当前 K 线尚未形成完毕,数据源只返回当前进度下的状态,避免意外暴露这根历史 K 线最终的完整数值。
在平移或缩放时,这一点尤为重要。
如果稍微向右拖动图表就能看到“未来”,回放便失去了意义。
接下来的问题:让 K 线逐步形成
妥善隐藏未来数据后,我希望当前 K 线能够逐步变化,而不是突然出现。
对于已经完成的历史 K 线,我知道以下数据:
Open
High
Low
Close
Volume但仅靠这五个数值,并不能准确还原该根 K 线内部的价格变化顺序。
例如,一根日 K 线开盘价为 100、最低价为 95、最高价为 108、收盘价为 106。仅凭 OHLC 数据,无法判断价格是先到 95,还是先到 108。
所以有两种方法。
第一种方式是合成插值:根据已知 OHLC 数值构建一条确定的模拟路径,逐步更新 K 线。
这种方式能较快实现流畅的 K 线生成效果,适用于只有当前较大周期数据的情况。
更好的方法是使用更短周期的历史 K 线。
例如:
Chart: 1D
Replay data:
09:30
09:45
10:00
10:15
...这些更短周期的历史 K 线,可以逐步聚合成同一根日 K 线。
这样,日 K 线就不只是朝最终状态播放一段模拟动画。
它是利用真实的短周期历史数据,重建当前 K 线内部的变化过程。
由此,我也明确区分了两个概念:
回放速度决定模拟多久推进一次。
回放步长决定每次推进代表多长的历史行情时间。
没有实际做过回放引擎时,这两个概念很容易混淆。
但它们控制的是不同的事情。
多图表支持:把回放状态隔离到窗格
WOWS 支持多个图表窗格。
这意味着 Replay 不能像这样使用一个全局布尔值:
isReplayMode = true;左侧窗格回放比特币时,右侧的实时股票图表不应跟着回到过去。
因此,每个图表窗格都有自己的:
回放控制器
数据源适配器
播放状态
历史数据边界
正在形成的 K 线
实时订阅处理
订阅标识也要划分命名空间,避免两个 TradingView 图表实例发生冲突。
这些细节不应成为用户需要操心的事。
因此,它们应该由底层架构妥善处理。
“暂停”比“播放”更值得推敲
一个很有代表性的 bug,发生在 K 线形成到一半时暂停回放的场景。
最初,暂停操作能够正确停止动画。
但再次点击播放后,系统却可能从开盘状态重新生成这根 K 线。
结果就是图表先回跳,再继续推进。
解决方法是保留动画自身的状态:
final bar
current progress
current forming state暂停操作会取消当前动画循环,但不会破坏该状态。
恢复播放时,从暂停的准确进度继续。
我还为播放操作与回放生命周期加入代次计数器,避免旧的异步任务稍后完成时,修改新一轮回放会话的状态。
这看起来有些繁琐,直到用户快速完成下面这一连串操作:
Play
Pause
Change interval
Jump to another candle
Play
Exit Replay而这样的操作,迟早会有用户做出来。
同一次更新,也改进了指标配置方式
开发回放功能时,另一个反复出现的操作也让我感到不便:
重复添加同一组指标。
图表可能使用:
EMA 20
EMA 50
RSI
成交量
一旦切换交易品种,又要把这些配置重新添加一遍。
于是,我在开发回放的同时,加入了指标预设系统。
WOWS 提供了面向常见技术分析流程的预设,但我不希望用户只能使用我挑选的组合。
于是便有了 My Sets(我的指标组合)。
你可以按自己的习惯设置指标参数与样式,再将整套配置保存为可重复使用的模板。
然后稍后将其应用到另一个图表中。
工作流程变为:
Build
↓
Customize
↓
Save
↓
Reuse比反复重建同一张图表要好得多。
一个功能需求,推动了图表架构改进
这里遇到的每一个问题都不是什么不可能解决的工程难题。
这个项目最有趣的地方在于,一个看似很小的功能竟然会影响到这么多系统。
Bar Replay 最终涉及:
历史数据加载
实时订阅
TradingView 的数据源生命周期
时间戳规范化
K 线内部数据聚合
动画时序
异步取消
多窗格隔离
历史数据保留期限
图表缓存失效
React 清理逻辑
指标重新计算
一旦这些要素到位, WOWS 的几个部分就比 Replay 出现之前更加完善了。
这就是我喜欢这种产品开发方式的原因之一。
用户提出了一些具体要求。
顺着这个需求,沿着技术栈深入梳理相关系统。
而有时,结果不仅仅是另一个按钮。
还可能是一次底层系统的改进。
WOWS 目前支持哪些功能?
图表工作区现在包括支持 K 线逐步形成的 Bar Replay、多种回放速度与历史推进步长、按窗格隔离的播放控制、精选指标预设、当前指标管理,以及可保存复用的指标组合。
更重要的是,这些功能可以配合使用。
我可以配置并保存一套技术分析指标,回到某个历史时点,再观察这套指标如何随着行情回放逐步变化。
这更接近我最初希望 WOWS 图表达到的效果:
这里不仅是查看价格的地方,更是实际探索价格的工作空间。
而这一切,始于一位内测用户提出的功能需求。
这大概是我最喜欢的产品工作类型:用户提出一个看似微不足道的要求,而正确地满足这个要求会促使产品从根本上变得更好。


