一位内测用户想要 K 线回放,于是我把它做进了 WOWS
独立网站开发

一位内测用户想要 K 线回放,于是我把它做进了 WOWS

7 分钟阅读

一位内测用户希望 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 图表达到的效果:

这里不仅是查看价格的地方,更是实际探索价格的工作空间。

而这一切,始于一位内测用户提出的功能需求。

这大概是我最喜欢的产品工作类型:用户提出一个看似微不足道的要求,而正确地满足这个要求会促使产品从根本上变得更好。