导读:本期聚焦于风铃创作的《iOS股票K线图如何用Core Graphics绘制VOL5、VOL10、VOL20成交量均线?》,敬请观看详情。股票K线图里的成交量区域,除了红绿柱之外,还经常叠加VOL5、VOL10、VOL20三条均线,用来判断量能趋势和主力动向。本文介绍如何在iOS端基于Core Graphics从零实现这套成交量图:先讲清三条均线的计算逻辑与数据预热处理,再分步说明绘制流程,包括上下文配置、坐标映射、柱状图与均线分层绘制、颜色与抗锯齿设置等关键细节,最后给出完整的绘制代码示例和性能优化建议,帮助你在自己的行情组件中快速落地量能均线功能。

打开任意一款股票App,在K线图主图的下方几乎都会跟随一个成交量副图。除了表示每日成交量的红绿柱之外,图上通常还会叠加三条颜色不同的曲线,这就是VOL5、VOL10、VOL20成交量均线。它们分别代表最近5日、10日、20日的平均成交量,是判断量能放大与萎缩、识别放量突破与缩量回调的重要指标。本文将基于Core Graphics框架,在iOS端完整实现这三条均线的计算与绘制。

iOS股票K线图如何用Core Graphics绘制VOL5、VOL10、VOL20成交量均线?

一、成交量均线的计算原理与数据预热

VOL均线的计算方式与价格均线(MA)完全一致,只是把计算对象从收盘价换成了成交量。以VOL5为例,第N日的VOL5值等于从第N-4日到第N日这5天成交量的算术平均值,公式为:VOL5(N) = (Vol(N-4) + Vol(N-3) + Vol(N-2) + Vol(N-1) + Vol(N)) / 5。VOL10和VOL20同理,只是窗口长度不同。

计算时有一个容易踩坑的点:前几根K线由于数据不足,是没有均线值的。比如第一根K线不存在VOL5值,第19根之前的K线不存在VOL20值。这些位置要么不画线,要么把起点从第一根有效数据开始,否则会出现均线起始端异常下坠到零值的情况,视觉上非常难看。下面是一段完整的计算代码:

/// 计算VOL均线,返回nil表示该位置数据不足,无法计算
+ (NSArray<NSNumber *> *)calcVolumeMA:(NSArray<NSNumber *>)volumes period:(NSInteger)period {
    NSMutableArray *result = [NSMutableArray arrayWithCapacity:volumes.count];
    for (NSInteger i = 0; i < volumes.count; i++) {
        if (i < period - 1) {
            // 数据不足窗口长度,该位置无数均线值
            [result addObject:[NSNull null]];
        } else {
            CGFloat sum = 0;
            for (NSInteger j = i - period + 1; j <= i; j++) {
                sum += volumes[j].doubleValue;
            }
            [result addObject:@(sum / period)];
        }
    }
    return result;
}

// 使用示例
self.vol5 = [self.class calcVolumeMA:self.volumes period:5];
self.vol10 = [self.class calcVolumeMA:self.volumes period:10];
self.vol20 = [self.class calcVolumeMA:self.volumes period:20];

上面的双重循环在数据量大时效率一般。如果K线数量达到几千根,可以改用滑动窗口累加法:每前进一根K线,减去移出窗口的值,加上新进入的值,把复杂度从O(n*m)降到O(n)。对于实时刷新的分时图和滚动加载的历史K线,这个优化很有意义。

二、绘制前的坐标映射与上下文配置

拿到均线数据后,不能直接开始画,需要先确定Y轴的映射关系。成交量区域的最大值应取可视范围内所有成交量以及所有均线值中的最大值,再乘以一个安全系数(比如1.1),给顶部留出呼吸空间。注意这里的最大值必须在可视数据范围内动态计算,而不是用全部数据的最大值,否则当用户翻看历史行情时,柱子和均线会被压得很矮。

坐标换算公式很简单:y = 区域底部 - (value / maxValue) * 区域高度。X轴方向,如果副图区域宽度为W,可视K线数量为n,则每根柱子的宽度约为W/n,柱子中心点x = i * 每根宽度 + 每根宽度/2。均线上的点就取各根K线的中心点,把相邻点用直线连起来即可。

绘制时建议在drawRect:中先做整体配置:设置setShouldAntialias:YES开启抗锯齿,否则斜线会出现明显锯齿;使用CGContextSaveGStateCGContextRestoreGState成对包裹每条曲线的绘制,避免路径和变换互相污染;柱状图先画,均线后画,这样均线能覆盖在柱子上方,层次更清晰。

三、分层绘制成交量柱与三条均线

具体的绘制可以分为三步。第一步画成交量柱,颜色跟随当日涨跌:收盘价大于等于开盘价用红色(A股习惯),反之用绿色,柱子之间留一点间隙会更好看。第二步依次绘制VOL5、VOL10、VOL20三条曲线,通常使用黄、紫、绿或白、黄、紫等配色加以区分。第三步在左上角绘制图例文字,标注各颜色对应的周期以及最新一日的数值。

