개발을 하다가, candlestick을 화면에 표시를 해야 했었고, 그때 사용한 코드를 정리
import { useMemo } from "react";
/* 전체 폭 */
const WIDTH = 16;
/* 심지 폭 */
const WICK_WITH = 1.5;
/* 몸통 폭 */
const BODY_WIDTH = 6;
/* 몸통 시작점 값 */
const BODY_X = (WIDTH - BODY_WIDTH) / 2;
/* 차트 절대 높이 */
const CHART_HEIGHT = 35;
const trendColors = {
foreground: "#ffffff",
trendUp: "#00ff00",
trendDown: "#ff0000",
};
export default function Candlestick({
openPrice,
closePrice,
highPrice,
lowPrice,
}) {
const priceRange = highPrice - lowPrice;
const { isUp, color, bodyY, bodyHeight, wickTopY, wickBottomY } =
useMemo(() => {
if (priceRange === 0) {
// 값이 모두 같을 때 처리
return {
isUp: false,
color: trendColors.foreground, // 상승/하락이 아닌 중립 색상 추가
bodyY: CHART_HEIGHT / 2,
bodyHeight: 0,
};
}
const isUp = closePrice > openPrice; // 상승 여부
const color = isUp ? trendColors.trendUp : trendColors.trendDown; // 상승/하락 색상 적용
const scale = CHART_HEIGHT / priceRange; // 가격과 픽셀 간 비율
const bodyHeight = Math.abs(closePrice - openPrice) * scale; // 시가-종가 차이
const bodyY = isUp
? (highPrice - closePrice) * scale // 상승: 종가 기준
: (highPrice - openPrice) * scale; // 하락: 시가 기준
return { isUp, color, bodyY, bodyHeight };
}, [openPrice, closePrice, highPrice, lowPrice, priceRange, trendColors]);
return (
<svg
width={WIDTH}
height={CHART_HEIGHT}
viewBox={`0 0 ${WIDTH} ${CHART_HEIGHT}`}
>
{/* 심지 (wick) */}
<line
/* 중앙 값 x1, x2 */
x1={WIDTH / 2}
y1={0}
x2={WIDTH / 2}
y2={CHART_HEIGHT}
stroke={color}
strokeWidth={WICK_WITH}
/>
{/* 몸통 (body) */}
<rect
x={BODY_X}
y={bodyY}
width={BODY_WIDTH}
height={bodyHeight}
fill={color}
/>
</svg>
);
}
import "./styles.css";
export default function App() {
return (
<div
style={{
display: "flex",
gap: "20px",
background: "#222",
padding: "20px",
}}
>
{/* 상승하는 캔들 */}
<Candlestick openPrice={10} closePrice={15} highPrice={18} lowPrice={8} />
{/* 하락하는 캔들 */}
<Candlestick openPrice={15} closePrice={10} highPrice={18} lowPrice={8} />
{/* 시가와 종가가 같은 중립 캔들 */}
<Candlestick
openPrice={12}
closePrice={12}
highPrice={16}
lowPrice={10}
/>
{/* 작은 몸통, 긴 심지 */}
<Candlestick openPrice={13} closePrice={14} highPrice={20} lowPrice={8} />
{/* 큰 몸통, 짧은 심지 */}
<Candlestick openPrice={8} closePrice={18} highPrice={19} lowPrice={7} />
</div>
);
}
scale: 가격을 픽셀 단위로 변환하는 비율
const scale = CHART_HEIGHT / priceRange;
- CHART_HEIGHT: 차트의 높이 (픽셀 단위, 35px)
- priceRange: 가격 범위 (highPrice – lowPrice)
- scale: 1원(또는 1달러)당 차트에서 몇 픽셀인지 계산
예제
- highPrice = 18, lowPrice = 8
- priceRange = 18 – 8 = 10
- scale = 35 / 10 = 3.5
- 즉, 가격 1단위 = 3.5px
bodyHeight: 몸통의 높이
const bodyHeight = Math.abs(closePrice - openPrice) * scale;
- closePrice – openPrice: 몸통의 실제 가격 변동 폭
- Math.abs(…): 시가 < 종가(상승)든, 시가 > 종가(하락)이든 항상 양수로 변환
- scale: 픽셀 단위 변환
예제
- openPrice = 10, closePrice = 15
- Math.abs(15 – 10) = 5
- bodyHeight = 5 * 3.5 = 17.5px
bodyY: 몸통의 시작 위치
const bodyY = isUp
? (highPrice - closePrice) * scale // 상승: 종가 기준
: (highPrice - openPrice) * scale; // 하락: 시가 기준
- 캔들스틱의 y 좌표는 highPrice에서부터 계산됨.
- 상승(isUp = true): 종가(closePrice) 기준으로 계산
- 하락(isUp = false): 시가(openPrice) 기준으로 계산
예제 (상승)
- highPrice = 18, closePrice = 15
- (18 – 15) * 3.5 = 3 * 3.5 = 10.5
- bodyY = 10.5px → 위에서부터 10.5px 아래에 위치
예제 (하락)
- highPrice = 18, openPrice = 15
(18 - 15) * 3.5 = 10.5
- bodyY = 10.5px → 위에서부터 10.5px 아래에 위치
정리
- scale → 가격을 차트 높이에 맞게 픽셀 단위로 변환
- bodyHeight → 몸통의 높이를 계산
- bodyY → 몸통이 시작될 위치를 결정
캔들스틱 차트가 내포하는 의미: https://steemit.com/kr/@phuzion7/candlestick-patterns