绘制K线入门
什么是K线
K线(又称蜡烛图)是金融领域用来表示价格走势的一种图表形式。每根K线代表一个时间周期(如1分钟、5分钟、1小时、1天等)内的价格变化情况。
K线的组成部分
一根K线由以下几个部分组成:
- 实体(Body) :矩形部分,表示开盘价和收盘价之间的价格区间
-
- 如果收盘价 > 开盘价:称为阳线(通常用红色表示,表示上涨)
- 如果收盘价 < 开盘价:称为阴线(通常用绿色表示,表示下跌)
- 上影线(Upper Shadow) :实体上方的细线,表示最高价到实体上边缘的距离
- 下影线(Lower Shadow) :实体下方的细线,表示最低价到实体下边缘的距离
K线的数据要素
每根K线需要以下数据:
- Date(时间) :该K线的时间周期(如:2025/01/20、2025-01-20 10:00:00等)
- Open(开盘价) :该时间周期开始时的价格
- Close(收盘价) :该时间周期结束时的价格
- High(最高价) :该时间周期内的最高价格
- Low(最低价) :该时间周期内的最低价格
- Volume(成交量) :该时间周期内的交易量(可选,用于其他分析)
数据模型定义
在代码中,我们用 Kotlin 的 data class 来表示K线数据:
类图
markdown
@startuml
class KLineEntity {
- Close: String
- Date: String
- High: String
- Low: String
- Open: String
- Volume: String
+ getCloseFloat(): Float
+ getOpenFloat(): Float
+ getHighFloat(): Float
+ getLowFloat(): Float
+ isRising(): Boolean
}
@enduml
代码实现
kotlin
data class KLineEntity(
val Close: String, // 收盘价
val Date: String, // 日期
val High: String, // 最高价
val Low: String, // 最低价
val Open: String, // 开盘价
val Volume: String // 成交量
) {
// 类型转换方法(String → Float)
fun getCloseFloat(): Float = Close.toFloatOrNull() ?: 0f
fun getOpenFloat(): Float = Open.toFloatOrNull() ?: 0f
fun getHighFloat(): Float = High.toFloatOrNull() ?: 0f
fun getLowFloat(): Float = Low.toFloatOrNull() ?: 0f
// 判断是否为阳线(上涨)
fun isRising(): Boolean = getCloseFloat() > getOpenFloat()
}
说明:
isRising()用于判断涨跌,决定绘制时使用阳线颜色还是阴线颜色
涨跌判断
判断一根K线是涨还是跌:
kotlin
fun isRising(): Boolean = getCloseFloat() > getOpenFloat()
- 阳线(上涨) :收盘价 > 开盘价
- 阴线(下跌) :收盘价 < 开盘价
- 平盘:收盘价 = 开盘价(较少见,通常也按阳线处理)
如何绘制蜡烛图(入门版本)
说明 :本章节介绍的是入门版本的K线绘制方法,采用最简单、最直观的实现方式,适合初学者循序渐进地学习。
特点:
- 固定坐标系(不滚动、不缩放)
- 绘制所有数据(不区分可见区间)
- 代码逻辑简单清晰
- 便于理解K线绘制的核心原理
学习路径:
- 先掌握入门版本的绘制方法(本章节)
- 理解坐标计算、价格转换等核心概念
- 后续章节会逐步引入滚动、缩放、性能优化等高级功能
循序渐进,从简单到复杂,才能更好地理解K线绘制的本质。
绘制步骤
绘制一根K线(蜡烛图)需要以下步骤:
- 计算坐标
-
- 计算K线的中心X坐标(基于索引位置)
- 将价格转换为Y坐标(最高价、最低价、开盘价、收盘价)
- 绘制影线
-
- 绘制上影线:从最高价到实体上边缘
- 绘制下影线:从最低价到实体下边缘
- 绘制实体
-
- 根据涨跌选择颜色(阳线红色,阴线绿色)
- 绘制矩形实体(从开盘价到收盘价)
坐标计算
X坐标计算
K线的X坐标基于其在数据列表中的索引位置:
arduino
// 每根K线占用的总宽度(从配置中获取)
val totalCandleWidth = config.getTotalCandleWidth() // candleWidth + candleSpacing = 30px
// K线中心X坐标(从0开始,不使用偏移)
val centerX = index * totalCandleWidth + config.candleWidth / 2
// K线实体左右边界
val left = centerX - config.candleWidth / 2
val right = centerX + config.candleWidth / 2
Y坐标计算
价格转Y坐标的公式:
kotlin
fun priceToY(price: Float, minPrice: Float, maxPrice: Float, height: Float): Float {
val priceRange = maxPrice - minPrice
if (priceRange == 0f) return 0f
val ratio = (maxPrice - price) / priceRange
return height * ratio
}
说明:
minPrice和maxPrice是所有K线的价格范围(包含10%边距)height是View的高度- 价格越高,Y坐标越小(屏幕上方)
- 入门版本绘制所有数据,不区分可见区间
绘制顺序
绘制K线时,应该先绘制影线,再绘制实体,这样实体可以覆盖影线的中间部分:
- 绘制上影线:从最高价到实体上边缘
- 绘制下影线:从最低价到实体下边缘
- 绘制实体:绘制矩形,覆盖影线的中间部分
绘制代码示例
scss
private fun drawCandle(
canvas: Canvas,
entity: KLineEntity,
index: Int,
minPrice: Float,
maxPrice: Float,
height: Float
) {
// 1. 计算X坐标
val totalCandleWidth = config.getTotalCandleWidth()
val centerX = index * totalCandleWidth + config.candleWidth / 2
val left = centerX - config.candleWidth / 2
val right = centerX + config.candleWidth / 2
// 2. 计算各价格的Y坐标
val highY = priceToY(entity.getHighFloat(), minPrice, maxPrice, height)
val lowY = priceToY(entity.getLowFloat(), minPrice, maxPrice, height)
val openY = priceToY(entity.getOpenFloat(), minPrice, maxPrice, height)
val closeY = priceToY(entity.getCloseFloat(), minPrice, maxPrice, height)
// 3. 计算实体边界
val top = minOf(openY, closeY) // 实体上边缘(较小的Y值)
val bottom = maxOf(openY, closeY) // 实体下边缘(较大的Y值)
// 4. 选择颜色和画笔
val isRising = entity.isRising()
val bodyPaint = if (isRising) risingPaint else fallingPaint
shadowPaint.color = if (isRising) config.risingColor else config.fallingColor
// 5. 绘制上影线(从最高价到实体上边缘)
canvas.drawLine(centerX, highY, centerX, top, shadowPaint)
// 6. 绘制下影线(从最低价到实体下边缘)
canvas.drawLine(centerX, bottom, centerX, lowY, shadowPaint)
// 7. 绘制实体(矩形)
canvas.drawRect(left, top, right, bottom, bodyPaint)
}
绘制要点
- 影线宽度:通常比实体窄,例如实体宽度20px,影线宽度2px
- 颜色区分:
-
- 阳线(上涨):通常用红色或白色
- 阴线(下跌):通常用绿色或黑色
- 实体高度:实体高度 = |收盘价 - 开盘价|,如果开盘价等于收盘价,实体高度为0(显示为一条横线)
- 影线长度:
-
- 上影线长度 = 最高价 - max(开盘价, 收盘价)
- 下影线长度 = min(开盘价, 收盘价) - 最低价
配置模型(KLineConfig)
K线绘制需要配置参数,我们使用 KLineConfig 来管理这些配置:
类图

