힐링 휴식 그리고 개발

트레이딩 뷰 차트 소켓데이터 적용하기 - 까먹는 개발자 본문

개발관련/차트

트레이딩 뷰 차트 소켓데이터 적용하기 - 까먹는 개발자

하루정도 2020. 11. 17. 15:08

트레이딩 뷰 차트 - Technical Analysis Charts for Reactjs (2 / 2)


TradingView Chart를 연동 create-react-app에 연동 시킨 후, 데이터 베이스와 소켓 데이터를 바인딩 시키기 위한 예제입니다.

준비사항

https://github.com/ookm1020/basic-tradingview.git

 

지난 1편에서 소켓 모듈을 설치했기 때문에 따로 업급은 하지 않겠습니다. 각 함수들에 것은 Jon Church님의 글에 자세하게 설명이 되어 있으니 참고하시면 됩니다.
시작하기 전에-
소켓 데이터 연결을 하기에 앞서, 업비트의 오픈API를 통해서 현재 체결되는 데이터를 소켓을 통해 받아오는 서버를 켜놓고 이 예제를 진행했습니다.

WebSocket을 이용한 업비트 시세 수신을 이용하여 가상 소켓 서버를 만드신 후, 진행하시면 원활하게 테스트가 가능합니다.

데이터 포맷
업비트에서 수신되는 실시간 매칭 데이터는 다음과 같습니다.
data: {
  acc_ask_volume: 614.8915618
  acc_bid_volume: 419.11867853
  acc_trade_price: 18943312506.64586
  acc_trade_price_24h: 83406758933.7941
  acc_trade_volume: 1034.01024033
  acc_trade_volume_24h: 4599.40064738
  ask_bid: "ASK"
  change: "FALL"
  change_price: 3000
  change_rate: 0.0001641587
  code: "KRW-BTC"
  delisting_date: null
  high_price: 18420000
  highest_52_week_date: "2020-11-16"
  highest_52_week_price: 18450000
  is_trading_suspended: false
  low_price: 18207000
  lowest_52_week_date: "2020-03-13"
  lowest_52_week_price: 5489000
  market_state: "ACTIVE"
  market_state_for_ios: null
  market_warning: "NONE"
  opening_price: 18275000
  prev_closing_price: 18275000
  signed_change_price: -3000
  signed_change_rate: -0.0001641587
  stream_type: "REALTIME"
  timestamp: 1605590707860
  trade_date: "20201117"
  trade_price: 18272000
  trade_status: null
  trade_time: "052507"
  trade_timestamp: 1605590707000
  trade_volume: 0.000132
  type: "ticker"
}
트레이딩 뷰 차트에 데이터 연결
실시간 소켓 데이터는 stream.js에서 관리합니다.
폴더 구조는 다음과 같습니다.

![스크린샷 2020-11-17 오후 2.30.49](/Users/lys/Documents/MD 문서/스크린샷 2020-11-17 오후 2.30.49.png)

실질적으로 stream.js에 작성되어 있는 updateBar 함수가 차트의 캔들을 업데이트를 해주는데 해당 함수를 따로 건드리지 않고 필요한 정보만 주입하는 방식으로 진행합니다. 수정 부분 - updateBar func, socket module

  1. 기존 require방식이 마음에 들지 않아 import방식으로 변경한 부분입니다.
// api/stream.js
import historyProvider from './historyProvider.js'
// we use Socket.io client to connect to cryptocompare's socket.io stream

// 업비트에서 수신할 가상의 소켓서버와의 연결을 위해 주석처리합니다.
//var io = require('socket.io-client')
//var socket_url = 'wss://streamer.cryptocompare.com'
//var socket = io(socket_url)

import socketClient from "socket.io-client";

// 로컬에 3001로 켜져 있는 가상의 소켓서버로 소켓을 연결합니다. 예제에서는 BTC코인의 시세만 반영하기 때문에 data=KRW-BTC로 지정했습니다.
const socket = socketClient(`http://localhost:3001?data=KRW-BTC`);

.
.
.
  1. socket.on의 코드를 가상의 소켓서버와 채널을 맞추고 data를 업비트의 데이터 포맷에 맞게 변경합니다.
