ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C#股票K线图开发:基于ZedGraph的轻量绘制全流程

C#股票K线图开发:基于ZedGraph的轻量绘制全流程 简介一套基于C#与ZedGraph开源图表库开发的股票K线图完整项目源码集成了行情数据获取、解析与图形化展示适合正在学习C#桌面开发、图表可视化或金融数据接口对接的开发者参考。资源共933个文件以424个.cs源码文件为主体辅以DLL依赖库、EXE可执行程序、day历史行情数据以及少量数据库与Web接口文件整套压缩包约9.46MB可直接在Visual Studio中加载解决方案运行验证。已有439人学习下载。项目采用三模块组织分别对应主程序逻辑、实时行情视图和K线绘制控件便于按需拆解研究代码经过可运行测试能帮助读者快速掌握C#窗体应用架构、ZedGraph定制K线样式、行情数据解析与多线程刷新界面等关键技能也可作为股票监控工具二次开发的基础框架。1. C#股票K线开发只需要一个轻量控件就能跑通全流程做股票K线图很多人的第一反应是去找付费组件或者自己用GDI写一套再或者干脆上重型的第三方图表库。但实际做一次就知道C# ZedGraph 的组合是能在一天内从零到一个“能运行”的K线界面的最短路径。标题里提到的这套方案核心就是 ZedGraph 这个开源控件自带的 CandlestickChart蜡烛图类配合股票OHLC数据数组直接绘制出标准的日K线。对量化交易的辅助分析、数据可视化学习、个人炒股工具的二次开发来说这是一个门槛低、落地快、后路也宽的方向。ZedGraph出身早、界面朴素但它的长处恰恰是稳定和可控。作为一个纯GDI绘制库它不依赖UI框架WinForms里放一个控件就能画它的数据模型是数组级的和后端拿到的行情数据天然对齐它支持轴缩放、鼠标拖拽、坐标自定义——这些是K线图最刚需的能力。这篇笔记会把从数据结构、控件配置、完整代码、动态刷新到常见坑位的整条链路都拆开讲。2. 为什么是ZedGraphK线绘制的选型对比与核心模型2.1 原生GDI、自绘和ZedGraph三种路线的真实成本自己用GDI画K线不是不行但代价会在一周后浮出来。画K线本身不难把收盘价按比例映射到坐标画一个矩形、画两条影线一个蜡烛就出来了。难在后续鼠标滚轮缩放时K线密集地区怎么重算坐标比例横轴怎么按交易日而非自然日显示提示框怎么跟随鼠标显示开高低收缩放后右侧价格轴怎么联动这些功能自己写至少需要维护一个坐标变换类、一个轴刻度计算类、一个数据窗口管理类工作量在一千到三千行代码之间而且每加一个功能都要动前面的底层逻辑。ZedGraph则把这些都封装好了。它的GraphPane对象管坐标轴、刻度、缩放它的CurveItem管数据曲线它的CandlestickChart直接接收OHLC数组并完成绘制。最直接的好处是画出来的图在拖动和缩放时不会出现坐标错乱因为轴刻度的算法规格已经稳定了。C#图表选型里ZedGraph虽然不算是“现代”组件但它对股票K线这一个垂直场景的适配度恰好比那些UI更漂亮的新库高出不少。2.2 读懂CandlestickChart的数据契约五个并列数组的结构ZedGraph画K线不需要自己构造一系列矩形对象而是向StockChart.CandlestickChart静态方法传数组。这是理解整套代码的关键它的数据契约是五个长度一致的并列数组// 五个数组日期、最高价、最低价、开盘价、收盘价 double[] dateValues; double[] highValues; double[] lowValues; double[] openValues; double[] closeValues; // 全部装载进一个 PointPairList PointPairList list new PointPairList(dateValues, highValues, lowValues, openValues, closeValues); // 创建一个K线曲线对象并加入GraphPane CurveItem candleCurve pane.AddCandlestickCurve(日K, list, Color.Transparent); StockChart.CandlestickChart( candleCurve, true, 1.0m, 2.0m ); // 后两个参数不是涨跌色是涨跌系数的默认值这段代码的后半部分要特别留意AddCandlestickCurve的第三个参数是上升K线的颜色第四个参数是下降K线的颜色这里的Color.Transparent只是占位颜色实际是在CandleStick属性里单独设置的。StockChart.CandlestickChart方法里的true表示“绘制实体为空心/实心”1.0m和2.0m是绘制体积的默认放大系数一般不用动。ZedGraph对K线的颜色约定是涨的时候用你指定的上升色跌的时候用下降色。国人习惯“红涨绿跌”但国外默认是“绿涨红跌”所以必须显式设置candleCurve.Line.Width 0.5f; // 影线宽度 candleCurve.Line.Color Color.Black; // 影线默认颜色 CandleStick stick candleCurve as CandleStick; stick.Color Color.Red; // 国内习惯涨红 stick.Color2 Color.Green; // 跌绿 stick.Fill new Fill(Color.Red, Color.Green); // 实体的填充色这一段是新手最先翻车的地方如果不设置Color2和FillK线可能统一显示一个颜色或者涨跌颜色和你预期的完全相反。2.3 日期轴的第一个坑XAxis.Type AxisType.DateZedGraph的X轴默认是数值轴如果你直接把DateTime类型的数组传给X轴绘制出来的横轴刻度会变成一串无意义的数字。正确做法是// 用 XDate 把 DateTime 转成 ZedGraph 内部的时间数值 double xValue XDate.DateTimeToXLDate(dt); // 设置X轴为日期类型 pane.XAxis.Type AxisType.Date; pane.XAxis.Scale.Format yyyy-MM-dd; pane.XAxis.Scale.MajorUnit DateUnit.Day; pane.XAxis.Scale.MinorUnit DateUnit.Day;转成 double 之后ZedGraph内部会用 XL Date一个以1900年为起点的浮点天数来参与坐标计算。这样做的成就很明显横轴缩放时刻度会自动按“日、周、月”切换标出的日期是整数不会出现半个交易日这种尴尬刻度。但副作用也明显你后续做数据窗口平移时所有坐标运算都要基于这个XL Date数值而不是DateTime。后面避坑章节会展开。3. 一套能跑的股票K线绘制代码从WinForms控件到完整绘图3.1 环境准备与控件初始化先说明一下整套方案基于 .NET Framework 4.7.2 或 .NET 6 都行ZedGraph 在NuGet上可以直接拿到任意一个稳定版本。安装方式是在NuGet包管理器控制台执行Install-Package ZedGraph界面准备工作量不大Windows窗体里拖入一个ZedGraphControl取名zgcKLine再放一个Button用于触发加载数据。在Form的构造函数或Load事件里做初始化// 初始化基类 public partial class KLineForm : Form { private ListStockDaily stockData; // 自定义的数据模型对应一条日K记录 public KLineForm() { InitializeComponent(); InitGraph(); } private void InitGraph() { // 获取GraphPane对象所有绘图配置都在它上面 GraphPane pane zgcKLine.GraphPane; pane.Title.Text 股票日K线; pane.XAxis.Title.Text 日期; pane.YAxis.Title.Text 价格; pane.XAxis.Type AxisType.Date; // X轴类型设为日期 pane.XAxis.Scale.Format yyyy-MM-dd; pane.XAxis.Scale.MajorUnit DateUnit.Day; pane.XAxis.Scale.MinorUnit DateUnit.Day; pane.XAxis.Scale.FontSpec.Size 9f; pane.YAxis.Scale.FontSpec.Size 9f; pane.Chart.Fill new Fill(Color.White, Color.FromArgb(245,245,245)); // 绘图区底色 pane.Fill new Fill(Color.FromArgb(40,40,40)); // 窗外区域深色背景模拟行情软件的夜间风格 zgcKLine.IsShowPointValues true; // 鼠标悬停显示数值 zgcKLine.IsZoomEnabled true; // 允许滚动缩放 zgcKLine.IsPanEnabled true; // 允许拖拽平移 } }参数说明MajorUnit决定主刻度即标记日期的刻度的单位是“天”配合Format显示成日期格式。IsShowPointValues开启后鼠标划过K线会出现一个小标签显示日期和开高低收这对做数据分析很友好。IsPanEnabled决定了能否用鼠标左键拖动图表平移K线数据长的时候这个是必需的。3.2 核心绘制方法数据转换、装载与刷新数据模型采用最普通的ListStockDaily每个对象包含日期、开高低收和成交量。从数据库或CSV读到的是字符串和DateTime需要组装然后转成ZedGraph的数组体系。public class StockDaily { public DateTime Date { get; set; } public double Open { get; set; } public double High { get; set; } public double Low { get; set; } public double Close { get; set; } public double Volume { get; set; } } private void DrawKLine(ListStockDaily data) { if (data null || data.Count 0) return; GraphPane pane zgcKLine.GraphPane; pane.CurveList.Clear(); // 清掉上一次的曲线 int n data.Count; double[] xValues new double[n]; double[] high new double[n]; double[] low new double[n]; double[] open new double[n]; double[] close new double[n]; for (int i 0; i n; i) { // 核心转换DateTime - XL日期数值 xValues[i] XDate.DateTimeToXLDate(data[i].Date); high[i] data[i].High; low[i] data[i].Low; open[i] data[i].Open; close[i] data[i].Close; } PointPairList list new PointPairList(xValues, high, low, open, close); CurveItem curve pane.AddCandlestickCurve(日K, list, Color.Red); StockChart.CandlestickChart(curve, true, 1.0m, 2.0m); // 设置涨跌颜色红涨绿跌 CandleStick stick curve as CandleStick; stick.Color Color.Red; stick.Color2 Color.Green; stick.Fill new Fill(Color.Red, Color.Green); stick.Fill new Fill(Color.Red, Color.Green); // 自动缩放坐标轴让所有K线完全显示 pane.XAxis.Scale.Min XDate.DateTimeToXLDate(data[0].Date.AddDays(-2)); pane.XAxis.Scale.Max XDate.DateTimeToXLDate(data[n - 1].Date.AddDays(2)); pane.YAxis.Scale.MinAuto true; pane.YAxis.Scale.MaxAuto true; zgcKLine.AxisChange(); // 重新计算坐标轴和刻度 zgcKLine.Invalidate(); // 触发重绘 }要点有四个。第一个pane.CurveList.Clear()是清理整个绘图区的曲线而不是ClearData()后者只清数据不清曲线对象会导致再次绘制时曲线叠加。第二个X轴最大最小手动设置比自动设置可靠——自动设置会把边界卡在第一条K线下影线和最后一根上影线的位置图形充满整个绘图区但看起来两边被“削掉”一半。我一般在外侧留两天的空白视觉上更像行情软件。第三个Y轴的MinAuto和MaxAuto保持自动计算但如果你发现某一波K线波动很小时价格轴压缩得厉害后面避坑章节会说怎么处理。第四个AxisChange()是重算刻度格式Invalidate()触发重绘这两个缺一不可。光调用Refresh()而不做AxisChange()坐标轴刻度不会更新图会“变形”。3.3 接入数据源从CSV和数据库加载的通用套路K线代码要“测试可以运行”最关键的是有一份能读进来的数据。模拟数据或者从数据库读都行关键是数据格式转换成StockDaily的过程不难但有几个细节会决定后续能不能画对。private ListStockDaily LoadFromCsv(string path) { var list new ListStockDaily(); var lines File.ReadAllLines(path, Encoding.Default); // 假设CSV格式日期,开盘,最高,最低,收盘,成交量 for (int i 1; i lines.Length; i) // 跳过表头 { string[] f lines[i].Split(,); if (f.Length 6) continue; var item new StockDaily { Date DateTime.Parse(f[0]), Open double.Parse(f[1]), High double.Parse(f[2]), Low double.Parse(f[3]), Close double.Parse(f[4]), Volume double.Parse(f[5]) }; list.Add(item); } // 按日期排序数据乱序会导致影线错位 return list.OrderBy(x x.Date).ToList(); }这里有一个细节值得单独讲排序。真实数据源经常出现日期乱序尤其是从多接口拼接数据时。ZedGraph本身不管数据是否有序它只会按点的顺序逐根绘制。日期乱序时K线图上的影线会来回交错看起来像一坨乱麻。所以OrderBy这段不是一个优化是必须做的。另外DateTime.Parse在遇到形如2024/1/5或2024-01-05时都能解析但性能上对几万条数据没有半点问题不需要自己写格式解析。4. 把静态K线改成动态刷新多股票切换、实时推送与性能护栏4.1 下拉框切换股票重入绘制的正确姿势做股票K线工具最终都会面对多股票的问题。界面上放一个下拉框用户每次切换都要用新的数据重绘。这时最典型的错误是直接把新数据丢进DrawKLine而不处理旧数据然后图上出现两组K线叠在一起。原因是前面用了pane.CurveList.Clear()再重绘理论上是干净的但有一个容易被忽略的坑——Y轴刻度范围在切换股票后没有重置。比如上一只是股价100元以上的股票价格轴范围是80到120切换到一只5块钱的股票时如果Y轴仍然用8到12的刻度所有K线会被压扁成一条线。所以切换股票时的代码必须在绘制前把Y轴强制拉回自动private void cmbStock_SelectedIndexChanged(object sender, EventArgs e) { // 切换前保存当前图形配置可省略但下述两行是必须的 zgcKLine.GraphPane.YAxis.Scale.MinAuto true; zgcKLine.GraphPane.YAxis.Scale.MaxAuto true; zgcKLine.GraphPane.XAxis.Scale.MinAuto false; // X轴保持手动范围避免缩放状态丢失 zgcKLine.GraphPane.XAxis.Scale.MaxAuto false; var stockCode cmbStock.SelectedValue.ToString(); ListStockDaily data LoadStockFromDb(stockCode); DrawKLine(data); }注意这里的DrawKLine内部又设置了MinAutotrue看起来冗余但实际是双保险如果绘制过程中数据为空导致异常退出至少外部已经把Y轴放开了。如果你用自己的一套交易所数据源LoadStockFromDb通常返回的是按日期倒序的数据DrawKLine只认正序建议统一在DrawKLine第一行做排序。4.2 实时行情刷新用定时器和增量数据更新K线盘中实时刷新和静态绘制是两个逻辑。实时场景一般是这样的全量历史K线已加载然后每3到5秒收到一条最新行情tick需要更新最后一根K线。如果每来一条数据就全量重绘所有K线数据量上升到几千根时就会卡顿因为AxisChange()是全图刻度重算O(n)的代价跑不掉。合理的做法是分场景处理private void timer_Tick(object sender, EventArgs e) { // 假设这一帧收到一条最新的实时行情newTick StockDaily newTick GetLastTick(); // 取当前曲线对象的PointPairList GraphPane pane zgcKLine.GraphPane; if (pane.CurveList.Count 0) return; CurveItem curve pane.CurveList[0]; PointPairList list curve.Points as PointPairList; double newX XDate.DateTimeToXLDate(newTick.Date); double lastX list[list.Count - 1].X; // 判断是否同一交易日是则替换最后一根否则追加一根 if (newTick.Date.Date XDate.XLDateToDateTime(lastX).Date) { list[list.Count - 1].Y newTick.High; list[list.Count - 1].Z newTick.Low; // PointPairList 在 CandlestickChart 中Y/Z 分别存高/低Open/Close存在额外字段中 } else { list.Add(newX, newTick.High, newTick.Low, newTick.Open, newTick.Close); } // 只刷新显示不做全量AxisChange如果价格越界再重新刻度 zgcKLine.Invalidate(); }这里的Y和Z是PointPair的通用数据字段在K线模式下被ZedGraph内部约定为最高价和最低价开盘和收盘存在另外的内部数组里。所以直接修改list[i].Y是无效的因为它只改HighClose不会跟着变。正确做法是直接用PointPair的完整构造替换该元素list[list.Count - 1] new PointPair(newX, newTick.High, newTick.Low, newTick.Open, newTick.Close);这个细节是实时刷新的核心坑位也是“测试可以运行”但你实时刷新时发现最后一根K线开盘价一直不变的原因。new PointPair构造的第五个参数是Close值ZedGraph内部的CandlestickChart会从这个字段读取收盘价来画实体颜色。不做全量AxisChange时如果最新价突破当前的Y轴上下限图形会超出边界看不见解决办法是每帧判断newTick.High和newTick.Low是否超出pane.YAxis.Scale.Min/Max如果超出则调用一次AxisChange()让轴跟随。4.3 大数据量下的性能护栏超过3000根K线时的取舍ZedGraph画几千根K线没有任何问题但它的交互性能瓶颈会出现在缩放和拖动上。当你在一个窗体里拖拽K线图时每一次鼠标移动都触发一次全量重绘2000根K线时很流畅10000根时就开始掉帧。常见做法不是切换控件而是控制展示窗口。我的习惯是数据全部保存在内存的ListStockDaily中绘制时只截取最近500根。配合滚动条或者鼠标拖拽平移时动态调整显示的窗口范围private void DrawLastN(int n) { if (stockData.Count n) { DrawKLine(stockData); } else { // 截取最近n条 var sub stockData.Skip(stockData.Count - n).ToList(); DrawKLine(sub); } }这样做用户体验上没任何损失K线查看本来就是聚焦最近一段时间。如果你确实需要拖着看一年甚至几年的历史K线那建议用pane.XAxis.Scale.Min/Max做窗口控制而不是截取数据。这两个方案的差别是截取数据时Y轴自动按当前窗口内的高低点来算刻度看局部行情时更合理而调整轴范围时Y轴会按全量数据的最高最低来算如果一只股票一年内从5块涨到30块你拉到最近的K线看Y轴范围依然是5到30最近几天的波动就看不出来了。这个问题实际上是行情软件的标配——主图右侧的价格轴应该跟随可视窗口。5. 股票K线绘制的6个避坑点从日期错位到坐标轴失控5.1 日期错位开盘日跳空变成长影线现象K线的实体位置正常但影线上下拉得很长甚至实体和影线连在一起看像一根竖直的线或者仔细看每天的K线在横轴上的间距不均匀明明是没有交易的周六也出现了K线。原因X轴类型设置成AxisType.Date后ZedGraph的横轴是按XL Date数值等间距分布的。如果你传入的日期数组里包含双休日数据源里本来就没有但绘制时没有跳过本身不存在问题真正的问题是你在DrawKLine里把X轴范围设置成了data[0].Date.AddDays(-2)和data[n-1].Date.AddDays(2)这会把横轴拉宽到自然日距离而不是交易日距离。当两个交易日之间隔了一个周末时那两天的K线间距会比周一到周二的间距明显大看起来像跳空但又不是价格跳空。解决统一切片X轴范围不要用AddDays这种自然日偏移而是用交易日序列来定// 用索引范围来设置轴范围而不是DateTime加减 pane.XAxis.Scale.Min xValues[0] - 0.5; // 0.5个交易日间距 pane.XAxis.Scale.Max xValues[n - 1] 0.5;如果你要显示最近30个交易日但中间有停牌日期那么xValues[0]停牌前的位置和xValues[1]之间的间距是正常的因为停牌日本身没有K线点而ZedGraph是按点的X值来定位的不存在“缺失日期被补平”的问题。5.2 涨跌颜色反了国外默认和国内习惯的冲突现象K线的阴阳和行情软件完全相反明明今天上涨却画成绿色实体或者全部K线都是同一个颜色完全没有阴阳之别。原因AddCandlestickCurve的第三个参数是上升色第四个参数才是下降色。很多人写AddCandlestickCurve(日K, list, Color.Red, Color.Green)以为前面是涨后面是跌实际上这句话翻译过来是“上涨红、下跌绿”国外习惯正好反过来的情况是“上涨绿、下跌红”。更隐蔽的问题是如果CandleStick.Fill没有设置有些版本默认会用Color去填充所有实体导致涨跌颜色一样。解决统一在CandleStick这个层面设置不要依赖AddCandlestickCurve的默认参数CandleStick stick curve as CandleStick; stick.Color Color.Red; stick.Color2 Color.Green; stick.Fill new Fill(Color.Red, Color.Green); stick.Fill.Type FillType.Solid;Fill对象的两个颜色参数分别对应涨和跌和Color/Color2对应的是同一逻辑。设置完之后再调用一次zgcKLine.Invalidate()。5.3 Y轴压缩失效低价股波动显示的尴尬现象一只5块钱的股票最近几天每天波动只有1分钱K线在图上还行但一旦你把它和另一只50块钱的股票放在同一窗格对比5块钱那只的K线完全成了一条水平的细线看不清实体。原因Y轴自动范围是依据当前绘制的所有K线的最高和最低价计算的。如果两只股票的最高价差距很大低价股的波动会在总范围内被压缩成一个像素以内。这不是ZedGraph的问题是行情工具本身的显示逻辑问题。解决不推荐强行设置Y轴范围那会导致价格失真。有一种常见做法是在同一个Pane里只画同一价位区间的多只股票例如同是银行股否则就切分多个GraphPane每只股票独立轴。GraphPane pane1 zgcKLine.MasterPane.AddPane(股票A, new RectangleF(0, 0, 0.5f, 1.0f)); GraphPane pane2 zgcKLine.MasterPane.AddPane(股票B, new RectangleF(0.5f, 0, 0.5f, 1.0f)); // 分别在pane1和pane2里绘制各自的K线MasterPane是ZedGraph的分割窗口机制它把一个ZedGraphControl分成多个绘图区每个区有自己的坐标轴。对比多股票时用这个方案最稳妥也能保住每只股票的刻度精度。5.4 缩放后K线变密集散开坐标轴不跟随光标现象用鼠标滚轮缩放时K线的图形可以缩放但X轴的日期刻度不会自动重新计算日期标签挤在一起或者压根不显示。原因ZedGraph的IsZoomEnabled和IsShowPointValues是两个独立的开关。缩放开了但Scale.MajorStep可能被固定为一个不合适的值或者X轴的Scale.IsVisible设置有问题。最常见的是缩放到很窄的日期范围内时主刻度步长没变小导致显示间隔过大。解决把MajorStep和MinorStep的自动计算打开pane.XAxis.Scale.MajorStepAuto true; pane.XAxis.Scale.MinorStepAuto true; pane.XAxis.Scale.MinAuto true; pane.XAxis.Scale.MaxAuto true;这几个*Auto属性在初始化的InitGraph()里设置一次之后缩放时ZedGraph会自己按可视宽度重新计算刻度步长。如果你发现缩放后日期显示成2024-01-01 00:00:00这样又长又挤把Format设成MM-dd放大后看年、缩小后看日这种动态格式化需要你在鼠标滚轮事件里手动切换ZedGraph没有内置。5.5 数据量超过一万根时界面卡死全量绘制的代价现象加载一只上市十年以上的股票的日K线约2500根无卡顿。但如果你加载的是分钟级K线例如一年分钟数据约40000根一拖动就卡CPU占满。原因Invalidate()触发的重绘是全量绘制ZedGraph的曲线绘制没有层级裁剪任何一次重绘都会把所有点重新计算并画一遍。这本身对GDI来说是可以接受的但如果同时开着IsShowPointValues鼠标移动时每个点都要做命中测试一万个点就是一次上万次循环。解决分钟级数据不要一次画完截取可视窗口内最近500根左右。这个方案前面已经说过了这里提供一个自适应策略private void zgcKLine_ZoomEvent(ZedGraphControl sender, ZoomState oldState, ZoomState newState) { // 缩放时判断当前可视范围内有多少个点如果超过阈值则做数据降采样 GraphPane pane zgcKLine.GraphPane; double minX pane.XAxis.Scale.Min; double maxX pane.XAxis.Scale.Max; var visiblePoints stockData.Where(x XDate.DateTimeToXLDate(x.Date) minX XDate.DateTimeToXLDate(x.Date) maxX).ToList(); if (visiblePoints.Count 1000) { // 直接重新截取并重绘 DrawKLine(visiblePoints.TakeLast(1000).ToList()); } }这里有一个明显的取舍降采样后如果想再缩小看到更多K线数据已经丢了所以这个策略只适合“从大窗口聚焦到小窗口”这个单向操作。如果你需要任意缩放就保持全量数据在内存然后用轴范围控制可视区域但这时候不要开IsShowPointValues只在鼠标暂停事件里做点命中。5.6 窗口最小化再恢复后图形空白现象窗体最小化一段时间再恢复K线图一片空白但代码没做任何操作。原因ZedGraph控件的GDI绘图表面在窗口最小化时被销毁恢复后需要重绘。Invalidate()只在数据变化时调用最小化时没有触发重绘事件所以画布是空的。解决在窗体的Resize或Activated事件里加一次强制刷新private void KLineForm_Activated(object sender, EventArgs e) { zgcKLine.Invalidate(); }这是最低成本的方案。如果还不行就调用zgcKLine.AxisChange()再Invalidate()一次双保险。浪费的性能可以忽略因为只在窗体激活时触发一次。6. 进阶扩展成交量副图、均线叠加与绘图验证清单K线主图做完下一步如果能加上成交量副图和均线这套代码才算真正能用于日常看盘。成交量和K线在同一个Pane里画价格轴会失衡因为成交量是0到几百万的数值而价格是几块钱到几十块。常见做法是用MasterPane分上下两个Pane上Pane画K线下Pane画成交量柱状图。GraphPane mainPane zgcKLine.MasterPane.AddPane(主图, new RectangleF(0, 0, 1, 0.75f)); GraphPane volPane zgcKLine.MasterPane.AddPane(成交量, new RectangleF(0, 0.75f, 1, 0.25f)); // 在成交量Pane中画柱状图 double[] volX xValues; double[] volY data.Select(x x.Volume).ToArray(); PointPairList volList new PointPairList(volX, volY); BarItem bar volPane.AddBar(成交量, volList, Color.SteelBlue); bar.Bar.Fill new Fill(Color.SteelBlue); volPane.XAxis.Type AxisType.Date; volPane.XAxis.Scale.Format yyyy-MM-dd; // 上下Pane的X轴对齐 volPane.XAxis.IsVisible false; // 或者只显示下轴的刻度均线叠加相对简单直接在主图上增加Line曲线X轴复用日期数组Y轴用收盘价的N日均值var ma5 new double[n]; for (int i 0; i n; i) { if (i 4) ma5[i] close.Skip(i - 4).Take(5).Average(); } PointPairList ma5List new PointPairList(xValues, ma5); LineItem ma5Curve pane.AddCurve(MA5, ma5List, Color.Orange, SymbolType.None); ma5Curve.Line.Width 1.2f;注意一个顺序问题必须先添加K线曲线再添加均线否则均线会被K线的实体覆盖住。ZedGraph的绘制顺序是按CurveList里的索引从后往前画的索引小的在上层。最后给一个验证清单每次改完代码后需要检查这四项再交付第一加载最近一年的数据用鼠标滚轮连续缩放到最近5根K线确认X轴日期格式没有变成乱码、Y轴范围跟随波动第二切换股票时确认界面没有残留上一次的曲线Y轴重新自适应第三实时推送模拟数据时观察最后一根K线是否在跳动、开盘价是否保持不变、量柱是否同步更新第四加一个“测试数据”按钮——生成一组间隔为1天、高开低收在10到12之间的假数据这样能排除数据源问题验证绘图函数本身是否正确。我做这套东西有一个血泪教训——别把K线绘制的验证工作留到“接真实行情”之后再做。先把模拟数据跑通再把真实数据接进来这样出问题的时候你知道该查数据还是查绘图。这个顺序省下的调试时间比多写一两百行测试代码要多得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表