Cùng làm Candlestick Chart. Thay vì làm người chơi, hãy làm chủ

Mở đầu Chào mọi người, chắc hẳn mọi người đã quá quen thuộc với trào lưu Trading trong những năm gần đây. Dưới góc độ của một nhà phát triển chúng ta hãy cùng tìm hiểu các yếu tố kĩ thuật tạo nên một sàn giao dịch như thế. Mở đầu hôm nay chúng ta

Mở đầu 👋

Chào mọi người, chắc hẳn mọi người đã quá quen thuộc với trào lưu Trading trong những năm gần đây. Dưới góc độ của một nhà phát triển chúng ta hãy cùng tìm hiểu các yếu tố kĩ thuật tạo nên một sàn giao dịch như thế. Mở đầu hôm nay chúng ta sẽ cùng làm một module khá quen thuộc, tạo nên nét đặt trưng của các sàn giao dịch đó là Candlestick Chart

Công nghệ mình dùng cho project là: ReactJS, SVG

Nội dung

1.Data

Đầu tiên chúng ta cùng tìm hiểu về những ngọn nến của Candle Chart. Một Candle (ngọn nến) sẽ bao gồm ít nhất 4 thuộc tính: Open price, High price, Low price và Close price. Nếu Open price < Close price ngọn nến sẽ có màu xanh (mua nhà) và ngược lại ngọn nến sẽ có màu đỏ (bán nhà)

Về mặt dữ liệu, đầu vào Candle Chart của chúng ta sẽ là một mảng các object, mỗi object { O, H, L, C } đại điện cho các thuộc tính kể trên sẽ được dùng để vẽ nên một candle.

Ở đây chúng ta sẽ tạo một module data.js để generate dữ liệu

constDirection=()=>(Math.random()<0.5?-1:1)constRandom=(rand)=> Math.random()* rand
constRandomProbabilityZero=(rand)=>(Math.random()<0.3?0:Random(rand))//cái random này để có 30% trả về 0 để cho cái Chart của chúng ra đỡ rối mắt thôiconstRound=(num)=> Math.round(num *100)/100// lấy 2 chữ số thập phânexportfunctiongenCandle(prevCandleC){constO= prevCandleC        
  // ở đây mình lấy Open price của nến hiện tại sẽ bằng Close price của cây nến trước đó. // Thực tế 2 thông số này thường sẽ không bằng nhau, nhưng mình để thế cho dễ generate vậy :((constC=Round(O+Direction()*Random(500))constH=Round((O<C?C:O)+RandomProbabilityZero(300))constL=Round((O>C?C:O)-RandomProbabilityZero(300))return{O,H,L,C}}exportfunctiongenCandles(initNum, amount){let candles =[genCandle(initNum)]
  Array.from(Array(amount)).forEach((x, i)=>{// hàm này tương đương for(let i = 0; i < amount; i++) nhìn cũng rối nhưng không sao, mình thích vậy :))
    candles.push(genCandle(candles[i].C))})return candles
}

2.Vẽ nến nào

Ở đây mình sẽ dùng thẻ svg, bạn nào chưa biết có thể tìm hiểu thông qua bài viết trước của mình nhé npmjs chart

// CandleChart.jsx<svg
  width='1200'
  height='600'><path fill='#ee4947' stroke='#ee4947' d={pathD.dRed}></path><path fill='#22f6db' stroke='#22f6db' d={pathD.dGreen}></path>// ...</svg>

Chúng ta sẽ nhóm hết các cây nến màu xanh, đỏ vào với nhau để trong cùng một thẻ path nhằm cho đỡ rối và tăng hiệu xuất.

Trước hết tạo module Candle.js để tính ra pathD từ dữ liệu đầu vào là một mảng các cây nến candles. Ở đây mình sẽ fix cứng một số thông số

constHEIGHT=600constCANDLE_W=20// chiều rộng cấy nếnconstD=4// khoảng cách 2 cây nếnlet deltaY =0// tỉ số số pixel tương ứng với giá trị (H,O,L,C price) của cây nếnlet min =0// max price của tất cả cây nếnlet max =0exportfunctioncalcPathD(candles){// hàm tính `d` của tất cả cây nếnfindMinMax(candles)
  deltaY =HEIGHT/(max - min)let dRed =''let dGreen =''
  candles.forEach((candle, index)=>{if(candle.O< candle.C){// nến xanh
      dGreen +=calcD(candle, index)}else{// nến đỏlet candleTem ={H: candle.H,O: candle.C,C: candle.O,L: candle.L}
      dRed +=calcD(candleTem, index)}})return{ dRed, dGreen }}functioncalcD(candle, index){// hàm tính `d` của một cây nến cụ thểlet posH =(max - candle.H)* deltaY
  let posO =(max - candle.O)* deltaY
  let posC =(max - candle.C)* deltaY
  let posL =(max - candle.L)* deltaY
  let posXCandle = index *(CANDLE_W+D)+CANDLE_W/2// tính tọa độ của cây nến theo trục Xlet d =`M ${posXCandle}${posH} L ${posXCandle}${posO}`// vẽ cái đường upper shadow
  d +=`M ${posXCandle -CANDLE_W/2}${posO}`// vẽ body cây nến
  d +=`L ${posXCandle +CANDLE_W/2}${posO}`
  d +=`L ${posXCandle +CANDLE_W/2}${posC}`
  d +=`L ${posXCandle -CANDLE_W/2}${posC} Z `
  d +=`M ${posXCandle}${posC} L ${posXCandle}${posL}`// vẽ đường lower shadowreturn d
}// ....constfindMinMax=(candles)=>{
  min = Number.MAX_VALUE
  max = Number.MIN_VALUE
  candles.forEach((candle)=>{if(max < candle.H) max = candle.Hif(min > candle.L) min = candle.L})}

