Skip to content

Commit

Permalink
add BinanceDataProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
msaltnet committed Oct 29, 2023
1 parent 004ac25 commit e8f490e
Show file tree
Hide file tree
Showing 7 changed files with 451 additions and 1 deletion.
1 change: 1 addition & 0 deletions integration_tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
MassSimulatorIntegrationTests,
MassSimulator3mIntervalIntegrationTests,
)
from .binance_data_provider_ITG_test import BinanceDataProviderIntegrationTests
71 changes: 71 additions & 0 deletions integration_tests/binance_data_provider_ITG_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import unittest
from smtm import BinanceDataProvider
from unittest.mock import *


class BinanceDataProviderIntegrationTests(unittest.TestCase):
def setUp(self):
pass

def tearDown(self):
pass

def test_ITG_get_info_return_correct_data(self):
dp = BinanceDataProvider()
info = dp.get_info()
self.assertEqual("market" in info, True)
self.assertEqual("date_time" in info, True)
self.assertEqual("opening_price" in info, True)
self.assertEqual("high_price" in info, True)
self.assertEqual("low_price" in info, True)
self.assertEqual("closing_price" in info, True)
self.assertEqual("acc_price" in info, True)
self.assertEqual("acc_volume" in info, True)

def test_ITG_get_info_return_correct_data_when_currency_is_BTC(self):
dp = BinanceDataProvider("BTC")
info = dp.get_info()
self.assertEqual(info["market"], "BTC")
self.assertEqual("date_time" in info, True)
self.assertEqual("opening_price" in info, True)
self.assertEqual("high_price" in info, True)
self.assertEqual("low_price" in info, True)
self.assertEqual("closing_price" in info, True)
self.assertEqual("acc_price" in info, True)
self.assertEqual("acc_volume" in info, True)

def test_ITG_get_info_return_correct_data_when_currency_is_ETH(self):
dp = BinanceDataProvider("ETH")
info = dp.get_info()
self.assertEqual(info["market"], "ETH")
self.assertEqual("date_time" in info, True)
self.assertEqual("opening_price" in info, True)
self.assertEqual("high_price" in info, True)
self.assertEqual("low_price" in info, True)
self.assertEqual("closing_price" in info, True)
self.assertEqual("acc_price" in info, True)
self.assertEqual("acc_volume" in info, True)

def test_ITG_get_info_return_correct_data_when_currency_is_DOGE(self):
dp = BinanceDataProvider("DOGE")
info = dp.get_info()
self.assertEqual(info["market"], "DOGE")
self.assertEqual("date_time" in info, True)
self.assertEqual("opening_price" in info, True)
self.assertEqual("high_price" in info, True)
self.assertEqual("low_price" in info, True)
self.assertEqual("closing_price" in info, True)
self.assertEqual("acc_price" in info, True)
self.assertEqual("acc_volume" in info, True)

def test_ITG_get_info_return_correct_data_when_currency_is_XRP(self):
dp = BinanceDataProvider("XRP")
info = dp.get_info()
self.assertEqual(info["market"], "XRP")
self.assertEqual("date_time" in info, True)
self.assertEqual("opening_price" in info, True)
self.assertEqual("high_price" in info, True)
self.assertEqual("low_price" in info, True)
self.assertEqual("closing_price" in info, True)
self.assertEqual("acc_price" in info, True)
self.assertEqual("acc_volume" in info, True)
1 change: 0 additions & 1 deletion integration_tests/upbit_data_provider_ITG_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import unittest
from smtm import UpbitDataProvider
from unittest.mock import *
import requests


