-
-
Notifications
You must be signed in to change notification settings - Fork 95
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
451 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.