DMA(Different of Moving Average,平行线差指标)是经典的趋势型技术指标,它回避了单一均线系统的滞后问题,通过两条不同周期移动平均线的差值构建出一个独立的震荡体系。在自建股票行情App时,DMA经常和MACD、KDJ一起出现在副图区域。本文将从指标原理、公式推导讲起,再完整演示如何用Core Graphics在iOS端绘制一个带DMA指标的K线图副图。

DMA平行线差指标的计算原理与公式推导
DMA的核心思想很简单:股价短期波动与长期趋势之间的偏离程度,往往能提前反映趋势的转折。短期均线代表近期成本,长期均线代表总体成本,两者差值放大说明短期资金推动明显,差值收敛甚至转负则说明动能衰竭。
标准DMA指标由两条线组成。第一条是DDD线(有些软件直接叫DMA线),等于短期均线减去长期均线;第二条是AMA线,即对DDD再做一次移动平均,起到平滑滤噪的作用。公式为:
// DMA指标计算公式 DDD = MA(close, N1) - MA(close, N2) // 常用参数 N1=10, N2=50 AMA = MA(DDD, M) // 常用参数 M=10 // 信号判断 // DDD上穿AMA => 金叉,买入信号 // DDD下穿AMA => 死叉,卖出信号
举个具体例子更容易理解。假设某股票近10日收盘价的算术平均是12.5元,近50日平均是11.8元,那么当日DDD就是0.7元,表示短期成本高出长期成本7个点左右的溢价。如果连续多日DDD持续放大,说明上涨趋势正在加速;反之DDD从高位回落并向零轴靠拢,通常是趋势转弱的预警。
参数选择上,主流软件默认采用10、50、10的组合。短线交易者可以把N1缩短到5天以提高灵敏度,但代价是信号噪音增多、假交叉频发。需要注意的是,国内股市软件(如通达信)有时采用SMA加权平滑而非简单算术平均,计算结果会和国际默认参数略有出入,开发时建议以自己产品定义的口径为准,并在文档中注明。
iOS端DMA数据计算与K线数据的组织
绘图之前先解决数据层。K线图需要每根K线的开高低收四个价格,DMA计算只需要收盘价序列。建议定义一个蜡烛模型和一个指标模型,把行情数据与指标数据解耦,这样后续扩展MACD等其他指标时不需要改动K线模型。
// K线与DMA数据模型
@interface Candle : NSObject
@property (nonatomic, assign) CGFloat open;
@property (nonatomic, assign) CGFloat high;
@property (nonatomic, assign) CGFloat low;
@property (nonatomic, assign) CGFloat close;
@end
@interface DMAData : NSObject
@property (nonatomic, assign) CGFloat ddd; // 短期均线与长期均线的差
@property (nonatomic, assign) CGFloat ama; // DDD的移动平均
@end
// 简单算术移动平均
+ (NSArray<NSNumber *> *)maWithCloses:(NSArray<NSNumber *> *)closes period:(NSInteger)period {
NSMutableArray *result = [NSMutableArray array];
for (NSInteger i = 0; i < closes.count; i++) {
if (i < period - 1) {
[result addObject:[NSNull null]]; // 数据不足,填充空值
continue;
}
CGFloat sum = 0;
for (NSInteger j = i - period + 1; j <= i; j++) {
sum += closes[j].doubleValue;
}
[result addObject:@(sum / period)];
}
return result;
}有了通用均线函数后,DMA的计算就是两次调用:先分别求N1周期和N2周期的均线数组,逐点相减得到DDD序列,再对DDD序列求M周期平均得到AMA。注意数据不足的头部点位要用空值占位,绘图时跳过,否则曲线会从错误的位置起笔画出一段塌陷。
// 计算DMA序列
+ (NSArray<DMAData *> *)dmaWithCloses:(NSArray<NSNumber *> *)closes {
NSArray *shortMA = [self maWithCloses:closes period:10];
NSArray *longMA = [self maWithCloses:closes period:50];
// 先得到DDD序列
NSMutableArray *dddArr = [NSMutableArray array];
for (NSInteger i = 0; i < closes.count; i++) {
if (i < 49 || shortMA[i] == [NSNull null]) {
[dddArr addObject:[NSNull null]];
} else {
CGFloat d = [shortMA[i] floatValue] - [longMA[i] floatValue];
[dddArr addObject:@(d)];
}
}
// 对DDD求M周期均线得到AMA,逻辑类似,此处省略
// 最终组装DMAData数组返回
return result;
}数据层还要处理增量更新。真实行情是逐笔推送的,最新一根K线会不断变化,重算全部历史显然浪费。合理的做法是缓存历史均线的前缀和,新数据到来时只更新最后一根K线对应的DDD与AMA,滑动到新的一天才追加一个点。这一点在分时图、日线图切换频繁的场景下对性能影响很大。
用Core Graphics绘制K线主图与DMA副图
Core Graphics的核心API是CGContextRef,绘制一般在UIView的drawRect方法内完成。整体布局上,建议把主图(K线加均线)和副图(DMA)拆成两个视图或者用两个绘制区域,中间留出间隔,各自独立计算Y轴刻度,避免副图的负值把主图价格区间压扁。
绘图流程分为四步:第一,根据可见K线的最高价最低价确定主图Y轴范围,根据DDD、AMA的最大最小值确定副图范围,注意副图范围要对称包含零轴;第二,把价格值映射为屏幕坐标,CGContext的Y轴向下为正,所以映射时要翻转;第三,画网格与零轴线;第四,逐根绘制蜡烛和两条指标线。
// 副图绘制核心代码(drawRect内)
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 1. 计算副图数值范围,保证包含零轴
CGFloat maxV = 0, minV = 0;
for (DMAData *d in self.visibleDMA) {
maxV = MAX(maxV, MAX(d.ddd, d.ama));
minV = MIN(minV, MIN(d.ddd, d.ama));
}
CGFloat padding = (maxV - minV) * 0.1;
maxV += padding; minV -= padding;
// 2. 值到Y坐标的映射(flipRect为副图绘制区域)
CGFloat (^yFor)(CGFloat) = ^CGFloat(CGFloat v) {
return flipRect.origin.y + flipRect.size.height
* (1 - (v - minV) / (maxV - minV));
};
// 3. 画零轴虚线
CGContextSetStrokeColorWithColor(ctx, [UIColor grayColor].CGColor);
CGFloat dash[] = {2, 2};
CGContextSetLineDash(ctx, 0, dash, 2);
CGContextMoveToPoint(ctx, flipRect.origin.x, yFor(0));
CGContextAddLineToPoint(ctx, flipRect.origin.x + flipRect.size.width, yFor(0));
CGContextStrokePath(ctx);
CGContextSetLineDash(ctx, 0, NULL, 0);
// 4. 用ADD模式绘制DDD与AMA两条曲线
[self strokeLineWithContext:ctx values:dddValues
color:[UIColor systemOrangeColor] yFor:yFor];
[self strokeLineWithContext:ctx values:amaValues
color:[UIColor systemBlueColor] yFor:yFor];
}绘制连续曲线时不要每段单独stroke,把所有点用CGContextAddLineToPoint加入同一条路径后一次提交,性能会明显更好。蜡烛体的绘制则要先填充实体再描边影线,阳线阴线用不同颜色区分。如果需要叠加交易信号,可以在金叉死叉的位置画一个小箭头标记,判断逻辑就是相邻两个采样点DDD与AMA的差值符号发生翻转。
交叉信号解读与绘制细节优化
DMA的实战用法主要看三点:金叉死叉、零轴位置、与股价的背离。DDD上穿AMA且两者位于零轴上方,属于强势回调结束的买点;DDD在零轴下方上穿AMA,只能算弱反弹信号,需要结合成交量确认。DDD下穿AMA无论在什么位置都应警惕,若同时伴随股价创新高而DDD走低,就是典型的顶背离,趋势反转概率较大。
绘图细节上,建议在副图顶部显示当前DDD、AMA的实时数值和参数说明,例如“DMA(10,50,10) DDD:0.72 AMA:0.58”,数值颜色与曲线一致,方便用户对照。触控交互方面,可以在副图区域加长按手势,根据触摸点的X坐标反推索引,绘制一条十字光标并刷新数值标签,这是自绘图表区别于第三方库的常见体验点。
性能方面,当K线数量达到几千根时,应只绘制屏幕可见区间的数据,配合CADisplayLink或重绘节流避免高频回调导致的重复绘制。所有颜色、线宽等参数建议收敛到一个样式对象统一管理,方便后续做夜间模式切换。至此,一个完整支持DMA平行线差指标的iOS K线图组件就具备了数据、计算、绘制、交互四个层面的完整闭环,可以在此基础上继续扩展MACD、RSI等其他副图指标。
Core GraphicsDMA指标K线图修改时间:2026-09-15 10:20:49