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

效果

相关推荐
2501_944525541 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 支出分析页面
android·开发语言·前端·javascript·flutter
松☆3 小时前
Dart 核心语法精讲:从空安全到流程控制(3)
android·java·开发语言
_李小白4 小时前
【Android 美颜相机】第二十三天:GPUImageDarkenBlendFilter(变暗混合滤镜)
android·数码相机
小天源7 小时前
银河麒麟 V10(x86_64)离线安装 MySQL 8.0
android·mysql·adb·麒麟v10
2501_915921437 小时前
傻瓜式 HTTPS 抓包,简单抓取iOS设备数据
android·网络协议·ios·小程序·https·uni-app·iphone
csj508 小时前
安卓基础之《(20)—高级控件(2)列表类视图》
android
JMchen1238 小时前
Android计算摄影实战:多帧合成、HDR+与夜景算法深度剖析
android·经验分享·数码相机·算法·移动开发·android-studio
恋猫de小郭9 小时前
Flutter 在 Android 出现随机字体裁剪?其实是图层合并时的边界计算问题
android·flutter·ios
2501_9159184110 小时前
把 iOS 性能监控融入日常开发与测试流程的做法
android·ios·小程序·https·uni-app·iphone·webview
benjiangliu11 小时前
LINUX系统-09-程序地址空间
android·java·linux