简介:TradingView 是全球领先的金融分析与交易平台,拥有超过五千万月活用户,平台覆盖股票、外汇、加密货币、贵金属等多个市场,其界面布局与图表交互常被开发者参考。这个源码包正是围绕看盘工具前端实现的可运行示例,面向希望快速上手网页版行情看板、研究平台布局或进行二次开发的开发者,也适合作为课程设计、毕业设计或个人项目的基础原型。压缩包共包含 3 个文件,包括 HTML 页面、inscode 脚本/配置以及 gitignore 版本控制配置,其中 HTML 负责页面展示,inscode 提供脚本/运行配置,gitignore 规范版本管理;整体大小仅 4KB,属于轻量级工程,可直接运行和修改。目前已有 412 人学习/下载。通过源码可以直观了解页面结构、基础样式和项目文件组织方式,适合作为金融数据看板项目的入门参考;开发者能在其上继续扩展技术指标、图表组件或实时行情展示功能,逐步搭建更完整的看盘工具,从而快速验证自己的交易分析想法。 做交易或者看盘稍微久一点,应该都绕不开TradingView。不管是流畅的K线缩放、十字线取价,还是里面那套能自己写脚本、跑策略回测的Pine Script,它在看盘和策略研究上的完成度,确实称得上“全球最强看盘软件”这个称号。不过我更关心的不是怎么把它用得更好,而是背后的工程实现——一个看盘工具到底是怎么把数据、指标、策略和图表串起来的。
这篇文章记录的是一个完整可运行的个人项目:前端使用TradingView团队开源的Lightweight Charts渲染交互图表,后端用Python完成行情数据拉取、技术指标计算和策略回测,最终生成一份带K线、均线、买卖点标注的网页看盘报告。整套源码结构不复杂,适合想做本地看盘工具、研究量化策略入门,或者纯粹想理解金融绘图底层逻辑的朋友参考。
1. 为什么拿TradingView当参照:拆解看盘软件的核心能力
1.1 看盘软件到底在拼什么
一个看盘工具好不好用,我自己的评判标准就三条。
第一是K线交互跟不跟手。TradingView在Web端拖动、缩放、十字线取值的流畅度基本是天花板,这种体验靠的是Canvas级别的渲染优化,不是普通SVG图表能做到的。你拖拽时图表会实时重新计算可视区间,缩放时均线跟着一起适配,这些细节决定了长时间盯盘时的疲劳程度。
第二是指标系统能不能按需扩展。均线、MACD、RSI、布林带这些基础指标要能叠加、能改参数,最好还能自己写脚本定义新指标。Pine Script在社区里能火,本质上是把指标的“可编程性”做到了极致,任何想法都能快速变成一条曲线画在K线上。
第三是研究链路能不能闭环。光看图没有意义,策略信号是怎么来的、历史表现如何、最大回撤多大,这些才是交易研究的价值所在。新手往往只盯着买入信号,老手更关心策略在极端行情下的表现,这部分必须靠回测系统来回答。
我这套源码没有打算模仿Pine Script的完整语法,但看盘工具的这三个核心能力——流畅K线、可叠加指标、策略回测闭环——都用可运行代码落地了。所以标题里的参照物是TradingView,而不是说“复刻TradingView”。完整商业产品的工程量太大,而核心链路完全可以在一个小而精的项目里跑通。
1.2 从商业软件里摘出来的开源组件
这里有个关键事实:TradingView自己开源了一个叫Lightweight Charts的轻量级图表库。它不包含行情数据、不包含技术指标,但把K线渲染、时间轴缩放、十字线、成交量柱子这些看盘交互都做得很到位,压缩后只有40多KB,适合嵌入到自己的工具里。
用这个库,相当于直接站在TradingView的图表工程基础上,后面只需要聚焦数据和策略。这也是很多个人交易工具的真实做法:商业软件的图表交互不是随便能超越的,与其自己造轮子,不如用官方开源组件,把精力花在更核心的策略逻辑上。
2. 可运行源码的整体设计
2.1 技术选型:为什么是Lightweight Charts + Python
我见过不少人一上来就上Vue、React,加上ECharts,再配一个后端框架,项目结构拉得很长,结果最后K线交互还是很生硬。这套源码刻意保持轻量,主要基于三个考虑。
图表层选Lightweight Charts,不选ECharts。ECharts的K线能力也不错,社区案例也多,但它在大量数据缩放的交互流畅度、十字线取价的专业感上,和专门的金融图表库还是有差距。Lightweight Charts的API设计更贴近交易场景,setData传数组、addLineSeries加均线,上手成本非常低。
数据处理层选Python,因为pandas和numpy处理时序数据确实太方便了。一个ewm函数能算出EMA,一个resample能完成K线周期转换,如果用Java或者Go,光是实现这些基础算子就要花费大量时间。数据获取方面,yfinance能拉美股,akshare能拉A股,个人研究阶段完全够用。
不引数据库,不用WebSocket长连接。很多初学者会把架构搞复杂,但个人看盘工具和回测系统最重要的前提是先把离线数据链路跑通。历史K线算完生成JSON文件,前端一次性读取,逻辑清晰、部署简单,也方便调试。实时行情推送是后续扩展的事,不是第一版的核心诉求。
2.2 项目文件结构与数据流
项目一共分成四个核心文件,外加一个web目录,结构是这样的:
trade-studio/ ├── fetch_data.py # 拉取历史K线,清洗后输出JSON ├── indicators.py # 技术指标计算模块 ├── backtest.py # 策略回测引擎 ├── run_all.py # 一键串联全流程 ├── data/ # 原始数据与结果数据 └── web/ ├── view.html # 看盘页面入口 ├── app.js # 图表渲染与交互逻辑 └── style.css整个数据流是单向的:run_all.py先调用fetch_data.py拉取并清洗K线,然后indicators.py计算技术指标,backtest.py根据指标信号生成买卖记录和绩效统计,最后全部汇总成一个结构化JSON,由web端读取并渲染。
这个单向数据流的好处是每个环节可以单独调试。数据拉出来后先画原始K线确认没有问题,再叠加指标看是否对齐,最后才跑策略。如果一次性把全部逻辑写在一起,出了问题很难定位是数据的问题、指标的问题还是信号判断的问题。
2.3 为什么采用生成JSON而不是前后端实时通信
很多人看到“看盘”两个字,第一反应是必须上WebSocket实时推送,否则就不专业。但实际上,对于回测和复盘场景来说,历史数据才是主角。你要研究的策略,大多数时候是基于过去几个月甚至几年数据的统计规律,不需要秒级推送。
JSON方案的另一个优势是可追溯。每次跑完回测,数据目录里都会留下当天的行情快照和结果文件,你随时可以复盘当时为什么产生这个信号、当时的市场状态是什么样的。如果走实时接口,数据不断变化,复现问题会非常麻烦。
等离线链路跑通之后,实时行情完全可以在fetch_data.py基础上扩展一个WebSocket数据源,把新到的K线增量追加到JSON里,不影响现有架构。
3. 核心细节解析与实操要点
3.1 K线数据结构与多时间框架
K线图最基本的单位是OHLCV:开盘价、最高价、最低价、收盘价、成交量。Lightweight Charts对数据格式要求很严格,时间戳必须是秒级的UNIX时间戳,从小到大排列,不能有重复,否则图表会渲染异常。
处理数据时最容易踩的坑是时间格式不统一。yfinance返回的是带时区的datetime索引,直接转JSON很容易变成字符串,前端解析时要么格式不对,要么时区错乱。我在代码里会统一做一个处理:
import pandas as pd def normalize_kline(df: pd.DataFrame) -> pd.DataFrame: df = df.copy() df.columns = [c.lower() for c in df.columns] df = df.rename(columns={ 'open': 'open', 'high': 'high', 'low': 'low', 'close': 'close', 'volume': 'volume' }) # 统一转为UTC,去掉时区信息,再转秒级时间戳 if df.index.tz is None: df.index = df.index.tz_localize('UTC') else: df.index = df.index.tz_convert('UTC') df.index = df.index.tz_localize(None) df['time'] = df.index.astype('int64') // 10**9 return df[['time', 'open', 'high', 'low', 'close', 'volume']]多时间框架也是用pandas解决。如果你拉到了日线数据,想生成周线和月线,直接resample:
def resample_ohlc(df: pd.DataFrame, rule: str) -> pd.DataFrame: ohlc = df['close'].resample(rule).ohlc() volume = df['volume'].resample(rule).sum() return pd.concat([ohlc, volume], axis=1).dropna()注意resample的rule是一个字符串,比如周线用“W-FRI”,代表以周五为周期末尾;月线用“ME”或“M”。K线聚合的规则要统一,否则不同时间框架之间的指标即使公式相同,画出来也会对不上。
3.2 指标计算:EMA、MACD、RSI、布林带的实现要点
技术指标是我觉得最有意思的部分,因为公式看着简单,但细节能影响结果。以MACD为例,三个核心参数是12日EMA、26日EMA、9日DEA信号线,柱子高度是DIF减DEA再乘2。有人会好奇为什么要乘2,这是国内惯例,为了让柱状图看起来更明显,A股行情软件都这么画,算是事实行业标准。
计算EMA不要自己写递归循环,pandas的ewm方法又快又准:
def ema(series: pd.Series, period: int) -> pd.Series: return series.ewm(span=period, adjust=False).mean() def macd(close: pd.Series, fast=12, slow=26, signal=9): dif = ema(close, fast) - ema(close, slow) dea = ema(dif, signal) hist = (dif - dea) * 2 return dif, dea, histRSI的常见坑是平滑方式不统一。很多人用简单移动平均算RSI,但更标准的做法是Wilder平滑,也就是用alpha=1/period的EWMA来平滑上涨幅度和下跌幅度,而不是用普通平均。两种算法在单边行情里差值会很明显。
def rsi(close: pd.Series, period=14): delta = close.diff() gain = delta.clip(lower=0) loss = -delta.clip(upper=0) avg_gain = gain.ewm(alpha=1/period, adjust=False).mean() avg_loss = loss.ewm(alpha=1/period, adjust=False).mean() rs = avg_gain / avg_loss return 100 - 100 / (1 + rs)布林带相对简单:中轨是20日均线,上下轨是中轨加减两倍标准差。但标准差的计算默认是样本标准差,有ddof=0和ddof=1两种选择,不同平台的做法不一样。我自己对比过,日线级别差异很小,但为了和自己参考的行情软件对齐,建议统一采用ddof=0也就是总体标准差。
指标计算完成后,一定要检查最后一个值是否和最新K线日期对齐。常见问题是指标序列的索引没有对齐,导致前端画线时整体偏移一根K线,这种错误很隐蔽,光看曲线走势难以发现,但会导致策略信号错位。
3.3 策略引擎:一个双均线回测框架
现在网上搜“tradingview策略”,大多数结果是Pine Script脚本分享,代码片段可以直接复制到TradingView里跑。这套源码里我用Python实现了一个最基础的双均线策略,金叉买入、死叉卖出,但接口设计成可以扩展任意规则。
回测引擎的核心是一个逐K线循环,遍历每一根K线,判断当前持仓状态,执行买入或卖出,并记录账户净值:
def run_backtest(df: pd.DataFrame, fast=5, slow=20, initial_cash=100000, fee=0.001, slippage=0.0005): position = 0 cash = initial_cash trades = [] equity_curve = [] for i in range(1, len(df)): price = df['close'].iloc[i] fast_ma = df['fast_ma'].iloc[i] slow_ma = df['slow_ma'].iloc[i] # 金叉买入 if fast_ma > slow_ma and position == 0: position = cash / price cash = 0 trades.append({'type': 'buy', 'price': price, 'date': df.index[i]}) # 死叉卖出 elif fast_ma < slow_ma and position > 0: cash = position * price position = 0 trades.append({'type': 'sell', 'price': price, 'date': df.index[i]}) equity = cash + position * price equity_curve.append({'date': df.index[i], 'equity': equity}) return pd.DataFrame(equity_curve).set_index('date'), trades这个循环里最难讲清楚的是触发的时点:用第i根K线的收盘价生成信号,并在同一根K线收盘成交,这是回测中比较常见的假设。不要把第i根的信号拿到第i+1根去判断,也不要用未来数据回填,否则就是未来函数,回测结果会严重失真。
手续费和滑点至少按单边千分之一来设置。双均线这种交易频率不高的策略,手续费影响不大,但高频策略如果不加成本,回测年化收益率可能虚高一倍以上。滑点的设置取决于品种流动性,主流头部品种每笔0.05%已经算是比较保守的假设。
4. 实操过程:从零跑起这套源码
4.1 环境准备与依赖安装
整个项目的Python依赖就三个:yfinance、pandas、numpy。安装命令:
pip install yfinance pandas numpy如果你主要研究A股,可以把yfinance换成akshare,用法略有不同,但数据结构化的思路完全一样。这里我以yfinance举例,因为它的API对个人研究非常友好,不需要申请密钥,一条命令就能拉数据。
还需要确认本机装了Python 3.9以上版本,以及一个现代浏览器。前端没有引入Node.js构建流程,所以不需要npm install,这是故意保持的简单:打开HTML文件就能看,或者用任意静态服务器托管。
4.2 数据拉取与清洗
在fetch_data.py里,核心函数是拉取K线和清洗数据。以苹果公司股票为例:
import yfinance as yf symbol = "AAPL" df = yf.download(symbol, start="2020-01-01", interval="1d", auto_adjust=True)yfinance返回的表结构列名是大写的,多股票时会带MultiIndex,这里只保留单股票场景。auto_adjust=True表示自动使用后复权价格,处理了拆股和分红的影响,对做技术指标很关键。如果不用复权价格,长期K线图上会出现不自然的跳空,均线也会失真。
数据拉下来后,第一件事不是算指标,而是检查数据质量。打印一下df.info(),看看有没有明显的NaN,再打印头三行和尾三行,确认时间范围符合预期。很多回测问题都是数据质量造成的,这个检查习惯要养成。
4.3 计算指标并生成前端JSON
run_all.py会把数据拉取、指标计算、策略回测全部串起来,最后输出一个前端可用的JSON。前端读取的核心数据包括K线数组、均线数组、买卖点标记数组和回测绩效摘要。
买卖点标记是Lightweight Charts非常实用的功能,它有专门的markers接口,可以在K线上方或下方显示箭头和标签。我会把策略产生的买卖点转换成markers数组,买点用绿色向上箭头,卖点用红色向下箭头,并附带成交价和日期信息。
对应的前端核心逻辑在app.js里,关键就几步:
const chart = LightweightCharts.createChart( document.getElementById('chart'), { layout: { background: { color: '#ffffff' } }, timeScale: { timeVisible: true }, } ); // K线 const candleSeries = chart.addCandlestickSeries(); candleSeries.setData(ohlcData); // 均线 const maFast = chart.addLineSeries({ color: '#2962FF', lineWidth: 2 }); maFast.setData(fastMaData); const maSlow = chart.addLineSeries({ color: '#FF9800', lineWidth: 2 }); maSlow.setData(slowMaData); // 买卖点 candleSeries.setMarkers(buySellMarkers);用createChart创建图表实例,addCandlestickSeries添加K线序列,addLineSeries添加线型指标,setData是核心传值方法。熟悉这个结构后,叠加成交量、MACD副图都能在此基础上扩展。
4.4 启动看板并验证结果
在项目根目录执行:
python run_all.py运行结束后,打开另一个终端启动静态服务:
cd web python -m http.server 8000浏览器访问http://localhost:8000/view.html,正常会看到从2020年到今天的K线,上面叠加了快线和慢线,金叉位置有绿色买入箭头,死叉位置有红色卖出箭头。页面下方或者控制台里会有回测摘要,包括总收益率、最大回撤、交易次数和胜率。
第一次跑通时,建议先用一个你熟悉的股票和区间验证结果的合理性。比如你知道某只股票在某段明显上涨,那图上就应该有对应的买入信号,这类常识验证能帮你快速发现代码bug,而不是只看回测收益率数字。
5. 常见问题与排查技巧实录
5.1 时间戳与K线日期对不上
这是我在这个项目里遇到最多的一个问题,现象是日线图上的K线整体偏移了一天,或者每天显示的时间是同一天的前后12小时。
出现这个问题的根本原因是时区。yfinance返回的数据本身带有UTC时间,如果你本机时区是UTC+8,直接转成字符串就会在日期上偏移。Lightweight Charts默认按UTC处理时间戳,但是很多人会把datetime对象直接转字符串传给前端,两边时区一叠加,日期就错乱了。
解决方案是在后端统一先把索引转成UTC并去掉时区信息,再生成秒级时间戳。这个过程我在3.1节已经给出了代码,关键是要在生成JSON之前完成,而不是在前端做转换。
5.2 指标曲线突然断裂或偏移
如果均线在K线图上中间有一段消失了,大概率是数据里有NaN。原因是停牌、节假日或者数据源本身返回缺失值。resample生成周线月线时,如果某一段没有交易,就会产生NaN。
处理方式有两种:如果是计算指标用的序列,用ffill前向填充;如果仅仅是某些日期没有数据,直接dropna删除空行。但要注意,最好不要一手ffill一手dropna混用,否则不同指标之间的对齐关系会被打破。我通常统一先ffill再dropna。
还有一类偏移问题是pandas的shift使用不当。很多人算“昨日收盘价”时会在循环里不自觉用了shift(1)再加其他条件,最后发现信号整体慢了一拍。排查思路是打印某几个关键日期的指标值和信号,对照原始K线一根一根验证。
5.3 大量K线渲染卡顿
一次性加载5000根K线时,Lightweight Charts其实能扛得住,但如果你自己做了实时更新,每次setData全量重传,交互就会明显掉帧。常见的优化手段是视口裁剪,只把当前可视化范围内的K线传给图表。
Lightweight Charts提供了subscribeVisibleLogicalRangeChange接口,可以在用户拖动或缩放时获得当前可见的逻辑范围,然后对数据做切片。对于个人看盘工具,一根箭头的性能优化往往不是必须的,但如果你后续接入实时行情,这个优化会变得很重要。另外注意不要频繁调用setData,合并更新比逐根追加要高效得多。
5.4 回测曲线很漂亮但实盘不赚钱:策略过拟合
最后聊一个策略层面的问题,而不是代码层面。我在调双均线参数时,一开始是拿2022年到2023年的数据优化,找到一个收益率很高的参数组合,结果换到2020年到2021年就大幅回落,典型的过拟合。
过拟合的典型操作是反复调整参数直到历史业绩最好,但历史不会重演。更合理的做法是留出一段样本外数据,比如用2020到2023年做参数优化,用2024年至今做验证,如果样本外表现明显衰减,说明策略逻辑本身不具备稳定性。
另外回测绩效里有几个指标要特别注意:最大回撤比总收益率更值得关注,盈亏比比胜率更值得关注。一套策略如果胜率只有30%,但盈亏比超过3,长期下来可能比一个胜率60%但每次亏多赚少的策略更赚钱。这些判断经验,是比任何源码都更重要的一课。
| 常见问题 | 可能原因 | 排查思路 |
|---|---|---|
| K线偏移一天 | 时区未统一 | 后端统一转UTC,生成秒级时间戳 |
| 指标曲线断裂 | 数据NaN未处理 | 先ffill填充,再dropna |
| 信号整体慢一拍 | shift使用不当 | 逐根K线对照验证信号日期 |
| 大量K线卡顿 | 全量数据传输 | 按视口切片,合并数据更新 |
| 回测表现不稳定 | 参数过拟合 | 保留样本外区间,做交叉验证 |
这套源码从最初只会画一根K线,到把数据、指标、回测、图表全部串起来,我前后折腾了两个周末。最大的体会是,看盘软件看起来简单,真正落到工程上,时间处理、指标对齐、性能手感这些细节每一样都得单独磕。尤其是策略回测,加不加手续费滑点、有没有用未来数据,结果可能是差出一个数量级。
如果你打算用这套框架扩展,我建议下一步从两个方向考虑:一是接入实时行情,通过WebSocket把最新K线增量追加到现有JSON里,让页面自动更新;二是增加自定义策略文件接口,不用改回测引擎就能写新的买卖规则。这比一开始就追求大而全的架构要务实得多。
另外提醒一句,所有行情数据仅供技术研究使用,回测收益不代表未来收益,策略上杠杆之前务必做好风险控制。
本文还有配套的精品资源,点击获取