첫 번째 커밋
This commit is contained in:
0
collectors/__init__.py
Normal file
0
collectors/__init__.py
Normal file
207
collectors/collector.py
Normal file
207
collectors/collector.py
Normal file
@@ -0,0 +1,207 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import requests, logging, json, time, sys
|
||||
from datetime import datetime
|
||||
from db import DB
|
||||
|
||||
from math import log10, floor
|
||||
|
||||
class Collector:
|
||||
_auth = {}
|
||||
_heaeder = {}
|
||||
_mathod = ''
|
||||
_name = 'target'
|
||||
_temp_api_url = ''
|
||||
_api_url = ''
|
||||
|
||||
allow_items = None
|
||||
|
||||
db = None
|
||||
|
||||
def _set_allow_items(self):
|
||||
if self.is_db_connection():
|
||||
self.allow_items = [_['item_code'] for _ in self.db.select_allow_items_all()]
|
||||
|
||||
def _get(self, url, headers=None, data=None, params=None):
|
||||
resp = requests.get(url, headers=headers, data=data, params=params)
|
||||
|
||||
if resp.status_code == 429 : # 업비트 요청 수 제한으로 인한 1분 지연
|
||||
time.sleep(60)
|
||||
return self._get(url, headers, data, params)
|
||||
|
||||
if resp.status_code not in [200, 201]:
|
||||
logging.error('get(%s) failed(%d)' % (url, resp.status_code))
|
||||
if resp.text is not None:
|
||||
logging.error('resp: %s' % resp.text)
|
||||
raise Exception('request.get() failed(%s)' % resp.text)
|
||||
raise Exception(
|
||||
'request.get() failed(status_code:%d)' % resp.status_code)
|
||||
self._update_remaining_req(resp)
|
||||
|
||||
return json.loads(resp.text)
|
||||
|
||||
def data_columns_init(self, df):
|
||||
t_col = []
|
||||
for c in df.columns:
|
||||
t_col.append(c.lower().capitalize())
|
||||
|
||||
df.columns = t_col
|
||||
|
||||
def cal_ceil(self, x, sig=4):
|
||||
return float(str(x)[:sig])
|
||||
|
||||
def packaging_data_per_time_unit(self, df, t_time):
|
||||
time_type = {
|
||||
'hour': '60min',
|
||||
'hour4':'240min',
|
||||
'hour6': '360min',
|
||||
'hour12': '720min',
|
||||
}
|
||||
ohlc_dict = {
|
||||
'Open': 'first',
|
||||
'High': 'max',
|
||||
'Low': 'min',
|
||||
'Close': 'last',
|
||||
'Volume': 'sum'
|
||||
}
|
||||
|
||||
df = df.resample(time_type[t_time], how=ohlc_dict, label='left', base=540) # UTC (트레이딩뷰)
|
||||
|
||||
# return df[:-1]
|
||||
return df
|
||||
|
||||
def _update_remaining_req(self, resp):
|
||||
if 'Remaining-Req' not in resp.headers.keys():
|
||||
return None
|
||||
keyvals = resp.headers['Remaining-Req'].split('; ')
|
||||
group = None
|
||||
keyval = dict()
|
||||
for _keyval in keyvals:
|
||||
kv = _keyval.split('=')
|
||||
if kv[0] == 'group':
|
||||
group = kv[1]
|
||||
else:
|
||||
keyval[kv[0]] = kv[1]
|
||||
if group is None:
|
||||
return
|
||||
keyval['update_time'] = datetime.now()
|
||||
self.remaining_req[group] = keyval
|
||||
|
||||
def is_db_connection(self):
|
||||
if self.db is None :
|
||||
self.db = DB()
|
||||
|
||||
return True
|
||||
|
||||
# 테이블 체크 후 없을 시 기존 데이터를 추가(거래소별 max의 개수)
|
||||
def is_table_in_db(self, finance, exchange, item, time_type = None):
|
||||
if self.is_db_connection() :
|
||||
return self.db.is_item_table_in_db(finance, exchange, item, time_type)
|
||||
|
||||
# 테이블 확인이 안될 시 없는 것으로 간주
|
||||
return False
|
||||
|
||||
def _save_to_db_from_collectors_data(self, finance = 'crypto', exchange = 'upbit', item='KRW-BTC', data = None, time_type = None):
|
||||
if self.is_db_connection() :
|
||||
if data is not None and time_type is not None :
|
||||
self.db.insertItemData(finance, exchange, item, data, time_type)
|
||||
|
||||
def _save_to_db_from_collectors_dataframe(self, finance = 'crypto', exchange = 'upbit', item='KRW-BTC', data = None, time_type = None):
|
||||
if self.is_db_connection() :
|
||||
if data is not None and time_type is not None :
|
||||
self.db.insertItemDataframe(finance, exchange, item, data, time_type)
|
||||
|
||||
|
||||
def _get_api_url(self):
|
||||
if self.is_db_connection() :
|
||||
url = self.db.select_api_url_from_tb_exchange(self._name)
|
||||
if url is () :
|
||||
return self._temp_api_url
|
||||
|
||||
return str(url['api_url'])
|
||||
|
||||
# 거래소 24시간 기준금액 업데이트
|
||||
def update_exchange_standard_options(self):
|
||||
markets = self._get_data_all_markets()
|
||||
|
||||
self._standard_price = self._get_standard_trade_price(markets)
|
||||
self.db.update_standard_options_from_exchange(self._name, self._standard_price)
|
||||
|
||||
def get_exchange_info_from_db(self):
|
||||
return self.db.select_row_data_from_exchange(self._name)
|
||||
|
||||
def _get_item_list_tb_exchange_and_item(self): # dev
|
||||
e_info = self.get_exchange_info_from_db()
|
||||
|
||||
if e_info is () :
|
||||
return None
|
||||
|
||||
return self.db.select_items_data_from_tb_items_by_exchange_id(e_info['id'])
|
||||
|
||||
# 거래소 기본정보 데이터 저장
|
||||
def _add_exchange_info_to_db(self, finance, exchange, s_price, url):
|
||||
if self.is_db_connection() :
|
||||
self.db.insert_finance_and_exchange_data(finance, exchange, s_price, url)
|
||||
|
||||
# 아이템 종목 리스트 저장(종목 행 데이터 생성)
|
||||
def _save_item_data_to_item_table(self, finance, exchange, item, total_trade_price_24h, total_trade_volume_24h):
|
||||
if self.is_db_connection() :
|
||||
self.db.insert_item_main_data(finance, exchange, item, total_trade_price_24h, total_trade_volume_24h)
|
||||
|
||||
# 거래종목 리스트 로드
|
||||
def _load_markets(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
# 거래금액으로 필터링 된 거래종목 리스트 반환
|
||||
def _return_list_filted_markets(self, markets):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
# 거래 금액 기준 반환 (달러 or 원화)
|
||||
def _get_trade_price(self, markets):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def _get_currency_type(self, item):
|
||||
'''
|
||||
0 : doller
|
||||
1 : krw
|
||||
|
||||
:param item: target item
|
||||
:return: currency_type
|
||||
'''
|
||||
|
||||
if 'KRW' in str(item).upper() :
|
||||
return 1
|
||||
else :
|
||||
return 0
|
||||
|
||||
def get_based_time_from_data(self, data):
|
||||
if 'KRW' in data['target']:
|
||||
return '9'
|
||||
|
||||
return '0'
|
||||
|
||||
# need override
|
||||
def save_current_data(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def _return_list_filted_markets(self, markets):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def save_current_min_data(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def save_current_hour_data(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def save_current_day_data(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def _set_standard_price(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def get_history_data(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
def get_last_price_from_orderbook(self):
|
||||
print('not defined function : %s' % sys._getframe().f_code.co_name)
|
||||
|
||||
0
collectors/crypto/__init__.py
Normal file
0
collectors/crypto/__init__.py
Normal file
370
collectors/crypto/c_binance.py
Normal file
370
collectors/crypto/c_binance.py
Normal file
@@ -0,0 +1,370 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
from collectors.collector import Collector
|
||||
import logging
|
||||
|
||||
# import API
|
||||
from lib.pybinancefutures import *
|
||||
|
||||
# curl
|
||||
import json
|
||||
import datetime, time
|
||||
import requests
|
||||
from pandas import DataFrame
|
||||
import pandas as pd
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from db import DB
|
||||
|
||||
# access
|
||||
# DPob3MlV51nb55D6OovjKTNRiyoiMWihX2phcunUNxI73Z7gSyo2ALX87dxcmuXB
|
||||
# secret
|
||||
# qgd5YHf4TiWvD8KjOL1qoPz9QX354mYMIoQ6FBt5VCv1tswQq3X6eGaFFrHZ7a7a
|
||||
|
||||
angel = 'power'
|
||||
|
||||
class Binance(Collector):
|
||||
def __init__(self, access=None, secret=None):
|
||||
self.remaining_req = {}
|
||||
self._heaeder = {}
|
||||
self._mathod = 'get'
|
||||
self._finance = 'crypto' # 금융 종목
|
||||
self._name = 'binance' # 거래소 이름
|
||||
self.leverage = 1
|
||||
self._standard_price = 0
|
||||
self._temp_api_url = 'https://fapi.binance.com/fapi/v1/' # 임시 api 주소
|
||||
|
||||
self._market = MarketData()
|
||||
|
||||
if secret is not None:
|
||||
self._api = Client(access, secret)
|
||||
|
||||
self._api_url = self._temp_api_url
|
||||
|
||||
self._set_allow_items()
|
||||
|
||||
# get filtered markets
|
||||
self.markets = self._load_markets() # 테스트때 주석
|
||||
|
||||
'''
|
||||
For Cron Funtions
|
||||
'''
|
||||
# 바이낸스 기준 시총 정의
|
||||
def _get_standard_trade_price(self, markets=None):
|
||||
p_list = []
|
||||
for m in self._market.ticker_price_24h():
|
||||
p = m['lastPrice']
|
||||
v = m['volume']
|
||||
|
||||
p_list.append(float(p)*float(v))
|
||||
|
||||
p_list.sort(reverse=True)
|
||||
|
||||
return float(p_list[1] - p_list[1] * 0.01)
|
||||
|
||||
def _return_list_filted_markets(self):
|
||||
filtered_list = []
|
||||
|
||||
for m in self._market.ticker_price_24h():
|
||||
s = m['symbol']
|
||||
p = m['lastPrice']
|
||||
v = m['volume']
|
||||
|
||||
f_if = False
|
||||
|
||||
# 리플 제외
|
||||
if 'XRP' in s:
|
||||
continue
|
||||
|
||||
# 허용 아이템 조건
|
||||
for i in self.allow_items:
|
||||
if i == s.replace('USDT', ''):
|
||||
f_if = True
|
||||
|
||||
# 24시간 거래금액 및 종목 가격 제한 조건
|
||||
# f_if = float(p) * float(v) > self._standard_price \
|
||||
# and float(p) > float(100)
|
||||
|
||||
if f_if:
|
||||
item = {
|
||||
'market': str(s),
|
||||
'acc_trade_price_24h': float(p) * float(v), # 거래 총 금액
|
||||
'acc_trade_volume_24h': float(v), # 거래량
|
||||
}
|
||||
filtered_list.append(item)
|
||||
|
||||
return filtered_list
|
||||
|
||||
def _save_market_list_to_db(self, markets):
|
||||
for m in markets:
|
||||
self._save_item_data_to_item_table(self._finance, # 금융
|
||||
self._name, # 거래소
|
||||
m['market'], # 종목
|
||||
m['acc_trade_price_24h'], # 24시간 거래 금액
|
||||
m['acc_trade_volume_24h'] # 24시간 거래량
|
||||
)
|
||||
|
||||
def _load_markets(self):
|
||||
# DB에서 종목이 없을 경우 아래 로직 실행
|
||||
try:
|
||||
markets = self._return_list_filted_markets()
|
||||
|
||||
# set exchange min marget cap(시총)
|
||||
self._standard_price = self._get_standard_trade_price(markets)
|
||||
|
||||
# 거래소 기본정보 DB 저장 (거래 기준가, api_url 등)
|
||||
self._add_exchange_info_to_db(self._finance, self._name, self._standard_price, self._api_url)
|
||||
|
||||
# 종목 리스트 DB 저장
|
||||
self._save_market_list_to_db(markets)
|
||||
|
||||
return markets
|
||||
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
return self._load_markets()
|
||||
# raise Exception(e) # for dev
|
||||
|
||||
def save_current_min_data(self, market):
|
||||
time_type = 'minute'
|
||||
data = self.get_history_data(market, time_type)
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def save_current_hour_data(self, market):
|
||||
time_type = 'hour'
|
||||
data = self.get_history_data(market, time_type)
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def save_current_day_data(self, market):
|
||||
time_type = 'day'
|
||||
data = self.get_history_data(market, time_type)
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def get_history_data(self, symbol="BTCUSDT", interval="hour", rm_last=True):
|
||||
int2type = {
|
||||
"day": "1d",
|
||||
"hour12": "12h",
|
||||
"hour6": "6h",
|
||||
"hour4": "4h", # custom hour
|
||||
"hour": "1h",
|
||||
"minute30": "30m",
|
||||
"minute15": "15m",
|
||||
"minute10": "15m",
|
||||
"minute5": "5m",
|
||||
"minute3": "3m",
|
||||
"minute": "1m",
|
||||
}
|
||||
# set symbol
|
||||
self._market.symbol = symbol
|
||||
|
||||
data = self._market.candles_data(int2type[interval], None, None, 1500)
|
||||
columns = ['Date', 'Open', 'High', 'Low', 'Close', 'Volume', 'Close_Time',
|
||||
'Quote_Asset_Volume', 'Number_of_Trades', 'Taker_Buy_Base_Asset_Volume',
|
||||
'Taker_Buy_Quote_Asset_Volume', 'Ignore']
|
||||
|
||||
df = DataFrame(data, columns=columns)
|
||||
self.data_columns_init(df)
|
||||
|
||||
if not df.empty:
|
||||
# Convert timestamp to date format
|
||||
dates = [datetime.datetime.fromtimestamp(int(d) / 1000).strftime("%Y-%m-%d %H:%M:%S")
|
||||
for d in df['Date'].values]
|
||||
# df.loc[:, 'Date'] = df['Date']\
|
||||
# .apply(lambda d: datetime.datetime.fromtimestamp(int(d)/1000).strftime("%Y-%m-%d %H:%M:%S"))
|
||||
|
||||
# Set needs columns
|
||||
df = df[['Open', 'High', 'Low', 'Close', 'Volume']]
|
||||
|
||||
# Remove last candle
|
||||
if rm_last:
|
||||
df = df[:-1]
|
||||
|
||||
df.loc[:, symbol] = pd.Series(dates)
|
||||
df = df.set_index(symbol)
|
||||
|
||||
# return df
|
||||
return df.astype(float)
|
||||
else:
|
||||
return None
|
||||
|
||||
'''
|
||||
For Trade Funtions
|
||||
'''
|
||||
|
||||
def get_current_price(self, symbol):
|
||||
self._market.symbol = symbol
|
||||
recent_price = self._market.ticker_price_symbol(symbol)
|
||||
|
||||
if recent_price:
|
||||
return recent_price[0]['price']
|
||||
|
||||
return None
|
||||
|
||||
def get_position_info(self):
|
||||
return self._api.position_info()
|
||||
|
||||
def get_last_price_from_orderbook(self, symbol, position, cnt=0):
|
||||
if cnt > 10:
|
||||
return None, None
|
||||
|
||||
self._market.symbol = symbol
|
||||
order_book = self._market.ticker_orderbook_symbol(symbol)
|
||||
cnt += 1
|
||||
|
||||
for b_d in order_book:
|
||||
if b_d['symbol'] in symbol:
|
||||
if position == 'long':
|
||||
return float(b_d['bidPrice'])
|
||||
elif position == 'short':
|
||||
return float(b_d['askPrice'])
|
||||
|
||||
time.sleep(1)
|
||||
return self.get_last_price_from_orderbook(symbol, position, cnt)
|
||||
|
||||
def get_trading_fee(self):
|
||||
return float(0.02)
|
||||
|
||||
# get_position_balance
|
||||
def get_position_balance(self, symbol='USDT'):
|
||||
time.sleep(1)
|
||||
|
||||
for b in self._api.position_info():
|
||||
if str(b['symbol']) == str(symbol):
|
||||
return float(abs(float(b['positionAmt'])))
|
||||
|
||||
return False
|
||||
|
||||
def get_balance(self, symbol='USDT'):
|
||||
for b in self._api.balance():
|
||||
if str(b['symbol']) == str(symbol):
|
||||
return self.cal_ceil(b['positionAmt'], 5)
|
||||
return 0
|
||||
|
||||
def get_now_amount(self, symbol):
|
||||
res = {}
|
||||
|
||||
for b in self._api.balance():
|
||||
if str(b['asset']) == 'USDT':
|
||||
res['KRW'] = '{:.8f}'.format(float(b['withdrawAvailable']))
|
||||
elif str(b['asset']) in symbol:
|
||||
res[b['asset']] = '{:.8f}'.format(float(b['withdrawAvailable']))
|
||||
|
||||
return res
|
||||
|
||||
def get_all_seeds(self):
|
||||
for b in self._api.balance():
|
||||
if b['asset'] == 'USDT':
|
||||
return '{:.8f}'.format(float(b['balance']))
|
||||
|
||||
# BinanceFuturesPy
|
||||
def order_long(self, symbol, order=None, cnt=0):
|
||||
self._api.symbol = symbol
|
||||
|
||||
# 레버리지 설정
|
||||
self._api.change_leverage(self.leverage)
|
||||
|
||||
# 이전 주문 취소
|
||||
self.order_cancel(order)
|
||||
|
||||
target_price = self.get_last_price_from_orderbook(symbol, 'long')
|
||||
seeds = float(self.get_now_amount(symbol)['KRW'])
|
||||
amount = self.get_position_balance(symbol)
|
||||
|
||||
if not amount > 0:
|
||||
amount = self.cal_ceil(seeds / target_price, 5)*int(self.leverage)
|
||||
|
||||
if not amount > 0:
|
||||
return False, None
|
||||
|
||||
if cnt > 100:
|
||||
# 시장가 매수
|
||||
order = self._api.new_order(side='BUY',
|
||||
quantity=amount,
|
||||
orderType='MARKET')
|
||||
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
else:
|
||||
# 지정가 매수
|
||||
order = self._api.new_order(side='BUY',
|
||||
quantity=amount,
|
||||
price=target_price,
|
||||
orderType='LIMIT',
|
||||
timeInForce='GTC')
|
||||
|
||||
if order is None or 'msg' in order:
|
||||
print('주문 오류 -', order['msg'])
|
||||
return False, None
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
ordered = self._api.query_order(order['orderId'])
|
||||
|
||||
if ordered['origQty'] != ordered['executedQty']:
|
||||
cnt += 1
|
||||
|
||||
return self.order_long(symbol, order, cnt)
|
||||
|
||||
seeds = float(self.get_now_amount(symbol)['KRW'])
|
||||
# seeds = self.get_now_amount(symbol)['KRW']
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
|
||||
def order_short(self, symbol, order=None, cnt=0):
|
||||
# for test
|
||||
# return True, {'symbol': symbol, 'target_price': 8952000, 'amount': 1, 'seeds': 22000}
|
||||
|
||||
self._api.symbol = symbol
|
||||
|
||||
# 레버리지 설정
|
||||
self._api.change_leverage(self.leverage)
|
||||
|
||||
# 이전 주문 취소
|
||||
self.order_cancel(order)
|
||||
|
||||
target_price = self.get_last_price_from_orderbook(symbol, 'short')
|
||||
seeds = float(self.get_now_amount(symbol)['KRW'])
|
||||
amount = self.get_position_balance(symbol)
|
||||
|
||||
if not amount > 0:
|
||||
amount = self.cal_ceil(seeds / target_price, 5)*int(self.leverage)
|
||||
|
||||
if not amount > 0:
|
||||
return False, None
|
||||
|
||||
if cnt > 100:
|
||||
# 시장가 매도
|
||||
self._api.new_order(side='SELL',
|
||||
quantity=amount,
|
||||
orderType='MARKET')
|
||||
|
||||
seeds = self.get_now_amount(symbol)['KRW']
|
||||
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
else:
|
||||
order = self._api.new_order(side='SELL',
|
||||
quantity=amount,
|
||||
price=target_price,
|
||||
orderType='LIMIT',
|
||||
timeInForce='GTC')
|
||||
|
||||
if order is None or 'msg' in order:
|
||||
print('주문 오류 -', order['msg'])
|
||||
return False, None
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
ordered = self._api.query_order(order['orderId'])
|
||||
|
||||
if ordered['origQty'] != ordered['executedQty']:
|
||||
cnt += 1
|
||||
return self.order_short(symbol, order, cnt)
|
||||
|
||||
seeds = float(self.get_now_amount(symbol)['KRW'])
|
||||
# seeds = self.get_now_amount(symbol)['KRW']
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
|
||||
def order_cancel(self, order=None):
|
||||
if order is not None:
|
||||
return self._api.cancel_order(order['orderId'])
|
||||
|
||||
return False
|
||||
343
collectors/crypto/c_bithumb.py
Normal file
343
collectors/crypto/c_bithumb.py
Normal file
@@ -0,0 +1,343 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
from collectors.collector import Collector
|
||||
import logging
|
||||
|
||||
# import API
|
||||
import pybithumb
|
||||
|
||||
# curl
|
||||
import json
|
||||
import datetime, time
|
||||
import requests
|
||||
from pandas import DataFrame
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from db import DB
|
||||
|
||||
|
||||
class Bithumb(Collector):
|
||||
|
||||
def __init__(self, access=None, secret=None):
|
||||
self.remaining_req = {}
|
||||
self._heaeder = {}
|
||||
self._mathod = 'get'
|
||||
self._finance = 'crypto' # 금융 종목
|
||||
self._name = 'bithumb' # 거래소 이름
|
||||
self.leverage = 1
|
||||
self._standard_price = 0
|
||||
self._temp_api_url = 'https://api.bithumb.com/' # 임시 api 주소
|
||||
|
||||
# self._api = pybithumb
|
||||
|
||||
if secret is None:
|
||||
self._api = pybithumb.Bithumb
|
||||
else:
|
||||
self._api = pybithumb.Bithumb(access, secret)
|
||||
|
||||
self._api_url = self._temp_api_url
|
||||
|
||||
self._set_allow_items()
|
||||
|
||||
# get filtered markets
|
||||
self.markets = self._load_markets() # 테스트때 주석
|
||||
|
||||
# 빗썸 거래 기준금액 정의
|
||||
def _get_standard_trade_price(self, markets=None):
|
||||
p_list = []
|
||||
for m in markets:
|
||||
res = pybithumb.get_market_detail(m)
|
||||
# 종목 가격 제한 조건 추가 2019-11-12 => 잡코 방지
|
||||
|
||||
p_list.append(float(res[3])*float(res[4]))
|
||||
|
||||
# if res['status'] == '0000':
|
||||
# p_list.append(float(res['data']['acc_trade_value_24H']))
|
||||
|
||||
p_list.sort(reverse=True)
|
||||
|
||||
return float(p_list[0] - p_list[0] * 0.01)
|
||||
# return float(p_list[1] - p_list[1] * 0.01)
|
||||
|
||||
def _return_list_filted_markets(self, markets):
|
||||
filtered_list = []
|
||||
|
||||
for m in markets:
|
||||
f_if = False
|
||||
|
||||
# 리플 제외
|
||||
if 'XRP' in m:
|
||||
continue
|
||||
|
||||
res = pybithumb.get_market_detail(m)
|
||||
|
||||
# 허용 아이템 조건
|
||||
for i in self.allow_items:
|
||||
if i == m.replace('KRW-', ''):
|
||||
f_if = True
|
||||
|
||||
# 24시간 거래금액 및 종목 가격 제한 조건
|
||||
# f_if = float(res[3]) * float(res[4]) > self._standard_price \
|
||||
# and float(res[3]) > float(100)
|
||||
|
||||
if f_if:
|
||||
item = {
|
||||
'market': 'KRW-' + str(m),
|
||||
'acc_trade_price_24h': float(res[3]) * float(res[4]), # 거래 총 금액
|
||||
'acc_trade_volume_24h': float(res[4]), # 거래량
|
||||
}
|
||||
filtered_list.append(item)
|
||||
|
||||
return filtered_list
|
||||
|
||||
def _save_market_list_to_db(self, markets):
|
||||
for m in markets:
|
||||
self._save_item_data_to_item_table(self._finance, # 금융
|
||||
self._name, # 거래소
|
||||
m['market'], # 종목
|
||||
m['acc_trade_price_24h'], # 24시간 거래 금액
|
||||
m['acc_trade_volume_24h'] # 24시간 거래량
|
||||
)
|
||||
|
||||
def _load_markets(self):
|
||||
# DB에서 종목이 없을 경우 아래 로직 실행
|
||||
try:
|
||||
markets = self._api.get_tickers()
|
||||
|
||||
# set exchange min volume
|
||||
self._standard_price = self._get_standard_trade_price(markets)
|
||||
|
||||
filtered_markets = self._return_list_filted_markets(markets)
|
||||
|
||||
# 거래소 기본정보 DB 저장 (거래 기준가, api_url 등)
|
||||
self._add_exchange_info_to_db(self._finance, self._name, self._standard_price, self._api_url)
|
||||
|
||||
# 종목 리스트 DB 저장
|
||||
self._save_market_list_to_db(filtered_markets)
|
||||
|
||||
return filtered_markets
|
||||
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
raise Exception(e) # for dev
|
||||
|
||||
return markets
|
||||
|
||||
def get_symbol_for_chart_view(self, market):
|
||||
s = market.split('-')
|
||||
return s[1] + '_' + s[0]
|
||||
|
||||
def save_current_min_data(self, market):
|
||||
time_type = 'min'
|
||||
data = self.get_history_data(self.get_symbol_for_chart_view(market), 'minute')[:-1]
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def save_current_hour_data(self, market):
|
||||
time_type = 'hour'
|
||||
data = self.get_history_data(self.get_symbol_for_chart_view(market), 'hour')[:-1]
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def save_current_day_data(self, market):
|
||||
time_type = 'day'
|
||||
data = self.get_history_data(self.get_symbol_for_chart_view(market), 'day')[:-1]
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def _swap_item_name(self, symbol):
|
||||
symbol = str(symbol).replace('-', '_')
|
||||
|
||||
if 'KRW' in symbol.split('_')[0]:
|
||||
s = symbol.split('_')
|
||||
r_symbol = '_'.join([s[1], s[0]])
|
||||
|
||||
return r_symbol
|
||||
|
||||
return symbol
|
||||
|
||||
def get_history_data(self, symbol="BTC_KRW", interval="day", rm_last=True):
|
||||
symbol = self._swap_item_name(symbol)
|
||||
|
||||
int2type = {
|
||||
# "hour12": "12H",
|
||||
# "hour6": "06H",
|
||||
# "hour4": "01H", # custom hour
|
||||
# "hour": "01H",
|
||||
|
||||
"day": "24H",
|
||||
"hour12": "01H",
|
||||
"hour6": "01H",
|
||||
"hour4": "01H", # custom hour
|
||||
"hour": "01H",
|
||||
"minute30": "30M",
|
||||
"minute10": "10M",
|
||||
"minute5": "05M",
|
||||
"minute3": "03M",
|
||||
"minute": "01M",
|
||||
}
|
||||
|
||||
url = "https://m.bithumb.com/trade/chart/{}".format(symbol)
|
||||
resp = requests.get(url)
|
||||
html = resp.text
|
||||
|
||||
# parsing coin type
|
||||
string = html.split("COIN = ")[1].split(";")[0]
|
||||
coin = json.loads(string)
|
||||
tk2ct = {v['symbol_name']: k for k, v in coin['C0100'].items()}
|
||||
|
||||
# parsing xcoin name
|
||||
selector = "#barcodeForm > input[name=csrf_xcoin_name]"
|
||||
soup = BeautifulSoup(html, 'html5lib')
|
||||
xcoin_name = soup.select(selector)[0]['value']
|
||||
|
||||
url = "https://m.bithumb.com/trade_history/chart_data"
|
||||
headers = {
|
||||
"cookie": 'csrf_xcoin_name={}'.format(xcoin_name),
|
||||
"x-requested-with": "XMLHttpRequest"
|
||||
}
|
||||
|
||||
symbol = symbol.replace("KRW", "")
|
||||
symbol = symbol.replace("_", "")
|
||||
|
||||
data = {
|
||||
"coinType": tk2ct[symbol],
|
||||
"crncCd": "C0100",
|
||||
"tickType": int2type[interval],
|
||||
"csrf_xcoin_name": xcoin_name
|
||||
}
|
||||
|
||||
resp = requests.post(url, data=data, headers=headers).json()
|
||||
for x in resp['data']:
|
||||
x[0] = datetime.datetime.fromtimestamp(x[0] / 1000)
|
||||
|
||||
columns = [symbol, 'open', 'close', 'high', 'low', 'volume']
|
||||
df = DataFrame(resp['data'], columns=columns)
|
||||
df = df.set_index(symbol)
|
||||
|
||||
if not df.empty:
|
||||
# Remove last candle
|
||||
if rm_last:
|
||||
df = df[:-1]
|
||||
|
||||
self.data_columns_init(df)
|
||||
|
||||
if interval == 'hour4' or interval == 'hour6' or interval == 'hour12':
|
||||
return self.packaging_data_per_time_unit(df, interval)
|
||||
|
||||
return df.astype(float)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_current_price(self, symbol):
|
||||
symbol = self._swap_item_name(symbol)
|
||||
return self._api.get_current_price(symbol)
|
||||
|
||||
def get_last_price_from_orderbook(self, symbol, position):
|
||||
symbol = self._swap_item_name(symbol)
|
||||
res = self._api.get_orderbook(symbol)
|
||||
|
||||
if res:
|
||||
if position == 'long':
|
||||
return int(res['bids'][0]['price'])
|
||||
|
||||
elif position == 'short':
|
||||
return int(res['asks'][0]['price'])
|
||||
|
||||
return None, None
|
||||
|
||||
def get_trading_fee(self):
|
||||
return self._api.get_trading_fee()
|
||||
|
||||
def get_all_balance(self, symbol='ALL'):
|
||||
return self._api.get_balance(symbol)
|
||||
|
||||
def get_now_amount(self, symbol):
|
||||
s = symbol.replace('KRW', '').replace('-', '')
|
||||
res = {}
|
||||
|
||||
c, u, k, u = self.get_all_balance(s)
|
||||
|
||||
res[symbol] = '{:.8f}'.format(c)
|
||||
res['KRW'] = int(k)-int(u)
|
||||
|
||||
return res
|
||||
|
||||
# https://github.com/sharebook-kr/pybithumb
|
||||
# https://wikidocs.net/21887
|
||||
def order_long(self, symbol, order=None, cnt=0):
|
||||
# for test
|
||||
# return True, {'symbol': symbol, 'target_price': 8723000, 'amount': 1, 'seeds': 20000}
|
||||
|
||||
# 이전 주문 취소
|
||||
self.order_cancel(order)
|
||||
|
||||
target_price = self.get_last_price_from_orderbook(symbol, 'long')
|
||||
s = symbol.replace('KRW', '').replace('-', '')
|
||||
seeds = self.get_now_amount(symbol)['KRW']
|
||||
amount = float('{:.4f}'.format(seeds/target_price, 4))
|
||||
|
||||
if cnt > 100:
|
||||
# 시장가 매수
|
||||
self._api.buy_market_order(s, amount)
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
else:
|
||||
# 지정가 매수
|
||||
order = self._api.buy_limit_order(s, target_price, amount)
|
||||
|
||||
if order is None or 'message' in order:
|
||||
print('주문 오류 -', order['message'])
|
||||
return False
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
if self._api.get_outstanding_order(order) is not None:
|
||||
cnt += 1
|
||||
return self.order_long(symbol, order, cnt)
|
||||
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
|
||||
def order_short(self, symbol, order=None, cnt=0):
|
||||
# for test
|
||||
# return True, {'symbol': symbol, 'target_price': 8952000, 'amount': 1, 'seeds': 22000}
|
||||
|
||||
# 이전 주문 취소
|
||||
self.order_cancel(order)
|
||||
|
||||
target_price = self.get_last_price_from_orderbook(symbol, 'short')
|
||||
s = symbol.replace('KRW', '').replace('-', '')
|
||||
amount = float(format(self._api.get_balance(s)[0], ".4f"))
|
||||
|
||||
if not amount > 0:
|
||||
return True
|
||||
|
||||
if cnt > 100:
|
||||
# 시장가 매도
|
||||
self._api.sell_market_order(s, amount)
|
||||
seeds = self.get_now_amount(symbol)['KRW']
|
||||
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
else:
|
||||
# 지정가 매도
|
||||
order = self._api.sell_limit_order(s, target_price, amount)
|
||||
|
||||
if order is None or 'message' in order:
|
||||
print('주문 오류 -', order['message'])
|
||||
return False
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
if self._api.get_outstanding_order(order) is not None:
|
||||
cnt += 1
|
||||
return self.order_short(symbol, order, cnt)
|
||||
|
||||
seeds = self.get_now_amount(symbol)['KRW']
|
||||
return True, {'symbol': symbol, 'target_price': target_price, 'amount': amount, 'seeds': seeds}
|
||||
|
||||
def order_cancel(self, order=None):
|
||||
if order is not None:
|
||||
return self._api.cancel_order(order)
|
||||
|
||||
return False
|
||||
|
||||
# 포지션 종료 => 매도
|
||||
def close_position(self, symbol):
|
||||
return self.order_short(symbol)
|
||||
169
collectors/crypto/c_upbit.py
Normal file
169
collectors/crypto/c_upbit.py
Normal file
@@ -0,0 +1,169 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from collectors.collector import Collector
|
||||
|
||||
# Logging
|
||||
import logging
|
||||
|
||||
# Exchage API
|
||||
import pyupbit
|
||||
|
||||
import sys
|
||||
|
||||
# 거래금액 기준은 거래소 아이템 중 상위 3개에서 3번째의 종목 최소 거래금액 기준으로 정한다. # 종목 추가 기능을 추가한다.
|
||||
# 상위 3개 종목 기본 거래종목 후보로 추가(데이터 수집) => 조건 만족 시 거래
|
||||
# 1. 거래량
|
||||
# 2. 변동폭
|
||||
# 3. 수익성(시뮬레이터 결과 기반) : 머신러닝, 인디케이터 등 포함된 결과
|
||||
|
||||
|
||||
# 거래 종목에서 제외 => 거래량, 변동폭, 수익성
|
||||
# 거래 후보 종목에서 제외 => 거래량
|
||||
|
||||
# 거래량 기준 => 거래소 추가 시 상위 3개 종목 중 3번째 데이터 기준 => 사용자가 변경 가능 및 종목 추가 가능
|
||||
class Upbit(Collector):
|
||||
|
||||
def __init__(self):
|
||||
self._auth = {
|
||||
'access': 'IdO6IXpgC07 XB2KWoID37jxvFXgpLMkxZRTxHViI',
|
||||
'secret': 'QteNfsNZly1kZ1t3MGAc9bOxMDiIouozuQCZVVJI',
|
||||
}
|
||||
self.remaining_req = {}
|
||||
self._heaeder = {}
|
||||
self._mathod = 'get'
|
||||
self._finance = 'crypto' # 금융 종목
|
||||
self._name = 'upbit' # 거래소 이름
|
||||
self.leverage = 1
|
||||
self._standard_price = 0
|
||||
self._temp_api_url = 'https://api.upbit.com/v1/' # 임시 api 주소
|
||||
|
||||
# get api url from db
|
||||
self._api_url = self._get_api_url()
|
||||
self._api = pyupbit
|
||||
self._secret_api = pyupbit.Upbit(self._auth['access'], self._auth['secret'])
|
||||
|
||||
self._set_allow_items()
|
||||
|
||||
# get filtered markets
|
||||
self.markets = self._load_markets() # 테스트때 주석
|
||||
|
||||
|
||||
def set_key_data(self, access, secret):
|
||||
self._secret_api = pyupbit.Upbit(access, secret)
|
||||
|
||||
# 업비트 거래 기준금액 정의
|
||||
def _get_standard_trade_price(self, markets = None):
|
||||
if markets == None:
|
||||
markets = self._get_data_all_markets()
|
||||
|
||||
p_list = []
|
||||
for m in markets :
|
||||
c_type = self._get_currency_type(m['market'])
|
||||
|
||||
# 원화 거래
|
||||
if c_type is 1:
|
||||
p_list.append(int(m['acc_trade_price_24h']))
|
||||
|
||||
p_list.sort(reverse=True)
|
||||
|
||||
return int(p_list[0] - p_list[0] * 0.01)
|
||||
# return int(p_list[2] - p_list[2] * 0.01)
|
||||
|
||||
def _get_data_all_markets(self):
|
||||
market_all = self._get_market_all()
|
||||
|
||||
if market_all is None:
|
||||
return
|
||||
|
||||
markets = []
|
||||
for market in market_all:
|
||||
markets.append(market['market'])
|
||||
|
||||
URL = self._api_url + 'ticker?markets=%s' % str(','.join(markets))
|
||||
|
||||
return self._get(URL)
|
||||
|
||||
def _return_list_filted_markets(self, markets):
|
||||
filtered_list = []
|
||||
|
||||
for m in markets:
|
||||
c_type = self._get_currency_type(m['market'])
|
||||
f_if = False
|
||||
|
||||
# except ripple
|
||||
if 'XRP' in m['market']:
|
||||
continue
|
||||
|
||||
# 허용 아이템 조건
|
||||
for i in self.allow_items:
|
||||
if i == m['market'].replace('KRW-', ''):
|
||||
f_if = True
|
||||
|
||||
# 24시간 거래금액 및 종목 가격 제한 조건
|
||||
# f_if = int(m['acc_trade_price_24h']) > self._standard_price \
|
||||
# and float(m['low_price']) > float(100)
|
||||
|
||||
# 원화 거래 항목으로 제한
|
||||
if c_type is 1 and f_if:
|
||||
filtered_list.append(m)
|
||||
|
||||
return filtered_list
|
||||
|
||||
def _save_market_list_to_db(self, markets):
|
||||
for m in markets :
|
||||
self._save_item_data_to_item_table(self._finance, # 금융
|
||||
self._name, # 거래소
|
||||
m['market'], # 종목
|
||||
m['acc_trade_price_24h'], # 24시간 거래 금액
|
||||
m['acc_trade_volume_24h'] # 24시간 거래량
|
||||
)
|
||||
|
||||
def _load_markets(self):
|
||||
# DB에서 종목이 없을 경우 아래 로직 실행
|
||||
try:
|
||||
markets = self._get_data_all_markets()
|
||||
|
||||
# set exchange min price
|
||||
self._standard_price = self._get_standard_trade_price(markets)
|
||||
|
||||
filtered_markets = self._return_list_filted_markets(markets)
|
||||
|
||||
# 거래소 기본정보 DB 저장 (거래 기준가, api_url 등)
|
||||
self._add_exchange_info_to_db(self._finance, self._name, self._standard_price, self._api_url)
|
||||
|
||||
# 종목 리스트 DB 저장
|
||||
self._save_market_list_to_db(filtered_markets)
|
||||
|
||||
return filtered_markets
|
||||
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
raise Exception(e) # for dev
|
||||
|
||||
return markets
|
||||
|
||||
def _get_market_all(self):
|
||||
'''
|
||||
마켓 코드 조회
|
||||
업비트에서 거래 가능한 마켓 목록
|
||||
https://docs.upbit.com/v1.0/reference#%EB%A7%88%EC%BC%93-%EC%BD%94%EB%93%9C-%EC%A1%B0%ED%9A%8C
|
||||
:return: json array
|
||||
'''
|
||||
URL = self._api_url + 'market/all'
|
||||
|
||||
return self._get(URL)
|
||||
|
||||
def save_current_min_data(self, market):
|
||||
time_type = 'min'
|
||||
data = self._api.get_ohlcv(market, interval="minute1")[:-1]
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def save_current_hour_data(self, market):
|
||||
time_type = 'hour'
|
||||
data = self._api.get_ohlcv(market, interval="minute60")[:-1]
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
|
||||
def save_current_day_data(self, market):
|
||||
time_type = 'day'
|
||||
data = self._api.get_ohlcv(market, interval="day")[:-1]
|
||||
self._save_to_db_from_collectors_dataframe(self.finance, self._name, market, data, time_type)
|
||||
5
collectors/crypto/desktop.ini
Normal file
5
collectors/crypto/desktop.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[.ShellClassInfo]
|
||||
InfoTip=<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>¶<EFBFBD><C2B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>˴ϴ<CBB4>.
|
||||
IconFile=C:\Program Files\Google\Drive\googledrivesync.exe
|
||||
IconIndex=16
|
||||
|
||||
5
collectors/desktop.ini
Normal file
5
collectors/desktop.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[.ShellClassInfo]
|
||||
InfoTip=<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>¶<EFBFBD><C2B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>˴ϴ<CBB4>.
|
||||
IconFile=C:\Program Files\Google\Drive\googledrivesync.exe
|
||||
IconIndex=16
|
||||
|
||||
0
collectors/libs/__init__.py
Normal file
0
collectors/libs/__init__.py
Normal file
5
collectors/libs/desktop.ini
Normal file
5
collectors/libs/desktop.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[.ShellClassInfo]
|
||||
InfoTip=<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>¶<EFBFBD><C2B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>˴ϴ<CBB4>.
|
||||
IconFile=C:\Program Files\Google\Drive\googledrivesync.exe
|
||||
IconIndex=16
|
||||
|
||||
594
collectors/libs/upbitpy.py
Normal file
594
collectors/libs/upbitpy.py
Normal file
@@ -0,0 +1,594 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
import jwt
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from urllib.parse import urlencode
|
||||
|
||||
|
||||
class Upbitpy():
|
||||
"""
|
||||
Upbit API
|
||||
https://docs.upbit.com/v1.0/reference
|
||||
"""
|
||||
|
||||
def __init__(self, access_key=None, secret=None):
|
||||
'''
|
||||
Constructor
|
||||
access_key, secret이 없으면 인증가능 요청(EXCHANGE API)은 사용할 수 없음
|
||||
:param str access_key: 발급 받은 acccess key
|
||||
:param str secret: 발급 받은 secret
|
||||
'''
|
||||
self.access_key = access_key
|
||||
self.secret = secret
|
||||
self.remaining_req = dict()
|
||||
self.markets = self._load_markets()
|
||||
|
||||
###############################################################
|
||||
# EXCHANGE API
|
||||
###############################################################
|
||||
|
||||
def get_accounts(self):
|
||||
'''
|
||||
전체 계좌 조회
|
||||
내가 보유한 자산 리스트를 보여줍니다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%9E%90%EC%82%B0-%EC%A0%84%EC%B2%B4-%EC%A1%B0%ED%9A%8C
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/accounts'
|
||||
return self._get(URL, self._get_headers())
|
||||
|
||||
def get_chance(self, market):
|
||||
'''
|
||||
주문 가능 정보
|
||||
마켓별 주문 가능 정보를 확인한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%A3%BC%EB%AC%B8-%EA%B0%80%EB%8A%A5-%EC%A0%95%EB%B3%B4
|
||||
:param str market: Market ID
|
||||
:return: json object
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/orders/chance'
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
data = {'market': market}
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
|
||||
def get_order(self, uuid):
|
||||
'''
|
||||
개별 주문 조회
|
||||
주문 UUID 를 통해 개별 주문건을 조회한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EA%B0%9C%EB%B3%84-%EC%A3%BC%EB%AC%B8-%EC%A1%B0%ED%9A%8C
|
||||
:param str uuid: 주문 UUID
|
||||
:return: json object
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/order'
|
||||
try:
|
||||
data = {'uuid': uuid}
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
raise Exception(e)
|
||||
|
||||
def get_orders(self, market, state, page=1, order_by='asc'):
|
||||
'''
|
||||
주문 리스트 조회
|
||||
주문 리스트를 조회한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%A3%BC%EB%AC%B8-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%A1%B0%ED%9A%8C
|
||||
:param str market: Market ID
|
||||
:param str state: 주문 상태
|
||||
wait:체결 대기(default)
|
||||
done: 체결 완료
|
||||
cancel: 주문 취소
|
||||
:param int page: 페이지 수, default: 1
|
||||
:param str order_by: 정렬 방식
|
||||
asc: 오름차순(default)
|
||||
desc:내림차순
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/orders'
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
|
||||
if state not in ['wait', 'done', 'cancel']:
|
||||
logging.error('invalid state: %s' % state)
|
||||
raise Exception('invalid state: %s' % state)
|
||||
|
||||
if order_by not in ['asc', 'desc']:
|
||||
logging.error('invalid order_by: %s' % order_by)
|
||||
raise Exception('invalid order_by: %s' % order_by)
|
||||
|
||||
data = {
|
||||
'market': market,
|
||||
'state': state,
|
||||
'page': page,
|
||||
'order_by': order_by
|
||||
}
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
|
||||
def order(self, market, side, volume, price):
|
||||
'''
|
||||
주문하기
|
||||
주문 요청을 한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%A3%BC%EB%AC%B8%ED%95%98%EA%B8%B0-1
|
||||
:param str market: 마켓 ID (필수)
|
||||
:param str side: 주문 종류 (필수)
|
||||
bid : 매수
|
||||
ask : 매도
|
||||
:param str volume: 주문량 (필수)
|
||||
:param str price: 유닛당 주문 가격. (필수)
|
||||
ex) KRW-BTC 마켓에서 1BTC당 1,000 KRW로 거래할 경우, 값은 1000 이 된다.
|
||||
:return: json object
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/orders'
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
|
||||
if side not in ['bid', 'ask']:
|
||||
logging.error('invalid side: %s' % side)
|
||||
raise Exception('invalid side: %s' % side)
|
||||
|
||||
if market.startswith('KRW') and not self._is_valid_price(price):
|
||||
logging.error('invalid price: %.2f' % price)
|
||||
raise Exception('invalid price: %.2f' % price)
|
||||
|
||||
data = {
|
||||
'market': market,
|
||||
'side': side,
|
||||
'volume': str(volume),
|
||||
'price': str(price),
|
||||
'ord_type': 'limit'
|
||||
}
|
||||
return self._post(URL, self._get_headers(data), data)
|
||||
|
||||
def cancel_order(self, uuid):
|
||||
'''
|
||||
주문 취소
|
||||
주문 UUID를 통해 해당 주문을 취소한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%A3%BC%EB%AC%B8-%EC%B7%A8%EC%86%8C
|
||||
:param str uuid: 주문 UUID
|
||||
:return: json object
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/order'
|
||||
data = {'uuid': uuid}
|
||||
return self._delete(URL, self._get_headers(data), data)
|
||||
|
||||
def get_withraws(self, currency, state, limit):
|
||||
'''
|
||||
출금 리스트 조회
|
||||
https://docs.upbit.com/v1.0/reference#%EC%A0%84%EC%B2%B4-%EC%B6%9C%EA%B8%88-%EC%A1%B0%ED%9A%8C
|
||||
:param str currency: Currency 코드
|
||||
:param str state: 출금 상태
|
||||
submitting : 처리 중
|
||||
submitted : 처리 완료
|
||||
almost_accepted : 출금대기중
|
||||
rejected : 거부
|
||||
accepted : 승인됨
|
||||
processing : 처리 중
|
||||
done : 완료
|
||||
canceled : 취소됨
|
||||
:param int limit: 갯수 제한
|
||||
:return: json array
|
||||
'''
|
||||
LIMIT_MAX = 100
|
||||
VALID_STATE = ['submitting', 'submitted', 'almost_accepted',
|
||||
'rejected', 'accepted', 'processing', 'done', 'canceled']
|
||||
URL = 'https://api.upbit.com/v1/withdraws'
|
||||
data = {}
|
||||
if currency is not None:
|
||||
data['currency'] = currency
|
||||
if state is not None:
|
||||
if state not in VALID_STATE:
|
||||
logging.error('invalid state(%s)' % state)
|
||||
raise Exception('invalid state(%s)' % state)
|
||||
data['state'] = state
|
||||
if limit is not None:
|
||||
if limit <= 0 or limit > LIMIT_MAX:
|
||||
logging.error('invalid limit(%d)' % limit)
|
||||
raise Exception('invalid limit(%d)' % limit)
|
||||
data['limit'] = limit
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
|
||||
def get_withraw(self, uuid):
|
||||
'''
|
||||
개별 출금 조회
|
||||
출금 UUID를 통해 개별 출금 정보를 조회한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EA%B0%9C%EB%B3%84-%EC%B6%9C%EA%B8%88-%EC%A1%B0%ED%9A%8C
|
||||
:param str uuid: 출금 UUID
|
||||
:return: json object
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/withdraw'
|
||||
data = {'uuid': uuid}
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
|
||||
def get_withraws_chance(self, currency):
|
||||
'''
|
||||
출금 가능 정보
|
||||
해당 통화의 가능한 출금 정보를 확인한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%B6%9C%EA%B8%88-%EA%B0%80%EB%8A%A5-%EC%A0%95%EB%B3%B4
|
||||
:param str currency: Currency symbol
|
||||
:return: json object
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/withdraws/chance'
|
||||
data = {'currency': currency}
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
|
||||
def withdraws_coin(self, currency, amount, address, secondary_address=None):
|
||||
'''
|
||||
코인 출금하기
|
||||
코인 출금을 요청한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%BD%94%EC%9D%B8-%EC%B6%9C%EA%B8%88%ED%95%98%EA%B8%B0
|
||||
:param str currency: Currency symbol
|
||||
:param str amount: 출금 코인 수량
|
||||
:param str address: 출금 지갑 주소
|
||||
:param str secondary_address: 2차 출금 주소 (필요한 코인에 한해서)
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/withdraws/coin'
|
||||
data = {
|
||||
'currency': currency,
|
||||
'amount': amount,
|
||||
'address': address
|
||||
}
|
||||
if secondary_address is not None:
|
||||
data['secondary_address'] = secondary_address
|
||||
return self._post(URL, self._get_headers(data), data)
|
||||
|
||||
def withdraws_krw(self, amount):
|
||||
'''
|
||||
원화 출금하기
|
||||
원화 출금을 요청한다. 등록된 출금 계좌로 출금된다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%9B%90%ED%99%94-%EC%B6%9C%EA%B8%88%ED%95%98%EA%B8%B0
|
||||
:param str amount: 출금 원화 수량
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/withdraws/krw'
|
||||
data = {'amount': amount}
|
||||
return self._post(URL, self._get_headers(data), data)
|
||||
|
||||
def get_deposits(self, currency=None, limit=None, page=None, order_by=None):
|
||||
'''
|
||||
입금 리스트 조회
|
||||
https://docs.upbit.com/v1.0/reference#%EC%9E%85%EA%B8%88-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%A1%B0%ED%9A%8C
|
||||
:param str currency: Currency 코드
|
||||
:param int limit: 페이지당 개수
|
||||
:param int page: 페이지 번호
|
||||
:param str order_by: 정렬 방식
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/deposits'
|
||||
data = {}
|
||||
if currency is not None:
|
||||
data['currency'] = currency
|
||||
if limit is not None:
|
||||
data['limit'] = limit
|
||||
if page is not None:
|
||||
data['page'] = page
|
||||
if order_by is not None:
|
||||
data['order_by'] = order_by
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
|
||||
def get_deposit(self, uuid):
|
||||
'''
|
||||
개별 입금 조회
|
||||
https://docs.upbit.com/v1.0/reference#%EA%B0%9C%EB%B3%84-%EC%9E%85%EA%B8%88-%EC%A1%B0%ED%9A%8C
|
||||
:param str uuid: 개별 입금의 UUID
|
||||
:return: json object
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/deposit'
|
||||
data = {'uuid': uuid}
|
||||
return self._get(URL, self._get_headers(data), data)
|
||||
|
||||
###############################################################
|
||||
# QUOTATION API
|
||||
###############################################################
|
||||
|
||||
def get_market_all(self):
|
||||
'''
|
||||
마켓 코드 조회
|
||||
업비트에서 거래 가능한 마켓 목록
|
||||
https://docs.upbit.com/v1.0/reference#%EB%A7%88%EC%BC%93-%EC%BD%94%EB%93%9C-%EC%A1%B0%ED%9A%8C
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/market/all'
|
||||
return self._get(URL)
|
||||
|
||||
def get_minutes_candles(self, unit, market, to=None, count=None):
|
||||
'''
|
||||
분(Minute) 캔들
|
||||
https://docs.upbit.com/v1.0/reference#%EB%B6%84minute-%EC%BA%94%EB%93%A4-1
|
||||
:param int unit: 분 단위. 가능한 값 : 1, 3, 5, 15, 10, 30, 60, 240
|
||||
:param str market: 마켓 코드 (ex. KRW-BTC, BTC-BCC)
|
||||
:param str to: 마지막 캔들 시각 (exclusive). 포맷 : yyyy-MM-dd'T'HH:mm:ssXXX. 비워서 요청시 가장 최근 캔들
|
||||
:param int count: 캔들 개수(최대 200개까지 요청 가능)
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/candles/minutes/%s' % str(unit)
|
||||
if unit not in [1, 3, 5, 10, 15, 30, 60, 240]:
|
||||
logging.error('invalid unit: %s' % str(unit))
|
||||
raise Exception('invalid unit: %s' % str(unit))
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
|
||||
params = {'market': market}
|
||||
if to is not None:
|
||||
params['to'] = to
|
||||
if count is not None:
|
||||
params['count'] = count
|
||||
return self._get(URL, params=params)
|
||||
|
||||
def get_days_candles(self, market, to=None, count=None):
|
||||
'''
|
||||
일(Day) 캔들
|
||||
https://docs.upbit.com/v1.0/reference#%EC%9D%BCday-%EC%BA%94%EB%93%A4-1
|
||||
:param str market: 마켓 코드 (ex. KRW-BTC, BTC-BCC)
|
||||
:param str to: 마지막 캔들 시각 (exclusive). 포맷 : yyyy-MM-dd'T'HH:mm:ssXXX. 비워서 요청시 가장 최근 캔들
|
||||
:param int count: 캔들 개수
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/candles/days'
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
|
||||
params = {'market': market}
|
||||
if to is not None:
|
||||
params['to'] = to
|
||||
if count is not None:
|
||||
params['count'] = count
|
||||
return self._get(URL, params=params)
|
||||
|
||||
def get_weeks_candles(self, market, to=None, count=None):
|
||||
'''
|
||||
주(Week) 캔들
|
||||
https://docs.upbit.com/v1.0/reference#%EC%A3%BCweek-%EC%BA%94%EB%93%A4-1
|
||||
:param str market: 마켓 코드 (ex. KRW-BTC, BTC-BCC)
|
||||
:param str to: 마지막 캔들 시각 (exclusive). 포맷 : yyyy-MM-dd'T'HH:mm:ssXXX. 비워서 요청시 가장 최근 캔들
|
||||
:param int count: 캔들 개수
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/candles/weeks'
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
params = {'market': market}
|
||||
if to is not None:
|
||||
params['to'] = to
|
||||
if count is not None:
|
||||
params['count'] = count
|
||||
return self._get(URL, params=params)
|
||||
|
||||
def get_months_candles(self, market, to=None, count=None):
|
||||
'''
|
||||
월(Month) 캔들
|
||||
https://docs.upbit.com/v1.0/reference#%EC%9B%94month-%EC%BA%94%EB%93%A4-1
|
||||
:param str market: 마켓 코드 (ex. KRW-BTC, BTC-BCC)
|
||||
:param str to: 마지막 캔들 시각 (exclusive). 포맷 : yyyy-MM-dd'T'HH:mm:ssXXX. 비워서 요청시 가장 최근 캔들
|
||||
:param int count: 캔들 개수
|
||||
:return: json array
|
||||
'''
|
||||
|
||||
URL = 'https://api.upbit.com/v1/candles/months'
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
params = {'market': market}
|
||||
if to is not None:
|
||||
params['to'] = to
|
||||
if count is not None:
|
||||
params['count'] = count
|
||||
return self._get(URL, params=params)
|
||||
|
||||
def get_trades_ticks(self, market, to=None, count=None, cursor=None):
|
||||
'''
|
||||
당일 체결 내역
|
||||
https://docs.upbit.com/v1.0/reference#%EC%8B%9C%EC%84%B8-%EC%B2%B4%EA%B2%B0-%EC%A1%B0%ED%9A%8C
|
||||
:param str market: 마켓 코드 (ex. KRW-BTC, BTC-BCC)
|
||||
:param str to: 마지막 체결 시각. 형식 : [HHmmss 또는 HH:mm:ss]. 비워서 요청시 가장 최근 데이터
|
||||
:param int count: 체결 개수
|
||||
:param str cursor: 페이지네이션 커서 (sequentialId)
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/trades/ticks'
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
params = {'market': market}
|
||||
if to is not None:
|
||||
params['to'] = to
|
||||
if count is not None:
|
||||
params['count'] = count
|
||||
if cursor is not None:
|
||||
params['cursor'] = cursor
|
||||
return self._get(URL, params=params)
|
||||
|
||||
def get_ticker(self, markets):
|
||||
'''
|
||||
현재가 정보
|
||||
요청 당시 종목의 스냅샷을 반환한다.
|
||||
https://docs.upbit.com/v1.0/reference#%EC%8B%9C%EC%84%B8-ticker-%EC%A1%B0%ED%9A%8C
|
||||
:param str[] markets: 마켓 코드 리스트 (ex. KRW-BTC, BTC-BCC)
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/ticker'
|
||||
if not isinstance(markets, list):
|
||||
logging.error('invalid parameter: markets should be list')
|
||||
raise Exception('invalid parameter: markets should be list')
|
||||
|
||||
if len(markets) == 0:
|
||||
logging.error('invalid parameter: no markets')
|
||||
raise Exception('invalid parameter: no markets')
|
||||
|
||||
for market in markets:
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
|
||||
markets_data = markets[0]
|
||||
for market in markets[1:]:
|
||||
markets_data += ',%s' % market
|
||||
params = {'markets': markets_data}
|
||||
return self._get(URL, params=params)
|
||||
|
||||
def get_orderbook(self, markets):
|
||||
'''
|
||||
호가 정보 조회
|
||||
https://docs.upbit.com/v1.0/reference#%ED%98%B8%EA%B0%80-%EC%A0%95%EB%B3%B4-%EC%A1%B0%ED%9A%8C
|
||||
:param str[] markets: 마켓 코드 목록 리스트 (ex. KRW-BTC,KRW-ADA)
|
||||
:return: json array
|
||||
'''
|
||||
URL = 'https://api.upbit.com/v1/orderbook?'
|
||||
if not isinstance(markets, list):
|
||||
logging.error('invalid parameter: markets should be list')
|
||||
raise Exception('invalid parameter: markets should be list')
|
||||
|
||||
if len(markets) == 0:
|
||||
logging.error('invalid parameter: no markets')
|
||||
raise Exception('invalid parameter: no markets')
|
||||
|
||||
for market in markets:
|
||||
if market not in self.markets:
|
||||
logging.error('invalid market: %s' % market)
|
||||
raise Exception('invalid market: %s' % market)
|
||||
|
||||
markets_data = markets[0]
|
||||
for market in markets[1:]:
|
||||
markets_data += ',%s' % market
|
||||
params = {'markets': markets_data}
|
||||
return self._get(URL, params=params)
|
||||
|
||||
def get_remaining_req(self):
|
||||
'''
|
||||
요청 수 제한
|
||||
https://docs.upbit.com/docs/user-request-guide
|
||||
:return: dict
|
||||
ex) {'market': {'min': '582', 'sec': '2', 'update_time': datetime.datetime(2019, 6, 6, 7, 7, 12, 153219)}, 'candles': {'min': '592', 'sec': '6', 'update_time': datetime.datetime(2019, 6, 6, 7, 7, 12, 197177)}}
|
||||
- market 관련 요청은 2019년6월6일 7시7분12.153219초 이후 1분동안 582회, 1초동안 2회 호출 가능
|
||||
- candles 관련 요청은 2019년6월6일 7시7분12.197177초 이후 1분동안 592회, 1초동안 6회 호출 가능
|
||||
'''
|
||||
return self.remaining_req
|
||||
|
||||
###############################################################
|
||||
|
||||
def _update_remaining_req(self, resp):
|
||||
if 'Remaining-Req' not in resp.headers.keys():
|
||||
return None
|
||||
keyvals = resp.headers['Remaining-Req'].split('; ')
|
||||
group = None
|
||||
keyval = dict()
|
||||
for _keyval in keyvals:
|
||||
kv = _keyval.split('=')
|
||||
if kv[0] == 'group':
|
||||
group = kv[1]
|
||||
else:
|
||||
keyval[kv[0]] = kv[1]
|
||||
if group is None:
|
||||
return
|
||||
keyval['update_time'] = datetime.now()
|
||||
self.remaining_req[group] = keyval
|
||||
|
||||
|
||||
def _get(self, url, headers=None, data=None, params=None):
|
||||
resp = requests.get(url, headers=headers, data=data, params=params)
|
||||
if resp.status_code not in [200, 201]:
|
||||
logging.error('get(%s) failed(%d)' % (url, resp.status_code))
|
||||
if resp.text is not None:
|
||||
logging.error('resp: %s' % resp.text)
|
||||
raise Exception('request.get() failed(%s)' % resp.text)
|
||||
raise Exception(
|
||||
'request.get() failed(status_code:%d)' % resp.status_code)
|
||||
self._update_remaining_req(resp)
|
||||
return json.loads(resp.text)
|
||||
|
||||
def _post(self, url, headers, data):
|
||||
resp = requests.post(url, headers=headers, data=data)
|
||||
if resp.status_code not in [200, 201]:
|
||||
logging.error('post(%s) failed(%d)' % (url, resp.status_code))
|
||||
if resp.text is not None:
|
||||
raise Exception('request.post() failed(%s)' % resp.text)
|
||||
raise Exception(
|
||||
'request.post() failed(status_code:%d)' % resp.status_code)
|
||||
self._update_remaining_req(resp)
|
||||
return json.loads(resp.text)
|
||||
|
||||
def _delete(self, url, headers, data):
|
||||
resp = requests.delete(url, headers=headers, data=data)
|
||||
if resp.status_code not in [200, 201]:
|
||||
logging.error('delete(%s) failed(%d)' % (url, resp.status_code))
|
||||
if resp.text is not None:
|
||||
raise Exception('request.delete() failed(%s)' % resp.text)
|
||||
raise Exception(
|
||||
'request.delete() failed(status_code:%d)' % resp.status_code)
|
||||
self._update_remaining_req(resp)
|
||||
return json.loads(resp.text)
|
||||
|
||||
def _load_markets(self):
|
||||
try:
|
||||
market_all = self.get_market_all()
|
||||
if market_all is None:
|
||||
return
|
||||
markets = []
|
||||
for market in market_all:
|
||||
markets.append(market['market'])
|
||||
return markets
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
raise Exception(e)
|
||||
|
||||
def _get_token(self, query):
|
||||
payload = {
|
||||
'access_key': self.access_key,
|
||||
'nonce': int(time.time() * 1000),
|
||||
}
|
||||
if query is not None:
|
||||
payload['query'] = urlencode(query)
|
||||
return jwt.encode(payload, self.secret, algorithm='HS256').decode('utf-8')
|
||||
|
||||
def _get_headers(self, query=None):
|
||||
headers = {'Authorization': 'Bearer %s' % self._get_token(query)}
|
||||
return headers
|
||||
|
||||
def _is_valid_price(self, price):
|
||||
'''
|
||||
원화 마켓 주문 가격 단위
|
||||
원화 마켓은 호가 별 주문 가격의 단위가 다릅니다. 아래 표를 참고하여 해당 단위로 주문하여 주세요.
|
||||
https://docs.upbit.com/v1.0/docs/%EC%9B%90%ED%99%94-%EB%A7%88%EC%BC%93-%EC%A3%BC%EB%AC%B8-%EA%B0%80%EA%B2%A9-%EB%8B%A8%EC%9C%84
|
||||
~10 : 0.01
|
||||
~100 : 0.1
|
||||
~1,000 : 1
|
||||
~10,000 : 5
|
||||
~100,000 : 10
|
||||
~500,000 : 50
|
||||
~1,000,000 : 100
|
||||
~2,000,000 : 500
|
||||
+2,000,000 : 1,000
|
||||
'''
|
||||
if price <= 10:
|
||||
if (price*100) != int(price*100):
|
||||
return False
|
||||
elif price <= 100:
|
||||
if (price*10) != int(price*10):
|
||||
return False
|
||||
elif price <= 1000:
|
||||
if price != int(price):
|
||||
return False
|
||||
elif price <= 10000:
|
||||
if (price % 5) != 0:
|
||||
return False
|
||||
elif price <= 100000:
|
||||
if (price % 10) != 0:
|
||||
return False
|
||||
elif price <= 500000:
|
||||
if (price % 50) != 0:
|
||||
return False
|
||||
elif price <= 1000000:
|
||||
if (price % 100) != 0:
|
||||
return False
|
||||
elif price <= 2000000:
|
||||
if (price % 500) != 0:
|
||||
return False
|
||||
elif (price % 1000) != 0:
|
||||
return False
|
||||
return True
|
||||
Reference in New Issue
Block a user