绘制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坐标(最高价、最低价、开盘价、收盘价)
       ├─ 绘制上影线
       ├─ 绘制下影线
       └─ 绘制实体

效果

相关推荐
andr_gale25 分钟前
04_rc文件语法规则
android·framework·aosp
祖国的好青年1 小时前
VS Code 搭建 React Native 开发环境(Windows 实战指南)
android·windows·react native·react.js
黄林晴2 小时前
警惕!AGP 9.2 别只改版本号,R8 规则与构建链路全线收紧
android·gradle
小米渣的逆袭2 小时前
Android ADB 完全使用指南
android·adb
儿歌八万首2 小时前
Jetpack Compose Canvas 进阶:结合 animateFloatAsState 让自定义图形动起来
android·动画·compose
zhangphil3 小时前
Android Page 3 Flow读sql数据库媒体文件,Kotlin
android·kotlin
神探小白牙3 小时前
echarts,3d堆叠图
android·3d·echarts
李白的天不白4 小时前
如何项目发布到github上
android·vue.js
summerkissyou19874 小时前
Android-RTC、NTP 和 System Time(系统时间)
android