Ở component CandleChart chúng ta sẽ import 2 module và khởi tạo Chart

import{ genCandle, genCandles }from'./data'import{ calcPathD, calcValueY }from'./Candle'let candles =genCandles(10000,40)  khởi tạo

functionCandleChart(){const[pathD, setPathD]=useState(calcPathD(candles))// tính attribute d của thẻ path//...return(<>///...<svg
        width='1200'
        height='600'><path fill='#ee4947' stroke='#ee4947' d={pathD.dRed}></path><path fill='#22f6db' stroke='#22f6db' d={pathD.dGreen}></path>//...</svg></>)}exportdefault CandleChart

3.Thêm sự kiện mouse xem giá trị của từng cây nến

import React,{ useState, useEffect }from'react'import{ genCandle, genCandles }from'./data'import{ calcPathD, calcValueY }from'./Candle'let candles =genCandles(10000,40)functionCandleChart(){const[pathD, setPathD]=useState(calcPathD(candles))const[pointer, setPointer]=useState({ x:-100, y:-100, candleIndex:-1})consthandleMouseMove=(e)=>{let x = e.nativeEvent.layerX
    let y = e.nativeEvent.layerY
    let candleIndex = Math.floor(x /24)
    x = candleIndex *24+10setPointer({ x, y, candleIndex })}consthandleMouseLeonMouseLeave=(e)=>{setPointer({ x:-100, y:-100, candleIndex:-1})}const candleSelected = candles[pointer.candleIndex]const color = candleSelected?.O> candleSelected?.C?'red':'green'let delta =(candleSelected?.C- candleSelected?.O).toFixed(2)
  delta = delta >0?`+${delta}`: delta

  const pointerValueY =calcValueY(pointer.y)return(<><div className='info'>// các price của cây nến<div className='label'>{'Open '}</div><div className={`value ${color}`}>{candleSelected?.O}</div><div className='label'>{'High '}</div><div className={`value ${color}`}>{candleSelected?.H}</div><div className='label'>{'Low '}</div><div className={`value ${color}`}>{candleSelected?.L}</div><div className='label'>{'Close '}</div><div className={`value ${color}`}>{candleSelected?.C}</div><div className={`value ${color}`}>{delta}</div></div><svg
        width='1200'
        height='600'
        onMouseMove={handleMouseMove}
        onMouseLeave={handleMouseLeonMouseLeave}><path fill='#ee4947' stroke='#ee4947' d={pathD.dRed}></path><path fill='#22f6db' stroke='#22f6db' d={pathD.dGreen}></path><line x1={pointer.x} y1={0} x2={pointer.x} y2={800} stroke='#888' strokeDasharray='5'/>// vẽ 2 đường nét đứt ngang và dọc<line x1={0} y1={pointer.y} x2={1130} y2={pointer.y} stroke='#888' strokeDasharray='5'/><g><line x1={1130} y1={0} x2={1130} y2={600} stroke='#666'/><text x={1135} y={pointer.y +6} fill='#aaa'>// xem price của tọa độ đang chọn theo trục Y{pointerValueY}</text></g></svg></>)}exportdefault CandleChart

4.Thêm nến vào biểu đồ để biểu đồ chuyển động như thật : ))

Chúng ta chỉ cần thêm setInterval để thêm candle vào biến candles mỗi một khoảng thời gian là chart sẽ chạy ngay thôi

// CandleChart.jsx//...useEffect(()=>{let setIntervalId =setInterval(()=>{
    candles.shift()// thêm vào thì phải xóa đi
    candles.push(genCandle(candles[candles.length -1].C))setPathD(calcPathD(candles))// set lại giá trị pathD là React sẽ tự rereder lại cho ta},2000)//thêm mỗi 2 giâyreturn()=>{clearInterval(setIntervalId)}},[])//...

Vậy là ok rồi đó 👏

Nhìn qua có vẻ ổn đúng không? Nhưng có một vấn đề là mỗi lần chúng ta move chuột trên biểu đồ thì component sẽ được rerender lại để cập nhật thông tin chi tiết của cây nến, 2 đường nét đứt và cả price theo trục y nữa, mỗi lần như thế hàm calcPathD sẽ được gọi lại gây lãng phí tài nguyên, với dữ liệu nhiều sẽ làm trình duyệt bị giật ảnh hưởng đến trải nghiệm của người dùng (trong thực tế vấn đề này xảy ra khá nhiều, những bạn mới thường không để ý, hoặc không biết để xử lý). Nếu bạn nào quan tâm vấn đề này hãy ping mình biết (upvote, follow, comment 😂), mình sẽ viết một bài giới thiệu một kĩ thuật rất phổ biến memorization để giải quyết bài toán này và một số bài toán tương tự nhé.
Ngoài ra bạn nào muốn mình viết thêm bài tích hợp xử lý realtime nhận dữ liệu từ server vào project này thì cũng ping mình biết nhé 😂

À đây là demo dạo này mạng hơi kém, ai không xem được có thể bookmark lại để xem sau nhé

Kết luận

Cái title mình giật hơi quá mong các anh chị, các bạn, các em thông cảm nhé 😢

Cảm ơn các bạn đã đọc!!!

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