绘制K线入门

绘制K线入门

什么是K线

K线(又称蜡烛图)是金融领域用来表示价格走势的一种图表形式。每根K线代表一个时间周期(如1分钟、5分钟、1小时、1天等)内的价格变化情况。

K线的组成部分

一根K线由以下几个部分组成:

  1. 实体(Body) :矩形部分,表示开盘价和收盘价之间的价格区间
    • 如果收盘价 > 开盘价:称为阳线(通常用红色表示,表示上涨)
    • 如果收盘价 < 开盘价:称为阴线(通常用绿色表示,表示下跌)
  1. 上影线(Upper Shadow) :实体上方的细线,表示最高价到实体上边缘的距离
  2. 下影线(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线绘制的核心原理

学习路径

  1. 先掌握入门版本的绘制方法(本章节)
  2. 理解坐标计算、价格转换等核心概念
  3. 后续章节会逐步引入滚动、缩放、性能优化等高级功能

循序渐进,从简单到复杂,才能更好地理解K线绘制的本质。

绘制步骤

绘制一根K线(蜡烛图)需要以下步骤:

  1. 计算坐标
    • 计算K线的中心X坐标(基于索引位置)
    • 将价格转换为Y坐标(最高价、最低价、开盘价、收盘价)
  1. 绘制影线
    • 绘制上影线:从最高价到实体上边缘
    • 绘制下影线:从最低价到实体下边缘
  1. 绘制实体
    • 根据涨跌选择颜色(阳线红色,阴线绿色)
    • 绘制矩形实体(从开盘价到收盘价)

坐标计算

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
}

说明

  • minPricemaxPrice 是所有K线的价格范围(包含10%边距)
  • height 是View的高度
  • 价格越高,Y坐标越小(屏幕上方)
  • 入门版本绘制所有数据,不区分可见区间

绘制顺序

绘制K线时,应该先绘制影线,再绘制实体,这样实体可以覆盖影线的中间部分:

  1. 绘制上影线:从最高价到实体上边缘
  2. 绘制下影线:从最低价到实体下边缘
  3. 绘制实体:绘制矩形,覆盖影线的中间部分

绘制代码示例

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)
}

绘制要点

  1. 影线宽度:通常比实体窄,例如实体宽度20px,影线宽度2px
  2. 颜色区分
    • 阳线(上涨):通常用红色或白色
    • 阴线(下跌):通常用绿色或黑色
  1. 实体高度:实体高度 = |收盘价 - 开盘价|,如果开盘价等于收盘价,实体高度为0(显示为一条横线)
  2. 影线长度
    • 上影线长度 = 最高价 - 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坐标(最高价、最低价、开盘价、收盘价)
       ├─ 绘制上影线
       ├─ 绘制下影线
       └─ 绘制实体

效果

相关推荐
川石课堂软件测试3 小时前
Android和iOS APP平台测试的区别
android·数据库·ios·oracle·单元测试·测试用例·cocoa
花卷HJ4 小时前
Android 通用 BaseDialog 实现:支持 ViewBinding + 全屏布局 + 加载弹窗
android
生产队队长4 小时前
Linux:awk进行行列转换操作
android·linux·运维
叶羽西4 小时前
Android15 EVS HAL中使用Camera HAL Provider接口
android
2501_915918414 小时前
除了 Perfdog,如何在 Windows 环境中完成 iOS App 的性能测试工作
android·ios·小程序·https·uni-app·iphone·webview
泓博4 小时前
Android状态栏文字图标设置失效
android·composer
叶羽西5 小时前
Android15系统中(娱乐框架和车机框架)中对摄像头的朝向是怎么定义的
android
Java小白中的菜鸟5 小时前
安卓studio链接夜神模拟器的一些问题
android
莫比乌斯环5 小时前
【Android技能点】深入解析 Android 中 Handler、Looper 和 Message 的关系及全局监听方案
android·消息队列