// 기존 코드
socket.on('m', (e) => {
 // here we get all events the CryptoCompare connection has subscribed to
 // we need to send this new data to our subscribed charts
 const _data= e.split('~')
 if (_data[0] === "3") {
  // console.log('Websocket Snapshot load event complete')
  return
 }
 const data = {
  sub_type: parseInt(_data[0],10),
  exchange: _data[1],
  to_sym: _data[2],
  from_sym: _data[3],
  trade_id: _data[5],
  ts: parseInt(_data[6],10),
  volume: parseFloat(_data[7]),
  price: parseFloat(_data[8])
 };

 // 변경 코드
 socket.on('/ticker', (e) => {
   console.log("match data: ", e);

      const data = {
     sub_type: parseInt(0, 10),
     exchange: "BITHUMB",
     to_sym: e.code.split("-")[1], // "BTC"
     from_sym: e.code.split("-")[0], // "KRW"
     // trade_id: _data[5],
     trade_id: "",
     ts: e.timestamp / 1000, // timestamp
     volume: e.trade_volume, // 거래량
     price: e.trade_price // 가격
   };
  1. 차트에서 원하는 형식에 맞게 소켓으로 수신된 데이터로 바꿔줍니다. (간단 구현을 위해 close와 volume만 바꿨습니다.)
function updateBar(data, sub) {
 var lastBar = sub.lastBar
 let resolution = sub.resolution
 if (resolution.includes('D')) {
  // 1 day in minutes === 1440
  resolution = 1440
 } else if (resolution.includes('W')) {
  // 1 week in minutes === 10080
  resolution = 10080
 }
var coeff = resolution * 60
 // console.log({coeff})
 var rounded = Math.floor(data.ts / coeff) * coeff
 var lastBarSec = lastBar.time / 1000
 var _lastBar

if (rounded > lastBarSec) {
  // create a new candle, use last close as open **PERSONAL CHOICE**

  // 기존 코드
  _lastBar = {
   time: rounded * 1000,
   open: lastBar.close,
   high: lastBar.close,
   low: lastBar.close,
   close: data.price,
   volume: data.volume
  }

  // 변경 코드
  _lastBar = {
   time: rounded * 1000,
   open: lastBar.close,
   high: lastBar.close,
   low: lastBar.close,
   close: data.price,
   volume: data.volume
  }

 } else {
  // update lastBar candle!
  if (data.price < lastBar.low) {
   lastBar.low = data.price
  } else if (data.price > lastBar.high) {
   lastBar.high = data.price
  }

  lastBar.volume += data.volume
  lastBar.close = data.price
  _lastBar = lastBar
 }
 return _lastBar
}

/ticker 채널에서 받아오는 데이터는 위에서 언급한 데이터 포맷입니다. 또한 거래소 이름을 BITUMB로 한 이유는 현재 트레이딩 뷰 측에서 승인이 되지 않은 거래소를 사용할 수 없기 때문에 임시로 지정했습니다.

따라서 다음과 같이 index.jsx의 symbol도 변경합니다.

import * as React from "react";
import "./index.css";
import Datafeed from "./api/";

function getLanguageFromURL() {
  const regex = new RegExp("[\\?&]lang=([^&#]*)");
  const results = regex.exec(window.location.search);
  return results === null ? null : decodeURIComponent(results[1].replace(/\+/g, " "));
}

export class TVChartContainer extends React.PureComponent {
  static defaultProps = {
    symbol: "BITHUMB:BTC/KRW",
    interval: "1",
    containerId: "tv_chart_container",
    libraryPath: "/charting_library/",
    chartsStorageUrl: "https://saveload.tradingview.com",
    chartsStorageApiVersion: "1.1",
    clientId: "tradingview.com",
    userId: "public_user_id",
    fullscreen: false,
    autosize: true,
    studiesOverrides: {},
  };

  componentDidMount() {
    const widgetOptions = {
      debug: false,
      symbol: this.props.symbol,
      datafeed: Datafeed,
      interval: this.props.interval,
      container_id: this.props.containerId,
      library_path: this.props.libraryPath,
      locale: getLanguageFromURL() || "en",
      disabled_features: ["use_localstorage_for_settings"],
      enabled_features: ["study_templates"],
      charts_storage_url: this.props.chartsStorageUrl,
      charts_storage_api_version: this.props.chartsStorageApiVersion,
      client_id: this.props.clientId,
      user_id: this.props.userId,
      fullscreen: this.props.fullscreen,
      autosize: this.props.autosize,
      studies_overrides: this.props.studiesOverrides,
      overrides: {
        "mainSeriesProperties.showCountdown": true,
        "paneProperties.background": "#fff",
        "paneProperties.vertGridProperties.color": "#E6EFF4",
        "paneProperties.horzGridProperties.color": "#E6EFF4",
        "symbolWatermarkProperties.transparency": 90,
        "scalesProperties.textColor": "#AAA",
        "mainSeriesProperties.candleStyle.wickUpColor": "#336854",
        "mainSeriesProperties.candleStyle.wickDownColor": "#7f323f"
      }
    };

    Datafeed.onReady(() => {
      const widget = (window.tvWidget = new window.TradingView.widget(widgetOptions));

      widget.onChartReady(() => {
        console.log("Chart has loaded!");
      });
    });
  }

  render() {
    return <div id={this.props.containerId} className={"TVChartContainer"} />;
  }
}

※ 2 편은 업비트의 시세 수신 API를 이용해서 간단하게 최신시세를 반영하는 예제입니다. 승인 관련 문제때문에 BITUMB의 거래소 이름으로 진행했으며, 정상적으로 트레이딩뷰 측의 신청서를 통해서 인증을 받으신 분들은 자체 거래소 이름을 작성하시면 됩니다.

※ 요즘 정신이 없네요. 많은 분들이 2편을 요청하셔서 간략하게 나마 작성해봤습니다. 너무 간략하기는 하지만, 질문 사항 있으신 분들은 댓글 또는 방명록에 작성해주시면 최대한 답변해드리겠습니다 :)

예정 사항으로는 차트의 색상 변경 및 업비트 시세 수신 가상 소켓 서버 구현 등을 생각하고 있습니다. 여유가 생기는 대로 작성해보도록 하겠습니다..🙇‍♂️

감사합니다.

이 글을 작성하기 위해 Jon Church님의 글 을 참고하였습니다.

'개발관련 > 차트' 카테고리의 다른 글

트레이딩 뷰 차트 적용하기 - 까먹는 개발자  (40) 2019.07.10
Comments