가상 투자 서비스 개발기(1)

김준영·2022년 3월 20일
0
post-thumbnail
레파지토리

https://github.com/wnsdudSoftkim/virtual_investment

크립토 관련 회사를 다니면서, 실시간 데이터들이 빠른 속도로 차트에 기입되는 형상이나, 클린코드로 짜여져 있는 백엔드 코드를 보면서 조금 더 웹 서비스를 구축하는데 있어서, 구조화 된 형식과 잘 짜여진 코드 형식을 체득하고자 프로젝트를 기획하게 되었다.
프론트는 vue, 백엔드는 fastapi를 이용하고 그 외, mongodb와 docker를 활용 해야 겠다고 생각하였고 클라우드 환경은 netlify와 heroku를 이용하였다.
heroku는 30분 동안 유저 입력이 없으면 서버가 sleep하고 30분 후에 다시 접속 했을 때 가동되는데 10초 정도 걸린다. 만약 무료 클라우드 서비스인 heroku를 쓰고 싶다면 이 점을 명시하는게 좋다.
다만 cronjob(https://console.cron-job.org/dashboard)을 통해 15분 마다 ping을 날려주어 이 점을 회피 할 수 있는데 그렇다 하더라도, 서버 응답시간이 1.5초 정도 걸리는 듯 하다.

보통 웹서비스를 이용하는 유저는 평균 3초 대기시간 이후엔 나간다고 하니, 그냥 유명한 클라우드 서비스를 이용하는게 마음 편할 듯 하다. (heroku 회선이 한국에 없어서 느린거 같다. AWS 는 2017년에 생김)

이슈트래킹

  • 기본적인건 그냥 github project를 이용했는데 개인프로젝트면 주석처리로 코드에 TODO 적어놓는게 편할수도?
    - Intelij는 푸시하기 전에 TODO 키워드를 탐색하므로 이런 면에서 좋은듯.

설계

프론트는 이전까지 react를 이용하여서 그런지, 익숙한 방식인 container-component 아키텍처를 이용하였고,
기본적인 정보들은 상태 관리 라이브러리(vuex)를 통해서 관리하고, 해당 데이터는 container에서 component로 props를 통해
내려주는 하향식 방식을 이용하였다.

  • VUEX란?
    - 프론트엔드의 데이터베이스라고 생각하면 편하다. 쉽게 드나들 수 있는 LocalStorage.
    • react의 redux랑 유사한 구조를 가지고 있어서 react 유저는 러닝커브가 완만할듯.
  • vue3는 react 친화적으로 바뀌었다고 하니, 프레임워크간의 러닝커브는 더 완만해질 예정.
    기본적인 방식은 문서 참조.

백엔드도 정확하게 분리되어 있는 각 기능별로 mongodb -> 데이터 가공 -> front로 내려주고,
websocket을 통해 저장되어 있는 코인데이터를 내려주는 역할을 맡고있다.
처음에는 mongodb 배포 과정에서 aws를 사용함으로 비용이 들 줄 알았지만, mongodb atlas 무료 버전을 사용함으로 비용 문제는 없었다.

차트 라이브러리, scss 설정

기본 적인 차트 라이브러리는 chart.js를 이용하였고, 관련 문서는 다음과 같다.
https://www.chartjs.org/docs/latest/samples/information.html
상세한 sample과 설명이 있으니, 그냥 따라 적으면 됨.

const config = {
  type: 'line',
  data: data, //아까 말했듯. 이 data는 container에서 props를 통해 내려준다.
  options: {
    responsive: true,
    plugins: {
      legend: {
        position: 'top',
      },
      title: {
        display: true,
        text: 'Chart.js Line Chart'
      }
    }
  },
}
  • 주의해야할 점. shallow copy
    props를 통해 데이터를 내려보낼 때, const copied =[1,2,3]로 할당하게 되면 같은 주소 값을 가지게 되므로 DOM이 변경사항을 인식하지 못한다.
    아래는 shallow copy하는 두가지 방법.
const clone2 = Object.assign({}, original);
const copied = [1,2,3].slice()
vue.config.js

const path = require('path')

module.exports = {
  chainWebpack: config => {
    config.resolve.alias
      .set('@', path.resolve(__dirname, 'src/'))
  },
  css: {
    loaderOptions: {
      scss: {
        additionalData: `@import "~@/assets/scss/colors.scss";`
      }
    }
  } # npm install -D sass-loader@^10 sass
}

vs코드를 사용하다보면, 상대경로를 통해 import 하는 부분들이 상당히 복잡하게 느껴진다.
이런 부분에서 root를 src로 정해놓고 절대경로를 통해 import 시키는것.
또한 아래 있는 코드는 기본적인 scss 함수를 정의해놓은 것이다. 미리 정의해 놓은 scss 함수(변수)를
매 함수마다 import할 필요없이. 자동으로 import 된다.
예시 코드

import LeftSideBarLayout from '@/components/layout/LeftSideBarLayout'
<style lang="scss">
  .horizontal {
      list-style-type: none;
      margin:0;
      padding:0;
      overflow:hidden;
      background-color: $baseColor; // 위에 assets/scss/colors에서 정의한 변수 
      display: flex;
      justify-content: space-around;
      a{
          align-content: center;
          display: inline-block;
          color: white;
          text-align: center;
          padding: 14px 16px;
          font-size: 18px;
          text-decoration: none;
          cursor: pointer;
          &:hover {
              background-color: $secondColor;
          }
      }
  }
</style>

coindata

백엔드는 로그기록이라던가 모니터링 하는 그런 부분들은 없고, 데이터가 내려가는 과정에서, 정확하게 요구되어진 모델로 내려가는지 검증만 있다.
먼저, mongodb를 이용하였는데 sql은 find, find_one, update, create, aggregate를 이용하였다.
coindata를 내려받는 코드는 시중에 너무 많으므로 이 부분도 websocket을 이용하여 엑셀의 형식에 맞게 저장한다는 것만 알아두자.

import time
from datetime import datetime
from fastapi import APIRouter
from config.config import binance_symbol_uri
import requests
import pandas as pd

from logic.binance import BinanceHandler as bh

router = APIRouter(
    prefix='/binance',
    tags=['binance']
)


@router.get('/previous-data')
async def get_previous_data():
    years = bh.select_year(2017, 2022)
    symbols = bh.select_symbol_top()
    columes = bh.select_column()
    for symbol in symbols:
        for year in years:
            start_date = f'{year}-01-01'
            end_date = f'{year}-12-31'
            df = await _get_binance_data(start_date, end_date, symbol, columes)
            df.to_csv(f'C:/Users/lenovo/CoinData/1h/{symbol[:3].lower()}_{year}.csv', index=False)  # csv파일로 저장하는 부분
            time.sleep(1)


async def _get_binance_data(start_date, end_date, symbol, columns):
    data = []
    start = int(time.mktime(datetime.strptime(start_date + ' 00:00', '%Y-%m-%d %H:%M').timetuple())) * 1000
    end = int(time.mktime(datetime.strptime(end_date + ' 23:59', '%Y-%m-%d %H:%M').timetuple())) * 1000
    params = {
        'symbol': symbol,
        'interval': '1h',
        'limit': 1000,
        'startTime': start,
        'endTime': end
    }

    while start < end:
        print(datetime.fromtimestamp(start // 1000))
        params['startTime'] = start
        result = requests.get(binance_symbol_uri, params=params)
        js = result.json()
        if not js:
            break
        data.extend(js)
        start = js[-1][0] + 60000
    if not data:
        print('no data')
        return
    df = pd.DataFrame(data)
    df.columns = columns
    df['Open_time'] = df.apply(lambda x: datetime.fromtimestamp(x['Open_time'] // 1000).isoformat(), axis=1)
    df = df.drop(columns=['Close_time', 'ignore'])
    df['Symbol'] = symbol
    df.loc[:, 'Open':'tb_quote_av'] = df.loc[:, 'Open':'tb_quote_av'].astype(float)  # string to float
    df['trades'] = df['trades'].astype(int)
    return df

데이터가 엑셀(자신의 원하는 json으로 받던 상관 없음)의 형식으로 잘 들어왔으면 mongodb에 dump시켜주는 작업도 병행해야한다.


현재 개인프로젝트는 윈도우 환경이므로 cmd파일을 이용하였다. 개인적으로 bash문법이 더 좋은듯.

@echo off
set price_list=btc_ eth_
set year_list=2017 2018 2019 2020 2021
set value=_1h
set host="-"
for %%a in (%price_list%) do (
    for %%b in (%year_list%) do (
        mongoimport --uri %host%  --collection %%a%%b_1h --type csv --headerline --ignoreBlanks --file C:\Users\lenovo\CoinData\1h\%%a%%b.csv
    )
)

이 역시 관련 문서는 https://docs.mongodb.com/database-tools/mongoimport/ 에 너무 잘 나와있으므로. 그냥 따라 적으면됨.
cmd 파일을 실행시켜서 데이터가 잘 들어가는지 확인하자.

0개의 댓글