class UpbitDataProviderIntegrationTests(unittest.TestCase):
Expand Down
175 changes: 175 additions & 0 deletions notebook/binance_data_provider_test.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "worse-compiler",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"os.chdir('../')"
]
},
{
"cell_type": "markdown",
"id": "armed-racing",
"metadata": {},
"source": [
"### 현재 디렉토리가 smtm 프로젝트 root로 설정되었는지 확인\n",
"분석 결과 파일이 저장될 output 폴더 생성"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "nominated-allowance",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"현재 디렉토리 c:\\01_Code\\smtm\n"
]
}
],
"source": [
"print(\"현재 디렉토리 \" , os.getcwd())"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "regular-bulletin",
"metadata": {},
"outputs": [],
"source": [
"from smtm import BinanceDataProvider"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "metallic-canal",
"metadata": {},
"outputs": [],
"source": [
"dp = BinanceDataProvider()"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "dynamic-psychiatry",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'market': 'BTC',\n",
" 'date_time': '2023-10-28T08:12:00',\n",
" 'opening_price': 33838.0,\n",
" 'high_price': 33838.0,\n",
" 'low_price': 33837.99,\n",
" 'closing_price': 33838.0,\n",
" 'acc_price': 11947.8585644,\n",
" 'acc_volume': 0.35309}"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dp.get_info()"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "senior-ancient",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'market': 'BTC',\n",
" 'date_time': '2023-10-28T08:12:00',\n",
" 'opening_price': 33838.0,\n",
" 'high_price': 33838.0,\n",
" 'low_price': 33837.99,\n",
" 'closing_price': 33838.0,\n",
" 'acc_price': 88287.4011994,\n",
" 'acc_volume': 2.60912}"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dp.get_info()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "815ebf90",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'market': 'BTC',\n",
" 'date_time': '2023-10-28T08:12:00',\n",
" 'opening_price': 33838.0,\n",
" 'high_price': 33838.0,\n",
" 'low_price': 33837.99,\n",
" 'closing_price': 33837.99,\n",
" 'acc_price': 102336.2579556,\n",
" 'acc_volume': 3.0243}"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dp.get_info()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fad6a8a4",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.10"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
1 change: 1 addition & 0 deletions smtm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from .bithumb_trader import BithumbTrader
from .upbit_data_provider import UpbitDataProvider
from .bithumb_data_provider import BithumbDataProvider
from .binance_data_provider import BinanceDataProvider
from .controller import Controller
from .jpt_controller import JptController
from .telegram_controller import TelegramController
Expand Down
117 changes: 117 additions & 0 deletions smtm/binance_data_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""바이낸스 거래소의 실시간 거래 데이터를 제공하는 DataProvider 클래스"""

from datetime import datetime, timezone, timedelta
import requests
from .date_converter import DateConverter
from .data_provider import DataProvider
from .log_manager import LogManager


class BinanceDataProvider(DataProvider):
"""
바이낸스 거래소의 실시간 거래 데이터를 제공하는 클래스
바이낸스의 open api를 사용. 별도의 가입, 인증, token 없이 사용 가능
https://binance-docs.github.io/apidocs/spot/en/#kline-candlestick-data
"""

URL = "https://api.binance.com/api/v3/klines"
AVAILABLE_CURRENCY = {"BTC": "BTCUSDT", "ETH": "ETHUSDT", "DOGE": "DOGEUSDT", "XRP": "XRPUSDT"}
KST = timezone(timedelta(hours=9))

def __init__(self, currency="BTC", interval=60):
if currency not in self.AVAILABLE_CURRENCY:
raise UserWarning(f"not supported currency: {currency}")

self.logger = LogManager.get_logger(__class__.__name__)
self.market = currency
if interval == 60:
self.interval = "1m"
elif interval == 180:
self.interval = "3m"
elif interval == 300:
self.interval = "5m"
elif interval == 600:
self.interval = "10m"
else:
raise UserWarning(f"not supported interval: {interval}")
self.query_string = {
"symbol": self.AVAILABLE_CURRENCY[currency],
"limit": 1,
"interval": self.interval,
}

def get_info(self):
"""실시간 거래 정보 전달한다
Returns: 거래 정보 딕셔너리
{
"market": 거래 시장 종류 BTC
"date_time": 정보의 기준 시간
"opening_price": 시작 거래 가격
"high_price": 최고 거래 가격
"low_price": 최저 거래 가격
"closing_price": 마지막 거래 가격
"acc_price": 단위 시간내 누적 거래 금액
"acc_volume": 단위 시간내 누적 거래 양
}
"""
data = self._get_data_from_server()
return self._create_candle_info(data[0])

def _create_candle_info(self, data):
"""
sample response:
[
[
1499040000000, // Kline open time
"0.01634790", // Open price
"0.80000000", // High price
"0.01575800", // Low price
"0.01577100", // Close price
"148976.11427815", // Volume, 거래수량
1499644799999, // Kline Close time
"2434.19055334", // Quote asset volume, 거래대금
308, // Number of trades
"1756.87402397", // Taker buy base asset volume
"28.46694368", // Taker buy quote asset volume
"0" // Unused field, ignore.
]
]
"""
try:
return {
"market": self.market,
"date_time": self._get_kst_time_from_unix_time_ms(data[0]),
"opening_price": float(data[1]),
"high_price": float(data[2]),
"low_price": float(data[3]),
"closing_price": float(data[4]),
"acc_price": float(data[7]),
"acc_volume": float(data[5]),
}
except KeyError as err:
self.logger.warning(f"invalid data for candle info: {err}")
return None

@staticmethod
def _get_kst_time_from_unix_time_ms(unix_time_ms):
"""밀리세컨드 단위의 유닉스 시간을 한국 시간으로 변환해서 반환한다"""
return DateConverter.to_iso_string(
datetime.fromtimestamp(unix_time_ms / 1000, tz=BinanceDataProvider.KST)
)

def _get_data_from_server(self):
try:
response = requests.get(self.URL, params=self.query_string)
response.raise_for_status()
return response.json()
except ValueError as error:
self.logger.error(f"Invalid data from server: {error}")
raise UserWarning("Fail get data from sever") from error
except requests.exceptions.HTTPError as error:
self.logger.error(error)
raise UserWarning("Fail get data from sever") from error
except requests.exceptions.RequestException as error:
self.logger.error(error)
raise UserWarning("Fail get data from sever") from error
Loading

0 comments on commit e8f490e

Please sign in to comment.