메인 프로젝트 말아먹고 nextjs를 공부를 시작하니, 랜더링이니 SEO니 국비에서 REACT에서 컴포넌트나 만들고 상태관리나 했던 때는 중요성을 몰랐던 것들이 무더기로 다가오게 됐다. 막상 이렇게 모르는 것이 많다고 느껴지니 취업 지원서를 넣기가 망설여 졌고, serverless api, ssr, seo 등 여러 이슈들을 공부 하고 연습하느라 토이프로젝트나 기본 프로젝트를 만들게 되었다.
간단한 프로젝트더라도 api, 함수 그리고 훅을 만들어서 유저 데이터를 포함한 서비스 데이터의 흐름을 어느정도 통제하고 나면 진이 빠져서 막상 프론트에서는 구현된 기능의 반도 구현 못하는 경우가 생겼다. 상황이 이렇다 보니, 지금 나의 블로그를 확인해 보니 포트폴리오랄게 없더라...
코인이나 주식에 관심이 생겼을 무렵 가끔 스켈핑을 하곤 했는데, 이익은 지속적으로 기대할 수 있었지만 시간과 스트레스가 너무 심했던 때가 있었다. 그 때, 할줄 아는 거라곤 액셀과 c언어가 전부였던 때, 자동매매에 도전하게 됐는데 컴공적 지식이 없으니 엉뚱한 곳에서 시간을 허비하다가 중도 하차한 적이 있다. (백테스트, 시현 모두 완성했지만 로직 적용하고 서버를 구성 하는 등 스택 부족으로 미루게 됨)
이렇게 시스템 트레이딩이 내 인생의 숙원사업이 되었고 프론트엔드 과정에 임하게 됐다. 사실 프론트엔드 포트폴리오로 시스템트레이딩 앱을 만드는 것이 좋은 방법은 아닌거 같다. 프론트 외적인 요소들의 일이 훨씬 많고 솔로 프로젝트는 채용시장에서 메리트 있는 포트폴리오는 아니기 때문이다.
ApexCharts는 CnadleChart를 지원하면서도 설정 자유도가 높아서 이번 프로젝트에 사용해보기로 했다. 많은 애니메이션 라이브러리가 그렇듯, ApexChart도 ssr을 지원하지 않아, Next.js에서 import 하면 window가 정의되지 않는 오류를 발생시킨다. 개발 환경에서는 문제가 되지 않지만(오류를 무시한다면), 배포 환경에서는 분명히 문제가 되므로 동적 임포트가 필요하다.
import dynamic from "next/dynamic";
const Chart = dynamic(() => import("react-apexcharts"), {
ssr: false,
});
ApexCharts의 공식문서에서 제공하는 pie chart를 그리는 방법을 함수형 컴포넌트로 바꾸면 아래와 같이 사용 할 수 있다.
"use client";
import React, { useState } from "react";
import dynamic from "next/dynamic";
const ReactApexChart = dynamic(() => import("react-apexcharts"), {
ssr: false,
});
type Props = {
title: string;
data: { profit: number; loss: number };
};
//차트 이름(title)과 데이터(이 컴포넌트는 손익 비교만 함)
//data 갯수는 필요에 따라 늘려서 매핑하면 됨.
const PieChart = ({ title, data: { profit, loss } }: Props) => {
const [chartState, setChartState] = useState({
series: [profit, loss],
options: {
chart: {
type: "donut" as const,
},
title: {
text: title,
align: "left" as const,
},
labels: ["Profit", "Loss"],
colors: ["#26A69A", "#EF5350"],
responsive: [
{
breakpoint: 480,
options: {
chart: {
width: 200,
},
legend: {
position: "bottom" as const,
},
},
},
],
},
});
return (
<div className="p-4">
<ReactApexChart
options={chartState.options}
series={chartState.series}
type="donut"
/>
</div>
);
};
export default PieChart;
백테스트 에서는 실전에서 사용할 전략을 지난 틱 거래 데이터에 적용해 예상 수익률을 가늠해 본다. UI를 만들고 나서는 이 백테스트 로직을 수정하는 것이 주요 업무가 될 것이다. UI를 구성하기 위해 수익률은 고려하지 않고, 거래가 일어날만한 로직만 구현해 본다.
앞서 설명 했듯이 데이터는 bybit에서 제공하는 틱 데이터를 분봉으로 가공해서 쓴다. 가공된 데이터는 서버에도 업로드 해놨지만, 개발환경에서는 렌더링 방식을 막론하고 페이지가 마운트 될 때마다 데이터 패칭이 일어나므로 로컬에 저장해 놨다. 틱데이터와 달리 가공된 데이터는 60일 데이터분이 5MB에 그치므로 로컬에 데이터를 쓴다.
nextjs에서 로컬에 저장된 데이터는 리액트의 그것과는 달리 클라이언트 컴포넌트에서는 데이터 패칭이 불가능하다. 서버 컴포넌트로 데이터를 불러와 클라이언트 컴포넌트로 데이터를 전달한다.
MACD는 이동 평균선에 대한지표로 아래 그래프에서의 파란색 막대 그래프가 MACD를 나타낸다. 전략 초안에서는 단순히 MACD가 음수에서 상승추세를 보이면 매수를 하여 지정된 손실제한지점(-1%내외)에 도달하면 손절 하거나 MACD가 양수인 지점에서 n번의 상승을 거듭하면 매도하고 이익 실현 지점(1%내외)에 도달하면 익절한다.
백테스트를 마치면 많은 데이터들을 반환한다.아래 데이터들은 초안이고, 필요에 따라 추가 삭제 예정이다.
type MACDResult = {
ma1: ApexChartData[];
ma2: ApexChartData[];
macd: ApexChartData[];
macdSig: ApexChartData[];
macdOsc: ApexChartData[];
long: ApexChartData[];
longSell: ApexChartData[];
short: ApexChartData[];
shortSell: ApexChartData[];
dailyResult: DailyResult[];
uploadResult?: UploadDailyResult;
};
위 데이터들을 이용해서 전에 보았던 분봉차트를 그리게 된다. 이 데이터의 일부 혹은 전부가 prisma 를 이용해 planet scale에 업로드 된다.
export async function POST(req: Request) {
const request = await req.json();
const body = await JSON.parse(request.body);
const {
asset,
strategy,
constants,
result: { fluctuation, profitAverage, dailyReturn },
} = body;
//업로드 하고자 하는 데이터가 이미 있는지 확인
const data = await client.bTResult.findFirst({
where: { asset, strategy, constants },
});
//데이터가 존재 하는 경우, 메세지와 데이터 반환
if (data) {
console.log("already exsits");
return NextResponse.json({ ok: false, data });
}
//데이터가 존재하지 않는 경우 데이터 업로드
const result = await client.bTResult.create({
data: {
asset,
strategy,
constants,
...
},
});
return NextResponse.json({ ok: true, data: result });
}
백테스트를 완료한 매매 전략들은 각각의 데이터들을 반환한다. 일일 수익률에 대한 평균, 표준편차, 이들을 용한 평균치 분포, 표준편차 분포등이다. 이 데이터를 가지고도 백테스트가 왜곡된 결과를 주는지에 대한 검증이 가능하다. 가령 수익률의 분포는 정규 분포를 표준편차의 분포는 포아송 분포를 반환한다면 어느정도 데이터의 경향성을 신뢰 할 수 있는 근거가 될 수 있을 것이다.
또한 전략 설계에 따라 분석할 수 있는 데이터들의 종류와 양은 천차 만별일 것이므로 이를 나타내는 대쉬보드도 더 복잡해 질 것이다.
가치 있는 정보 공유해주셔서 감사합니다.