代码实现
kotlin
data class KLineConfig(
val candleWidth: Float = 20f, // K线实体宽度(像素)
val candleSpacing: Float = 10f, // K线之间的间距(像素)
val risingColor: Int = 0xFFFF0000, // 阳线颜色(红色)
val fallingColor: Int = 0xFF00FF00,// 阴线颜色(绿色)
val shadowWidth: Float = 2f // 影线宽度(像素)
) {
fun getTotalCandleWidth(): Float = candleWidth + candleSpacing
}
依赖关系
- KLineViewCase1 依赖 KLineConfig:KLineViewCase1内部持有KLineConfig实例,用于获取绘制参数(宽度、间距、颜色等)
- KLineViewCase1 使用 KLineEntity:KLineViewCase1接收KLineEntity列表作为数据源进行绘制
配置说明
- candleWidth (20px) : K线实体(矩形部分)的宽度
- candleSpacing (10px) : 相邻两根K线之间的间距
- risingColor: 阳线颜色(收盘价 > 开盘价)
- fallingColor: 阴线颜色(收盘价 < 开盘价)
- shadowWidth (2px) : 影线的宽度
参数关系
ini
每根K线占用的总宽度 = candleWidth + candleSpacing
= 20px + 10px = 30px
说明:入门版本会绘制所有K线数据,不涉及可见区间和数量限制的计算。
完整绘制流程
View的onDraw方法
K线图的绘制在自定义View的 onDraw() 方法中完成,完整流程如下:
kotlin
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 0. 绘制背景
val width = width.toFloat()
val height = height.toFloat()
canvas.drawRect(0f, 0f, width, height, backgroundPaint)
if (klineData.isEmpty()) {
return
}
// 1. 计算价格范围(包含边距)
val priceRange = calculatePriceRange()
if (priceRange.first >= priceRange.second) {
return // 价格范围无效
}
val minPrice = priceRange.first
val maxPrice = priceRange.second
// 2. 遍历所有K线数据,逐根绘制
klineData.forEachIndexed { index, entity ->
drawCandle(canvas, entity, index, minPrice, maxPrice, height)
}
}
价格范围计算
在绘制K线之前,需要先计算所有K线的价格范围(最高价和最低价),并添加边距:
kotlin
private fun calculatePriceRange(): Pair<Float, Float> {
if (klineData.isEmpty()) {
return 0f to 0f
}
var minPrice = Float.MAX_VALUE
var maxPrice = Float.MIN_VALUE
// 遍历所有K线,找出最高价和最低价
klineData.forEach { entity ->
val high = entity.getHighFloat()
val low = entity.getLowFloat()
if (high > maxPrice) maxPrice = high
if (low < minPrice) minPrice = low
}
// 添加10%的边距,让K线不会紧贴上下边缘
val priceRange = maxPrice - minPrice
val padding = priceRange * 0.1f
val adjustedMinPrice = minPrice - padding
val adjustedMaxPrice = maxPrice + padding
return adjustedMinPrice to adjustedMaxPrice
}
为什么要添加边距?
- 避免K线紧贴View的上下边缘
- 让图表看起来更美观
完整绘制链路
K线绘制的完整流程:
scss
1. 设置K线数据 → 触发onDraw()重绘
↓
2. onDraw()方法执行:
├─ 绘制背景
├─ 检查数据是否为空
├─ 计算价格范围
│ ├─ 遍历所有K线,找出最高价和最低价
│ └─ 添加10%边距
└─ 遍历K线数据,对每根K线调用drawCandle()
├─ 计算X坐标(基于索引)
├─ 计算Y坐标(最高价、最低价、开盘价、收盘价)
├─ 绘制上影线
├─ 绘制下影线
└─ 绘制实体
效果
