简介:基于C#与ZedGraph实现的股票K线图开发示例,面向Windows桌面开发者、金融数据可视化学习者,提供可运行的完整工程,经测试验证可正常启动。压缩包为RAR格式,大小约9.46MB,共933个文件,其中以424个C#源代码文件为主体,配合108个WGT界面文件、66个RESX资源文件以及数十个DLL动态库,另有EXE可执行程序和SLN解决方案文件,便于直接运行并继续二次开发。工程围绕StockMonitor主程序、WYStockRealView实时视图与StockDrawControl绘制控件展开,涵盖股票数据获取、接口解析、缓存处理到ZedGraph图表映射等环节;通过阅读源码可以了解如何将开盘价、收盘价、最高价、最低价绑定到K线图形元素上,并掌握C#WinForm项目的基本组织方式。资料还包含少量历史数据示例文件,适合用真实数据验证渲染效果。目前已有439人学习下载,适合希望快速上手金融图表开发或对ZedGraph库感兴趣的C#学习者。
1. 什么是C#股票K线开发:一个WinForm程序员要多久才能跑通
一个做上位机的朋友想给自己写的通讯测试工具加一个股票K线小窗,用来对着行情数据看趋势。他搜了一圈,最后锁定的方案还是老面孔——C# + ZedGraph。ZedGraph是个开源图表控件,我从 5.1 用到现在 5.2,画股票K线正好是它的经典场景,语法旧但稳定、资料多,从零跑通只需要两三个小时。
这篇笔记讲清楚三件事:怎么用C#和ZedGraph在WinForm里画出第一根K线、怎么把真实股票数据接进去、怎么把涨跌颜色和坐标轴调到符合看盘习惯。适合做桌面端看盘工具、量化回测面板,或者在上位机里塞一个K线窗口的开发者。代码我全部贴出来,参数含义写在代码后面,坑的位置单独拉了一章。
2. 为什么是C#和ZedGraph:这个老组合凭什么还能画股票K线
2.1 ZedGraph在图表控件里的位置
ZedGraph是2000年前后活跃起来的开源图表库,经历过SourceForge时代,后来迁移到GitHub,WinForm下的成熟度很高。它不搞动画、不搞交互特效,优点集中在三个方面:绘图效率高、坐标轴控制细、曲线类型覆盖广。对股票K线来说,最重要的CandleStick(蜡烛图)绘制是内置的,不需要自己拿矩形和线段拼。
围绕「C# 股票K线」这个需求,ZedGraph 的设计目标正好对准了老式桌面软件的痛点:数据量大、需要频繁缩放、需要在 Windows 窗体里原生嵌入。现在的 LiveCharts、OxyPlot 虽然界面更现代,但遇到几万根K线同样要调性能,ZedGraph 在这类场景下的底子反而更厚。这也是我到现在还在用它的原因——不是因为它好看,是因为它在我需要的地方够硬。
2.2 K线在ZedGraph里的数据模型:X/Y/Z装不下OHLC怎么办
ZedGraph的数据结构是分层的:最外层是 ZedGraphControl,里面是 GraphPane,GraphPane 的 CurveList 里放 CurveItem,CurveItem 的实际数据是一组 PointPair。要画K线,得先理解 ZedGraph 对 PointPair 的约定。
一个 PointPair 默认只有三个数值位:X、Y、Z。画折线时只用到 X 和 Y;画K线时需要五个值——时间、开、高、低、收。ZedGraph 的处理方式是:X 存日期,Y 存最高价,Z 存最低价,开盘价和收盘价塞进 Tag 里。Tag 是一个 object 类型,ZedGraph 规定它里面放一个 PointPair,这个 PointPair 的 X 是开盘价,Y 是收盘价。这个约定很老,文档里写得也不够显眼,我第一次用的时候在这里翻过车。
// K线数据:日期、开盘、最高、最低、收盘 PointPair pp = new PointPair(); pp.X = new XDate(2024, 5, 10); // ZedGraph.XDate,日期轴专用 pp.Y = 12.50; // High pp.Z = 12.10; // Low pp.Tag = new PointPair(12.30, 12.44); // Tag.X=Open, Tag.Y=Close这段代码体现了ZedGraph的K线数据模型的核心:PointPair本身只有三维,但通过Tag把OHLC补全。XDate是ZedGraph自己的日期类型,它和DateTime可以互相转换,专门用于坐标轴是日期类型时的计算。很多新手把X直接设成DateTime或者Unix时间戳,画出来要么X轴显示成数字,要么日期刻度错乱,根源就在这里。
2.3 和其他C#图表方案比,为什么还选它
做股票K线,C# 可选的图表控件不少。最常见的对比对象是这三个:
GDI+ 自己手绘。所有坐标变换、蜡烛绘制、十字光标、缩放逻辑都要自己写。优点是完全可控,缺点是开发周期长、边界情况多,一个除权跳空就能让坐标轴逻辑崩掉一次,适合教学不适合出活。
OxyPlot。现代、跨平台、API 清爽,对 WPF 支持很好。但K线类型需要自己组合 HighLow 系列和矩形系列,遇到大数据量加实时刷新时性能调起来费劲,WinForm 下的成熟度不如 ZedGraph。
LiveCharts。动画漂亮,上手快,但核心定位是展示型图表。股票K线这种需要拖动、缩放、叠加均线的场景,它的性能和数据承载能力都不是强项,做原型可以,做生产工具有风险。
ZedGraph的优势在于它就是为了 Windows 桌面上的大量数据点绘图设计的。日本蜡烛图(JapaneseCandleStickItem)直接内置,日期轴、对数轴、双Y轴、缩放滚动都原生支持,一个 ZGC 控件拖到窗体上就能开始画。做股票K线开发,它不是最时髦的,但它是验证成本最低的。
3. 用C#和ZedGraph画出第一根K线:最小可运行代码与参数说明
3.1 工程准备:装包、拖控件,五分钟内搞定
打开 Visual Studio,创建一个 .NET Framework 4.7.2 或者 .NET 6 的 WinForm 项目都可以。ZedGraph 在两种框架下都能用,我这里以 .NET Framework 4.7.2 为例,因为老项目用这个的多,遇到问题的参考资料也全。
在 NuGet 包管理器里搜索 ZedGraph,安装到项目。装完后工具栏里会出现 ZedGraphControl,直接拖到窗体上,调整 Dock 属性为 Fill,让控件铺满窗体。这一步要注意:如果工具箱里没出现 ZedGraphControl,是 VS 没有自动刷新工具箱,重新生成一下项目,或者手动在工具箱里添加引用ZedGraph.dll即可。
3.2 先造 50 根模拟K线数据
还没接真实行情之前,先用随机数造一组K线数据,把绘制链路跑通。这个做法能帮你把「画图问题」和「数据问题」分开排查——如果你连模拟数据都画不出来,那问题出在ZedGraph的使用上;如果模拟数据能画而真实数据画不出来,那问题出在数据源上。这个顺序很关键,能省下大量排查时间。
public class KLineData { 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; } } // 生成50根模拟K线,价格从10.0起步,每根随机波动±0.5 private List<KLineData> GenerateMockKLine(int count = 50) { Random rand = new Random(); List<KLineData> list = new List<KLineData>(); DateTime startDate = new DateTime(2024, 1, 1); double price = 10.0; for (int i = 0; i < count; i++) { double open = price; double close = open + (rand.NextDouble() - 0.45) * 0.6; // 收盘略偏向涨 double high = Math.Max(open, close) + rand.NextDouble() * 0.3; double low = Math.Min(open, close) - rand.NextDouble() * 0.3; list.Add(new KLineData { Date = startDate.AddDays(i * 7), // 每周一根,避开周末效果 Open = open, High = high, Low = low, Close = close }); price = close; // 下一根的开盘价等于上一根收盘价 } return list; }这里做了一个刻意的选择:Date = startDate.AddDays(i * 7),让模拟数据每周一根,和真实股票只有交易日有K线的情况对齐。这样后面画日期轴时不会出现周末断档干扰你判断问题。随机数用rand.NextDouble() - 0.45让收盘价有轻微上涨倾向,模拟出来的K线更接近真实走势。
3.3 把OHLC塞进PointPairList并画出K线
数据源准备完成后,接下来的核心步骤是把List<KLineData>转成 ZedGraph 能识别的PointPairList,然后通过AddCandleStick方法把K线加到 GraphPane 上。这是整个股票K线开发里最需要仔细看的一段代码,所有OHLC映射关系都在这里。
private void DrawKLineChart(List<KLineData> klineList) { GraphPane pane = zedGraphControl1.GraphPane; // 清空旧曲线 pane.CurveList.Clear(); // 设置标题和坐标轴 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.MajorStep = 7; // 主刻度间隔,单位是X轴数据单位(天) pane.XAxis.Scale.MinorStep = 1; // 构造PointPairList,把OHLC映射进X/Y/Z/Tag PointPairList list = new PointPairList(); foreach (var k in klineList) { PointPair pp = new PointPair(); pp.X = new XDate(k.Date); // X = 日期 pp.Y = k.High; // Y = 最高价 pp.Z = k.Low; // Z = 最低价 pp.Tag = new PointPair(k.Open, k.Close); // Tag = (开盘, 收盘) list.Add(pp); } // 添加蜡烛图曲线 JapaneseCandleStickItem candle = pane.AddCandleStick("K线", list, Color.Green); candle.ColorFill = Color.Red; // 实体填充色:红色阳线 candle.IsVisible = true; // 设置Y轴为自动范围,并让X轴正好包住所有数据 pane.YAxis.Scale.MinAuto = true; pane.YAxis.Scale.MaxAuto = true; pane.XAxis.Scale.MinAuto = true; pane.XAxis.Scale.MaxAuto = true; // 重新计算坐标轴并重绘 zedGraphControl1.AxisChange(); zedGraphControl1.Invalidate(); }逻辑说明:pane.CurveList.Clear()是刷新时必须做的,否则重复调用DrawKLineChart时K线会叠在一起。AxisType.Date告诉ZedGraph的X轴数据按日期解释,配合XDate类型才能让刻度显示成日期而不是浮点数。AddCandleStick返回JapaneseCandleStickItem,这是ZedGraph专门绘制日本蜡烛图的类,Color控制影线颜色,ColorFill控制实体填充颜色。AxisChange()方法是必须调的,它在数据变化后重新计算坐标轴范围,不调的话新数据可能超出可视范围显示不出来。
3.4 五个必调参数:颜色、轴类型、刻度步长、实体填充、自动范围
画股票K线,参数就那么几个,但每一个都有讲究。
第一个是pane.XAxis.Type = AxisType.Date。不设这个,ZedGraph默认把X轴当数值轴处理,XDate会被转成内部浮点数,日期标签全变成数字,后面的刻度设置全部失效。
第二个是pane.XAxis.Scale.MajorStep。它的单位是X轴的数据单位,对日期轴来说就是「天」。日K线一般设7表示每周一个主刻度,时K线设4或者直接不设让ZedGraph自动算。设错了会出现刻度稀疏或密集到糊在一起的问题。
第三个是Color和ColorFill的区分。很多新手只设置了Color,结果画出来的K线实体是黑的或者透明的,因为Color只控制影线(上影线、下影线)和K线轮廓,ColorFill才控制实体填充。两者分开设置才能调出红阳绿阴的效果。
第四个是candle.IsVisible。这个参数默认是true,但当你后面叠加均线、或在同一面板上画多个曲线时,某些曲线会被不小心设成false,排查时先确认这个值。
第五个是pane.XAxis.Scale.MinAuto/MaxAuto。ZedGraph默认会自动算范围,但当你后面加了新数据或者滚动缩放过,自动范围可能不再更新,需要手动调成true或者显式设置Min/Max。实时刷新的程序里,这个问题会变成最常见的「K线停在原地不跟着最新价走」。
跑完这五步,你会看到屏幕上出现一组基本的红色K线,能缩放能拖动。到这一步,ZedGraph画股票K线的最小链路已经打通了。
4. 接入股票数据,把K线调成A股看盘的形状:数据源、红绿颜色和时间轴
4.1 从CSV接真实数据:一个函数搞定
模拟数据跑通后,接下来换成真实股票数据。最常见的落地方式是从通达信、同花顺或量化平台上导出的CSV文件,格式一般长这样:date,open,high,low,close,volume。我需要写一个读取函数,把CSV解析成List<KLineData>,然后直接丢给上一章的DrawKLineChart就能画。
private List<KLineData> LoadKLineFromCsv(string filePath) { List<KLineData> list = new List<KLineData>(); string[] lines = File.ReadAllLines(filePath); // 从第1行开始,跳过表头 for (int i = 1; i < lines.Length; i++) { string[] fields = lines[i].Split(','); if (fields.Length < 6) continue; // 注意:真实数据可能带引号或前后空格 string dateStr = fields[0].Trim().Replace("\"", ""); DateTime date = DateTime.ParseExact(dateStr, "yyyy/MM/dd", CultureInfo.InvariantCulture); KLineData k = new KLineData { Date = date, Open = double.Parse(fields[1]), High = double.Parse(fields[2]), Low = double.Parse(fields[3]), Close = double.Parse(fields[4]) }; list.Add(k); } // 按日期升序排序,保证K线从左到右时间递增 list.Sort((a, b) => a.Date.CompareTo(b.Date)); return list; }然后调用方式极简:
List<KLineData> data = LoadKLineFromCsv("D:\\stock\\600519.csv"); DrawKLineChart(data);这中间有三个容易踩的细节。第一,日期格式要和你导出的数据源一致,不同平台导出的格式有yyyy/MM/dd、yyyy-MM-dd、yyyyMMdd三种,ParseExact的格式串写错直接抛异常。第二,真实CSV里字段可能带引号,比如"2024/05/10",所以我做了Trim().Replace("\"", "")处理。第三,list.Sort按日期排序不能省,有些数据源导出顺序是乱的,不排序的话K线会前后颠倒,涨跌颜色也会全部错乱。数据乱套和画图代码出错的表现完全不同,先排除数据问题再调绘图参数,效率最高。
4.2 A股红涨绿跌:Color和ColorFill怎么配
A股的习惯是红涨绿跌,和国际市场的绿涨红跌相反。ZedGraph的CandleStick默认逻辑是:Close >= Open时按一种颜色画,Close < Open时按另一种颜色画。它的颜色判定逻辑不用改,你只需要把两个颜色值按A股习惯配好,就能得到正确结果。
candle.Color = Color.Green; // 影线和边框:绿色 candle.ColorFill = Color.Red; // 实体填充:红色等一下,这里有个很容易搞混的地方。在ZedGraph里,Color设置的是影线颜色,而ZedGraph判定涨跌的标准是Close >= Open还是Close < Open,它会把涨幅对应的曲线段用ColorFill填充吗?不是的。实际行为是:ZedGraph的JapaneseCandleStickItem在绘制实体时,涨跌颜色由Color和ColorFill共同决定,具体来说,Close >= Open的K线实体用ColorFill填充,Close < Open的K线实体用Color填充。所以上面这段配置Color=Green、ColorFill=Red是错误的——这样反而是绿涨红跌了。
我刚接触这个库时在这里绕了很久。正确配法要看清楚你想要哪种习惯:
// A股习惯:红涨绿跌 // 阳线(收盘>=开盘)实体为红色,阴线实体为绿色 JapaneseCandleStickItem candle = pane.AddCandleStick("K线", list, Color.Green); candle.ColorFill = Color.Red;上面这个配置里,Color=Green不是指阳线的影线,而是指阴线那一侧的影线和实体边框,ColorFill=Red则作为阳线实体的填充。两个值各管一侧。如果你发现最终效果是「红绿颠倒」,把Color和ColorFill对调即可,不需要去改判定逻辑。这是我踩过的坑里最典型的ZedGraph黑匣子问题。
4.3 周末缺口和日期刻度:时间轴的两种处理
真实股票数据只有周一到周五有K线,如果直接把日期XDate丢给ZedGraph,X轴上会保留周六周日的空白,K线图看起来中间断裂。做看盘工具时,你有两种处理方式。
第一种是「留空」——接受周末缺口,把MajorStep设为7天,让主刻度正好落在周一。这种做法的好处是实现零成本,坏处是整个图形被拉宽,近期走势的细节被压缩。对分析日K线一年的数据来说,周末空白会占掉约30%的宽度,不划算。
第二种是「压缩」——X轴不用日期值,改成用数据索引(0、1、2...),然后通过轴的Scale.TextLabels特性把索引位显示成日期标签。这种做法股票软件普遍采用,因为界面紧凑,但ZedGraph实现起来要处理X轴类型的变化。
// 用索引做X,日期显示在刻度标签上 pane.XAxis.Type = AxisType.Text; // 文字轴 pane.XAxis.Scale.TextLabels = true; List<string> dateLabels = klineList.Select(k => k.Date.ToString("MM-dd")).ToList(); // 设置标签间隔,每5根显示一个日期 pane.XAxis.Scale.MajorStep = 5; pane.XAxis.Scale.MinorStep = 1; // 通过 GraphPane.XAxis.Scale.TextLabelArray 来绑定标签但这套方案在ZedGraph里比较折腾,TextLabels模式下缩放滚动的体验和日期轴差异很大,标签联动需要额外处理。我的实际建议是:做日K线、数据量在几百根以内时,直接用第一种方案,把MajorStep设为7天,周末留白其实不影响判断趋势;做分时图或分钟K线时,由于数据密集,才需要走第二种方案。看盘工具优先保证看得清,而不是一味追求紧凑。
4.4 缩放、滚轮和十字光标:ZedGraph给你省下的工作量
这个问题很多人忽略,但恰恰是股票K线开发里体验提升最大的部分。ZedGraph内置了鼠标滚轮缩放、拖拽平移、右键菜单保存图片这些功能,不需要你写一行代码。需要手动设置的只有两处:滚动条和十字光标。
// 开启水平和垂直滚动条,缩放后可以拖动查看 zedGraphControl1.IsShowHScrollBar = true; zedGraphControl1.IsShowVScrollBar = true; zedGraphControl1.IsEnableHPan = true; // 允许水平拖拽 zedGraphControl1.IsEnableVPan = true;十字光标需要自己画。做法是在 MouseMove 事件里拿到鼠标在 GraphPane 上的坐标,然后调用GraphPane.ReverseTransform把屏幕坐标转成数据坐标,再用GraphPane.Draw重绘并在画面上画两条十字线。这个逻辑不复杂,但代码量不少,属于典型的「有用但别急着做」的功能。先确认实时刷新和均线叠加没问题,再补十字光标不迟。
5. 股票K线开发的5个高频踩坑:现象、原因与解法
5.1 X轴显示成 0、1、2 而不是日期
现象:K线画出来了,但X轴刻度是 0、1、2、3 这种数字,日期完全不显示,水平拖动时数字跟着变。
原因:这是ZedGraph股票K线头号翻车现场。pane.XAxis.Type没有设置成AxisType.Date,或者设置了但X值没用XDate类型。ZedGraph的日期轴要求数据点和轴类型双配合,缺一个就退化成数值轴。
解决:在绘制函数开头显式设置pane.XAxis.Type = AxisType.Date,并且构造 PointPair 时用new XDate(k.Date),不要直接塞k.Date.ToOADate()或者k.Date.Ticks。可以用pane.XAxis.Scale.Format验证生效,格式化串设成yyyy-MM-dd后显示正常就说明轴类型对了。
5.2 K线实体全是黑色或者看不出来
现象:K线画出来只有上下影线,中间的矩形实体是黑色的或者几乎透明,整张图看起来像一堆天线。
原因:只设了Color没设ColorFill。ZedGraph的CandleStick里,影线和轮廓用Color,实体填充用ColorFill,两者独立。当ColorFill是默认值时,实体是黑色填充,在红绿配色的图形里非常扎眼。
解决:设置candle.ColorFill = Color.Red;。如果你发现实体颜色和预期相反(该红的绿、该绿的红),参照 4.2 节,把Color和ColorFill对调,这是ZedGraph里最让人口吐芬芳的配色玄学。
5.3 真实数据一多,拖动和缩放卡顿
现象:加载 8000 根以上K线后,鼠标拖动和滚轮缩放有明显迟滞感,CPU占用接近单核满载。
原因:ZedGraph默认把所有数据点逐根绘制,K线又是多线段组合(实体加影线),数据量上来后绘制开销按倍数增长。再加上Windows窗体的GDI重绘机制,拖动过程中每一帧都在全部重画,自然卡顿。
解决:分三步。第一步,zedGraphControl1.IsAntiAlias = false;关闭抗锯齿,这一步效果最明显。第二步,如果在实时刷新,用zedGraphControl1.IsEnableHPan和IsEnableVPan控制重绘时机,拖动过程中暂停数据更新。第三步,数据量超过两万根时,考虑抽稀——每5根取一根,或者只在缩小视图时用pane.XAxis.Scale的范围裁剪数据。这三步做完,几万根K线的流畅度基本可接受。ZedGraph画K线不是不能扛大数据,而是得伺候好它的绘制开关。
5.4 中文标题和坐标轴乱码
现象:K线正常,但标题、Y轴标题、图例里的中文全部变成方框或者问号。
原因:ZedGraph的默认字体是 Arial,不支持中文。所以中文显示全靠手动指定字体族。这个坑每换一次环境或者每新建一个项目就会踩一次,防不胜防。
解决:在DrawKLineChart里统一设置字体:
pane.Title.FontSpec.Family = "微软雅黑"; pane.XAxis.Title.FontSpec.Family = "微软雅黑"; pane.XAxis.Scale.FontSpec.Family = "微软雅黑"; pane.YAxis.Title.FontSpec.Family = "微软雅黑"; pane.YAxis.Scale.FontSpec.Family = "微软雅黑"; pane.Legend.FontSpec.Family = "微软雅黑";最省事的做法是把这段代码封到一个SetupChineseFont()方法里,每次初始化 GraphPane 时调用一次。注意:字体名称要用系统里真实存在的字体,Linux 环境下没有微软雅黑,要用WenQuanYi Micro Hei或者打包字体文件。
5.5 加载真实数据后K线被压成一条扁线
现象:K线图加载完真实数据后,所有K线在Y方向挤成一条扁平的带状,完全看不出高低点变化。
原因:数据里有异常值。最常见的是除权导致的价格跳空、CSV里混入 0 价格、或者某些平台的除权数据里 low 比 high 还大。ZedGraph的Y轴自动范围把这些异常值包进去后,正常价格区间的波动就被压扁了。
解决:加载数据时做一次清洗——过滤掉High < Low的记录、过滤掉High <= 0的记录、对除权跳空做复权处理。另外可以在绘制前手动设置pane.YAxis.Scale.Min和Max为最近N天的价格范围,绕开历史异常值。我的习惯是数据清洗永远放在画图之前,用LoadKLineFromCsv里统一处理,每个字段都用TryParse而不是Parse,解析失败的行直接跳过并输出日志。这样既保证K线形状正常,也为以后调策略省掉一半的脏数据问题。
6. 从K线图到看盘面板:实时刷新、均线叠加和性能兜底
6.1 用Timer做定时刷新,只改最后一个点
做成看盘工具,数据不会一次性到齐,而是不断有最新的Tick或者日K线进来。常见做法是扔一个System.Windows.Forms.Timer到窗体上,Interval 设 1000 毫秒,在 Tick 事件里更新数据。
private void timer1_Tick(object sender, EventArgs e) { // 从数据源拉最新行情,这里用模拟数据演示 double newClose = lastClose + (rand.NextDouble() - 0.5) * 0.2; KLineData last = klineList[klineList.Count - 1]; last.Close = newClose; last.High = Math.Max(last.High, newClose); last.Low = Math.Min(last.Low, newClose); // 只重算坐标轴并重绘,不需要重建整个PointPairList zedGraphControl1.AxisChange(); zedGraphControl1.Invalidate(); }这里有个细节:如果只更新最后一根K线的收盘价,不需要重新调用DrawKLineChart去重建整个曲线,直接改数据后AxisChange()加Invalidate()就够了。重建整个PointPairList在数据量大时会明显卡顿,这是「只改数据不换结构」的常见优化手段。多线程场景下注意:Timer的 Tick 事件跑在UI线程,可以直接碰控件;如果用后台线程收行情,Invoke或BeginInvoke回UI线程再更新,这是C#多线程操作ZedGraph的底线,直接跨线程修改数据控件必崩。
6.2 叠加MA5和MA20均线
看盘工具没有均线总感觉缺一半。ZedGraph加均线不需要额外控件,直接在同一个 GraphPane 上用 LineItem 叠加上去即可。均线的X值必须和K线的X一致,用同样的XDate,Y值是对应区间的收盘价均值。
private void AddMovingAverage(List<KLineData> klineList, int period, string name, Color color) { PointPairList list = new PointPairList(); for (int i = period - 1; i < klineList.Count; i++) { double sum = 0; for (int j = i - period + 1; j <= i; j++) sum += klineList[j].Close; double avg = sum / period; list.Add(new XDate(klineList[i].Date), avg); } LineItem line = zedGraphControl1.GraphPane.AddCurve(name, list, color, SymbolType.None); line.Line.Width = 1.2f; // 线宽1.2像素,默认太细 }调用时按需求叠加即可:AddMovingAverage(data, 5, "MA5", Color.Orange);和AddMovingAverage(data, 20, "MA20", Color.Blue);。这里有个细节:均线数据从第period - 1根K线才开始有值,直接循环到数据末尾即可,ZedGraph会正确显示线段起点。SymbolType.None表示不画数据点符号,否则K线图上会叠加出密密麻麻的小圆点,丑到没法看。
6.3 性能兜底和布局习惯
最后交代两个实战兜底做法。第一,K线图和成交量分开两个 GraphPane 放,同一个 ZedGraphControl 里用MasterPane拆成上下两块,上面画K线下面画成交量。ZedGraph的MasterPane支持GraphPane的合并布局,成交量用Bar类型曲线,X轴范围要和K线绑定一致,这样缩放时上下联动。这个布局是所有股票软件的基础结构,做看盘工具迟早要面对。
第二,数据更新时尽量复用对象。构造PointPairList这种引用类型时,如果数据量在反复刷新,每次都new一个列表并重新添加点,会触发大量GC,导致界面卡顿。我一般会预先分配好PointPairList的容量,或者复用同一个列表,只修改对应索引位的值,再Invalidate()。这个习惯在分钟级行情刷新时尤其重要。
我做过的几个K线项目里,最深刻的教训是:股票K线的难点从来不在画图,而在数据。数据顺序、除权、脏值、数据源格式不一致,每一项都能让看似正确的画图代码输出错误的图形。所以你现在跑通了上面这些代码,真正能拿出去用的不只是一张K线图,而是一条完整的数据处理管道——清洗、排序、映射、绘制、刷新。建议你先拿自己常用股票导出的CSV格式跑一遍,遇到格式不同的数据源就按 4.1 的方式单独写解析,积累一套自己的数据适配逻辑。这套东西的价值在项目后期会远超画图本身。希望帮到你。
本文还有配套的精品资源,点击获取