绘制曲线时推荐使用CGMutablePathRef构建路径,遇到值为NSNull的位置直接断开当前路径并重新开始新的一段,这样VOL20开头那十几根空白区不会拖出一条错误的长直线。下面是核心绘制代码:

- (void)drawVolumeMASeries:(NSArray<NSNumber *> *)ma
                     color:(UIColor *)color
                      context:(CGContextRef)ctx
                  plotFrame:(CGRect)frame
                  maxValue:(CGFloat)maxValue
                  barWidth:(CGFloat)barWidth {
    if (ma.count == 0) return;

    CGMutablePathRef path = CGPathCreateMutable();
    BOOL hasPoint = NO;
    CGFloat top = CGRectGetMinY(frame);
    CGFloat height = CGRectGetHeight(frame);

    for (NSInteger i = 0; i < ma.count; i++) {
        id value = ma[i];
        if (value == [NSNull null]) {
            // 数据断档,结束当前子路径
            hasPoint = NO;
            continue;
        }
        CGFloat x = i * barWidth + barWidth / 2.0;
        CGFloat y = top + frame.size.height - ([value doubleValue] / maxValue) * height;
        if (hasPoint) {
            CGPathAddLineToPoint(path, NULL, x, y);
        } else {
            CGPathMoveToPoint(path, NULL, x, y);
            hasPoint = YES;
        }
    }

    CGContextSetStrokeColorWithColor(ctx, color.CGColor);
    CGContextSetLineWidth(ctx, 1.0);
    CGContextSetLineJoin(ctx, kCGLineJoinRound);
    CGContextAddPath(ctx, path);
    CGContextStrokePath(ctx);
    CGPathRelease(path);
}

- (void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextSetShouldAntialias(ctx, YES);

    // 1. 计算最大值
    CGFloat maxValue = 0;
    for (NSInteger i = self.visibleStart; i < self.visibleEnd; i++) {
        maxValue = MAX(maxValue, self.volumes[i].doubleValue);
        maxValue = MAX(maxValue, [self.vol20[i] isKindOfClass:[NSNull class]] ? 0 : self.vol20[i].doubleValue);
    }
    maxValue *= 1.1;

    CGFloat barWidth = rect.size.width / (self.visibleEnd - self.visibleStart);

    // 2. 先画成交量柱
    for (NSInteger i = self.visibleStart; i < self.visibleEnd; i++) {
        CGFloat value = self.volumes[i].doubleValue;
        CGFloat h = (value / maxValue) * rect.size.height;
        CGFloat barRect = CGRectMake((i - self.visibleStart) * barWidth + 1,
                                     rect.origin.y + rect.size.height - h,
                                     barWidth - 2, h);
        UIColor *c = [self klines[i].close doubleValue] >= [self.klines[i].open doubleValue]
                   ? [UIColor colorWithRed:0.92 green:0.27 blue:0.27 alpha:1]
                   : [UIColor colorWithRed:0.13 green:0.71 blue:0.36 alpha:1];
        CGContextSetFillColorWithColor(ctx, c.CGColor);
        CGContextFillRect(ctx, barRect);
    }

    // 3. 再画三条均线,后画的覆盖在上方
    [self drawVolumeMASeries:self.vol5  color:[UIColor colorWithRed:1 green:0.8 blue:0.2 alpha:1]
                     context:ctx plotFrame:rect maxValue:maxValue barWidth:barWidth];
    [self drawVolumeMASeries:self.vol10 color:[UIColor colorWithRed:0.6 green:0.4 blue:0.95 alpha:1]
                     context:ctx plotFrame:rect maxValue:maxValue barWidth:barWidth];
    [self drawVolumeMASeries:self.vol20 color:[UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1]
                     context:ctx plotFrame:rect maxValue:maxValue barWidth:barWidth];
}

四、图例绘制与性能优化建议

图例一般放在副图左上角,用NSAttributedString或者直接调用drawAtPoint:绘制,例如显示为“VOL5:123456 VOL10:135789 VOL20:142030”,文字颜色与对应曲线颜色一致,方便用户对照。数值建议用万手或亿手做格式化缩写,避免长数字挤占空间。

性能方面有几点经验值得注意。首先,如果K线图支持捏合缩放和拖动,drawRect:会被高频调用,建议把三条均线的计算结果缓存起来,只在数据源变化时重新计算,拖动缩放时只做坐标映射和绘制。其次,CADisplayLink驱动的连续重绘场景下,可以考虑改用CAShapeLayer承载均线,配合path动画获得更流畅的效果。另外,当可视K线数量超过几百根时,柱子宽度会小于1个像素,此时可以把绘制粒度降低为每几根画一个聚合值,既省性能又不影响观感。

最后一点,实时行情推送时只需增量更新最后一根K线的成交量并重算最后几个均线值,不要整表重算。配合setNeedsDisplayInRect:只刷新脏区域,可以在低端机型上也保持流畅的刷新体验。按照以上思路实现后,一个带VOL5、VOL10、VOL20均线的专业成交量副图就完整落地了。

Core GraphicsK线图成交量均线修改时间:2026-09-01 18:04:38

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。