def __init__(self, exchange, symbol, leverage=None, api_key=None, api_secret=None, testnet=True, dry_run=False, candle_limit=None, candle_period=None): """ :param exchange: 거래소이름. bitmex, bithumb.. :param symbol: 심볼페어. XBTUSD, BTC/USD :param dry_run: 참일 경우 실제 주문 api를 호출하지 않는다. :param candle_limit: 저장할 최대 캔들갯수. :param candle_period: 봉주기. Bitmex는 4가지만 지원한다. 1m, 5m, 1h, 1d """ self.candle_handler = None raw_symbol = symbol.replace('/', '') self.api = utils.ccxt_exchange(exchange, api_key=api_key, api_secret=api_secret, is_async=False, opt={'test':testnet}) if api_key and api_secret and self.api and leverage is not None: self.api.private_post_position_leverage({'symbol': raw_symbol, 'leverage': leverage}) logger.info('>>candle_period>> %s',candle_period) # 웹소켓 처리기. self.ws = BitmexWS(raw_symbol, candle_period, api_key=api_key, api_secret=api_secret, testnet=testnet) if candle_limit and candle_period: period_in_second = 0 if candle_period == '1m': period_in_second = 60 elif candle_period == '5m': period_in_second = 300 elif candle_period == '1h': period_in_second = 3600 elif candle_period == '1d': period_in_second = 3600 * 24 since = datetime.datetime.now().timestamp() - period_in_second * candle_limit since = since * 1000 self.candle_handler = CandleHandler(self.api, symbol, period=candle_period, history=candle_limit, since=since)
def enter(self, df, hei): # 포지션이 없을때만 if self.s['current_qty'] == 0: if hei.HA_Diff.iloc[-1] > 0 and hei.HA_Diff.iloc[-2] > 0: # 이전 5개 봉중에 diff>0 인 봉이 다른 색일 경우, 진입가능. allowed = False for i in range(5): if abs(hei.HA_Diff.iloc[-2 - i]) > 0: allowed = hei.HA_Diff.iloc[-2 - i] < 0 break # 가격이 높아지는 추세. if allowed and abs(hei.HA_Diff.iloc[-1] + hei.HA_Diff.iloc[-2]) >= 1: # 롱 진입. self.s['order'] += 1 logger.info('롱 진입요청 %s XBT@%s', self.amount, self.s['bid1']) self.send_telegram('Enter Long Req {} XBT@{}'.format(self.amount, self.s['bid1'])) self.buy_orderbook1(self.amount) elif hei.HA_Diff.iloc[-1] < 0 and hei.HA_Diff.iloc[-2] < 0: allowed = False for i in range(5): if abs(hei.HA_Diff.iloc[-2 - i]) > 0: allowed = hei.HA_Diff.iloc[-2 - i] < 0 break # 가격이 낮아지는 추세. if allowed and abs(hei.HA_Diff.iloc[-1] + hei.HA_Diff.iloc[-2]) >= 1: # 숏 진입. self.s['order'] += 1 logger.info('숏 진입요청 %s XBT@%s', self.amount, self.s['ask1']) self.send_telegram('Enter Short Req {} XBT@{}'.format(self.amount, self.s['ask1'])) self.sell_orderbook1(self.amount)
def leave(self, df, hei): N = 3 # 3개의 연속 가격을 확인하여 익절. close = hei.HA_Close.iloc[-1] if self.s['current_qty'] < 0: # 숏 청산. tobe_pnl = self.s['entry_price'] - close if hei.HA_Diff.iloc[-1] > 0 \ or self.great_or_eq(hei.HA_Close, N) \ or tobe_pnl >= self.takeprofit \ or -tobe_pnl >= self.losscut: # takeprofit=익절. losscut=손절. self.s['order'] += 1 logger.info('숏 청산요청 @%s tobe_pnl[%s]', self.s['bid1'], tobe_pnl) self.send_telegram( 'End Short Req{} XBT@{} tobe_pnl[{}]'.format(abs(self.s['current_qty']), self.s['bid1'], tobe_pnl)) self.buy_orderbook1(self.s['current_qty'], reduce_only=True) elif self.s['current_qty'] > 0: # 롱 청산. tobe_pnl = close - self.s['entry_price'] if hei.HA_Diff.iloc[-1] < 0 \ or self.less_or_eq(hei.HA_Close, N) \ or tobe_pnl >= self.takeprofit \ or -tobe_pnl >= self.losscut: self.s['order'] += 1 logger.info('롱 청산요청 @%s tobe_pnl[%s]', self.s['ask1'], tobe_pnl) self.send_telegram( 'End Long Req {} XBT@{} tobe_pnl[{}]'.format(abs(self.s['current_qty']), self.s['ask1'], tobe_pnl)) self.sell_orderbook1(self.s['current_qty'], reduce_only=True)
def update_position(self, position): logger.debug('update_position > %s', position) self.s['current_qty'] = position['currentQty'] current_qty = self.s['current_qty'] if current_qty == 0: # 포지션이 닫힌 경우. if 'isOpen' in position and not position['isOpen']: prevPnl = position['prevRealisedPnl'] logger.info('Position Closed. pnl[%s%s]', prevPnl, position['currency']) self.send_telegram('Position Closed. pnl[{} {}]'.format(prevPnl, position['currency'])) # stop모두 취소. for order in self.data['order']: if order['ordType'] == 'Stop': order_id = order['orderID'] self.api.cancel_order(id=order_id, symbol=symbol) return if 'avgEntryPrice' in position: if position['avgEntryPrice']: ## entry_price가 대부분 안들어온다. 포지션이 수정될때만 들어옴. 봇을 리스타트시 안들어옴. self.s['entry_price'] = position['avgEntryPrice'] if current_qty > 0: # 롱 포지션은 아래쪽에 Sell을 예약한다. stop_price = round(self.s['entry_price'] - self.stoploss) found = False for order in self.data['order']: # limit 주문만 수정. stop은 손절주문이므로 수정하지 않으며, update_position에서 수정함. if order['ordType'] == 'Stop' and order['side'] == 'Sell': if order['stopPx'] != stop_price: order_id = order['orderID'] self.api.edit_order(id=order_id, symbol=symbol, type='Stop', side='Buy', amount=abs(current_qty), params={'execInst': 'Close,LastPrice', 'stopPx': stop_price}) found = True break if not found: self.api.create_order(symbol, type='Stop', side='Sell', amount=abs(current_qty), params={'execInst': 'Close,LastPrice', 'stopPx': stop_price}) elif current_qty < 0: # 숏 포지션은 위쪽에 Buy를 예약한다. stop_price = round(self.s['entry_price'] + self.stoploss) found = False for order in self.data['order']: # limit 주문만 수정. stop은 손절주문이므로 수정하지 않으며, update_position에서 수정함. if order['ordType'] == 'Stop' and order['side'] == 'Buy': if order['stopPx'] != stop_price: order_id = order['orderID'] self.api.edit_order(id=order_id, symbol=symbol, type='Stop', side='Sell', amount=abs(current_qty), params={'execInst': 'Close,LastPrice', 'stopPx': stop_price}) found = True break if not found: self.api.create_order(symbol, type='Stop', side='Buy', amount=abs(current_qty), params={'execInst': 'Close,LastPrice', 'stopPx': stop_price})
async def connect(self): self.websocket = await websockets.connect( self.url, extra_headers=self.__get_auth()) self.connected = True logger.info('Connected! websocket > %s...', self.websocket) if self.websocket: text = utils.json_dumps(self.topic_request) await self.websocket.send(text)
def load(self): logger.info(f'Load data: {os.path.abspath(self.filepath)}') self._data = pd.read_csv(self.filepath, index_col='time', usecols=['time', 'open', 'high', 'low', 'close', 'volume'], parse_dates=['time'], date_parser=lambda epoch: pd.to_datetime(epoch, unit='s') ) self._size = len(self._data.index)
async def start(self): try: if self.candle_handler: # await self.candle_handler.start() else: logger.info('candle_handler 를 사용하지 않습니다.') except: utils.print_traceback()
async def start(self): if not self.candle_handler: logger.error('Nexus mock is not loaded yet!') return logger.info('Backtest start timer!') while True: df = await self._update_candle() if df is None: break
async def _run_server(self): try: app = web.Application() app.add_routes([ web.get('/', self._handle_info), ]) runner = web.AppRunner(app) await runner.setup() site = web.TCPSite(runner, '0.0.0.0', self.http_port) await site.start() logger.info('HTTP server bind port %s', self.http_port) except Exception as e: logger.error('_run_server error %s', e)
async def send_telegram(telegram_bot_token, telegram_chat_id, text): if telegram_bot_token and telegram_chat_id: logger.info('Telegram %s [%s] [%s]', text, telegram_chat_id, telegram_bot_token) url = f'https://api.telegram.org/bot{telegram_bot_token}/sendMessage' data = { 'chat_id': telegram_chat_id, 'text': text, } async with aiohttp.ClientSession() as session: async with session.post(url, data=data) as response: resp = await response.text() return resp
async def load(self): try: # await self.api.load_markets() if self.candle_handler: # await self.candle_handler.load() else: logger.info('candle_handler 를 사용하지 않습니다.') await self.ws.connect() except: utils.print_traceback()
def __init__(self, params): self.symbol = params['symbol'] self.amount = int(params['amount']) # 계약 갯수. self.stoploss = int(params['stoploss']) # 자동손절범위. 강제청산피하기. 달러. self.losscut = int(params['losscut']) # 손해발생시 반대주문을 낼 범위. 달러 self.takeprofit = int(params['takeprofit']) # 익절범위. 달러. logger.info('%s created!', self.__class__.__name__) # 상태. self.s = { 'current_qty': 0, 'entry_price': 0, 'order': 0, 'bid1': 0, 'ask1': 0, }
def handle_topic(topic, action, data): print(topic, action, data) if topic == 'margin': if action == 'partial': self.margin = data elif action == 'update': self.margin.update(data) used_margin = self.margin['maintMargin'] avail_margin = self.margin['availableMargin'] logger.debug( f'[Margin] used_margin[{used_margin}], avail_margin[{avail_margin}]' ) elif topic == 'order': # for order in data: if action == 'partial' or action == 'insert': if data: order_id = data['orderID'] self.orders[order_id] = data else: if data and 'orderID' in data: order_id = data['orderID'] working_indicator = data['workingIndicator'] if working_indicator: self.orders[order_id].update(data) else: del self.orders[order_id] print(self.orders) print('--------------------') elif topic == 'position': if action == 'partial': self.position = data else: self.position.update(data) # 필수. currentQty = self.position['currentQty'] currency = self.position['currency'] markPrice = self.position['markPrice'] timestamp = self.position['timestamp'] liquidationPrice = self.position['liquidationPrice'] logger.info('%s %s current_qty[%s]', topic, action, currentQty)
def __get_auth(self): '''Return auth headers. Will use API Keys if present in settings.''' if self.api_key: logger.info("BitMEX websocket is authenticated with API Key.") # To auth to the WS using an API key, we generate a signature of a nonce and # the WS API endpoint. expires = generate_nonce() return { 'api-expires': str(expires), 'api-signature': generate_signature(self.api_secret, 'GET', '/realtime', expires, ''), 'api-key': self.api_key, } else: logger.warn("BitMEX websocket is not authenticated.") return {}
def __init__(self, api, symbol, candle_limit, candle_period=None): """ :param symbol: 심볼페어. XBTUSD, BTC/USD :param candle_limit: 저장할 최대 캔들갯수. :param candle_period: 봉주기. Bitmex는 4가지만 지원한다. 1m, 5m, 1h, 1d """ self.candle_handler = None self.api = api self.cb_update_candle = None logger.info(f'Nexus symbol[{symbol}] period[{candle_period}]') if candle_limit and candle_period: period_in_second = 0 if candle_period == '1m': period_in_second = 60 elif candle_period == '5m': period_in_second = 300 elif candle_period == '10m': period_in_second = 600 elif candle_period == '15m': period_in_second = 600 elif candle_period == '1h': period_in_second = 3600 elif candle_period == '4h': period_in_second = 3600 * 4 elif candle_period == '1d': period_in_second = 3600 * 24 ts = datetime.datetime.utcnow().timestamp( ) - period_in_second * candle_limit since = datetime.datetime.utcfromtimestamp(ts).strftime( '%Y-%m-%d %H:00:00') logger.info( f'Get Candle since {since}, ts[{ts}] candle_limit[{candle_limit}]' ) # 현재시각에서 since를 뺀 날짜를 string으로 만든다. self.candle_handler = CandleHandler( self.api, symbol, period=candle_period, since=since, _update_notify=self._update_candle)
def ccxt_exchange(exchange, api_key=None, api_secret=None, is_async=True, opt={}): if api_key is not None and api_secret is not None: opt.update({ 'apiKey': api_key, 'secret': api_secret, }) logger.info('exchange_id >>> %s', exchange) if is_async: api = getattr(ccxt_async, exchange)(opt) else: api = getattr(ccxt, exchange)(opt) api.substituteCommonCurrencyCodes = False return api
def __init__(self, api, symbol, candle_limit, candle_period=None, dry_run=True): """ :param symbol: 심볼페어. XBTUSD, BTC/USD :param dry_run: 참일 경우 실제 주문 api를 호출하지 않는다. :param candle_limit: 저장할 최대 캔들갯수. :param candle_period: 봉주기. Bitmex는 4가지만 지원한다. 1m, 5m, 1h, 1d """ self.candle_handler = None self.api = api self.cb_update_candle = None self.dry_run = dry_run logger.info( f'Nexus symbol[{symbol}] period[{candle_period}] dry_run[{dry_run}]' ) if candle_limit and candle_period: period_in_second = 0 if candle_period == '1m': period_in_second = 60 elif candle_period == '5m': period_in_second = 300 elif candle_period == '1h': period_in_second = 3600 elif candle_period == '1d': period_in_second = 3600 * 24 ts = datetime.datetime.now().timestamp( ) - period_in_second * candle_limit since = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d') # 현재시각에서 since를 뺀 날짜를 string으로 만든다. self.candle_handler = CandleHandler(self.api, symbol, period=candle_period, since=since)
def send_telegram(self, text): coro = utils.send_telegram(self.telegram_bot_token, self.telegram_chat_id, text) logger.info('Telegram %s [%s] [%s]', text, self.telegram_chat_id, self.telegram_bot_token) asyncio.get_event_loop().create_task(coro)
def run(self, algo): self.nexus.callback(update_orderbook=algo.update_orderbook, update_candle=algo.update_candle, update_order=algo.update_order, update_position=algo.update_position) # ccxt api 연결. algo.api = self.nexus.api algo.data = self.nexus.ws.data algo.send_telegram = self.send_telegram loop = asyncio.get_event_loop() try: logger.info('SYMBOL: %s', self.symbol) logger.info('CANDLE_PERIOD: %s', self.candle_period) logger.info('NOW: %s', datetime.now()) logger.info('UCT: %s', datetime.utcnow()) logger.info('ENV[TZ]: %s', os.getenv("TZ")) logger.info('LOGLEVEL: %s', os.getenv("LOGLEVEL")) logger.info('TZNAME: %s', time.tzname) ip_address = requests.get('http://ip.42.pl/raw').text logger.info('IP: %s', ip_address) logger.info('Loading...') self.send_telegram( 'Futuremaker Bot started.. {}'.format(ip_address)) loop.run_until_complete(self.nexus.load()) nexus_start = loop.create_task(self.nexus.start()) loop.run_until_complete(self.init()) scheduled_task = loop.create_task(self.schedule()) if self.http_port and not self.backtest: server_task = loop.create_task(self._run_server()) async def go(): await nexus_start loop.run_until_complete(go()) except KeyboardInterrupt: pass finally: loop.stop()
def update_candle(self, df, candle): logger.info('update_candle %s > %s : %s', df.index[-1], df.iloc[-1], candle) price = df.Close[-1] m, u, l = indicators.bollinger_bands(df.Close, n=20, stdev=1.5) high = round(u[-1], 2) low = round(l[-1], 2) logger.info('BBands high[%s] price[%s] low[%s]', high, price, low) df = indicators.RSI(df, period=14) rsi2 = ta.RSI(df.Close.values) rsi = df.RSI[-1] rsi = round(rsi, 2) rsi2 = round(rsi2[-1], 2) logger.info('RSI[%s] RSI2[%s]', rsi, rsi2) score = 0 if price >= high: logger.info('BBands Upper touched! price[%s] >= bbhigh[%s]', price, high) score += 1 elif price <= low: logger.info('BBands Lower touched! price[%s] <= bblow[%s]', price, low) score -= 1 if rsi >= 60: logger.info('RSI[%s] >= 60.', rsi) score += 1 elif rsi <= 40: logger.info('RSI[%s] <= 40.', rsi) score -= 1 order_qty = 10 if score >= 2: # 질문 메시지 text = f'주문타입: <b>BUY</b>\n' \ f'가격: {df.Close[-1]}\n' \ f'수량: {order_qty}\n' \ f'이유: {self.symbol} Too much buy! price[{price}] >= bbhigh[{high}] AND RSI[{rsi}] >= 60\n'\ f'<a href="https://www.bitmex.com/chartEmbed?symbol=XBTUSD">차트열기</a> \n' \ f'<a href="https://www.bitmex.com/app/trade/XBTUSD">거래소열기</a> \n' self.telegram_bot.send_question(question_text=text, yes_name="BUY", yes_func=self.order, yes_param={ "df": df, "qty": order_qty }, no_name="CANCEL") elif score <= -2: text = f'주문타입: <b>SELL</b>\n' \ f'가격: {df.Close[-1]}\n' \ f'수량: {-order_qty}\n' \ f'이유: {self.symbol} Too much sell! price[{price}] <= bblow[{low}] AND RSI[{rsi}] <= 40\n' \ f'<a href="https://www.bitmex.com/chartEmbed?symbol=XBTUSD">차트열기</a> \n' \ f'<a href="https://www.bitmex.com/app/trade/XBTUSD">거래소열기</a> \n' self.telegram_bot.send_question(question_text=text, yes_name="SELL", yes_func=self.order, yes_param={ "df": df, "qty": -order_qty }, no_name="CANCEL")
def update_candle(self, df, candle): # logger.info('update_candle %s > %s : %s', df.index[-1], df.iloc[-1], candle) logger.info( '>> %s >> %s', datetime.fromtimestamp(df.index[-1] / 1000), f'O:{candle.Open} H:{candle.High} L:{candle.Low} C:{candle.Close} V:{candle.Volume}' ) price = df.Close.iloc[-1] m, u, l = indicators.bollinger_bands(df.Close, n=20, stdev=1.5) high = round(u.iloc[-1], 2) low = round(l.iloc[-1], 2) logger.info('BBands high[%s] price[%s] low[%s]', high, price, low) df = indicators.RSI(df, period=14) rsi = df.RSI.iloc[-1] rsi = round(rsi, 2) logger.info('RSI[%s]', rsi) score = 0 if price >= high: logger.info('BBands Upper touched! price[%s] >= bbhigh[%s]', price, high) score += 1 elif price <= low: logger.info('BBands Lower touched! price[%s] <= bblow[%s]', price, low) score -= 1 if rsi >= 60: logger.info('RSI[%s] >= 60.', rsi) score += 1 elif rsi <= 40: logger.info('RSI[%s] <= 40.', rsi) score -= 1 if score >= 2: self.send_telegram( f'{self.symbol} Too much buy! price[{price}] >= bbhigh[{high}] AND RSI[{rsi}] >= 60' ) elif score <= -2: self.send_telegram( f'{self.symbol} Too much sell! price[{price}] <= bblow[{low}] AND RSI[{rsi}] <= 40' ) sys.exit(0)
def update_order(self, order): logger.info('update_order > %s', order)
def update_position(self, position): logger.info('update_position > %s', position)
def update_position(self, position): logger.info('update_position > %s', position) try: price = position["avgEntryPrice"] self.avg_entry_price = position["avgEntryPrice"] except KeyError: logger.debug("시정 평균가가 변동이 없습니다.") price = self.avg_entry_price current_qty = 0 if position["currentQty"] is None else position["currentQty"] * -1 if position['currentQty'] > 0: self.position = "BOT" price = price + self.limit_tick elif position['currentQty'] < 0: self.position = "SLD" price = price - self.limit_tick else: self.position = "NTL" logger.info(f"규모: {current_qty}, 시장평균가: {price}") if position["currentQty"] != 0: # 포지션을 가지고 있을 경우 if self.limit_order is None: # 익절 주문. self.limit_order = self.nexus.api.put_order(order_qty=current_qty, price=price, stop_price=price, type="LimitIfTouched") elif self.limit_order is not None \ and self.is_order_request \ and self.limit_order["orderQty"] != current_qty: # 기존 주문 가격, 규모 수정. self.limit_order = self.nexus.api.amend_order(order=self.limit_order, order_qty=current_qty, price=price, stop_price=price, type="LimitIfTouched") self.is_order_request = False else: # 포지션이 없을때. pass if self.count % 2 == 0: return try: last_price = position["lastPrice"] except KeyError: return order_list = self.nexus.api.get_order() cancel_orders = [] remove_orders = [] for pare_order in self.pare_orders: high_price = abs(pare_order["price"]) + (0.5 * 10) low_price = abs(pare_order["price"]) - (0.5 * 10) # 가격 범위가 넘은 경우 if last_price > high_price or low_price > last_price: cancel_orders.append(pare_order) # 대기 중이 아닌경우 is_deplay = False for order in order_list: if pare_order["limit_order"]["orderID"] == order["orderID"]: is_deplay = True break if not is_deplay: remove_orders.append(pare_order) # 취소처리 for cancel_order in cancel_orders: remove_orders.append(cancel_order) try: self.nexus.api.cancel_order(cancel_order["limit_order"]) except ExchangeError: pass try: self.nexus.api.cancel_order(cancel_order["stop_limit_order"]) except ExchangeError: pass # 저장된 데이터 제거 for remove_order in remove_orders: for pare_order in self.pare_orders: if remove_order["limit_order"]["orderID"] == pare_order["limit_order"]["orderID"]: self.pare_orders.remove(pare_order) break
def ingest_data(api, symbol, start_date, end_date, interval, history=0, reload=False): """ 데이터를 받아서 csv파일로 저장한다. 만약 파일이 존재한다면 스킵한다. :return: 해당 파일이 존재하는 디렉토리 경로. """ dir = tempfile.gettempdir() timer_start = timeit.default_timer() tz = start_date.tzinfo # 값, 단위 분리 interval_num, interval_unit = split_interval(interval) interval = interval.lower() interval_unit = interval_unit.lower() base_dir, filepath = ingest_filepath(dir, api, symbol, start_date, end_date, interval, history) logger.debug('file_path=%s', filepath) # 강제 리로드 요청이 없고, 파일이 존재하고, 사이즈도 0보다 크면, 그냥 사용. if not reload and os.path.exists( filepath) and os.path.getsize(filepath) > 0: logger.debug('# [{}] CandleFile Download Passed. {}'.format( symbol, filepath)) return base_dir, filepath prepare_date = get_prepare_date(start_date, interval, history) delta = (end_date - prepare_date) # 나누어 받을때 다음번 루프의 시작시점이 된다. next_delta = 0 if interval_unit == 'm': length = (delta.days * 24 * 60 + delta.seconds // 60) // interval_num next_delta = timedelta(minutes=interval_num).total_seconds() elif interval_unit == 'h': length = (delta.days * 24 + delta.seconds // 3600) // interval_num next_delta = timedelta(hours=interval_num).total_seconds() elif interval_unit == 'd': length = (delta.days) // interval_num next_delta = timedelta(days=interval_num).total_seconds() since = int(prepare_date.timestamp()) * 1000 logger.info('Ingest time range: %s ~ %s', prepare_date, end_date) logger.info('Ingest candle length[%s] of [%s] since[%s]', length, interval_unit, since) params = {} if api.id == 'bitmex': max_limit = 750 # params = { 'partial': 'true' } elif api.id == 'binance': max_limit = 1000 with open(filepath, 'w', encoding='utf-8', newline='') as f: wr = csv.writer(f) wr.writerow( ['Datetime', 'Index', 'Open', 'High', 'Low', 'Close', 'Volume']) seq = 0 while length > 0: if seq > 0: time.sleep(api.rateLimit / 1000) # time.sleep wants seconds limit = min(length, max_limit) logger.debug('#### fetch_ohlcv request [%s / %s]', limit, length) # candles = await candles = fetch_ohlcv(api, symbol, interval, since, limit, params) # 읽은 갯수만큼 빼준다. length -= limit logger.debug('#### fetch_ohlcv remnant [%s]', length) logger.debug('#### fetch_ohlcv response [%s] >> \n%s', len(candles), candles) if len(candles) == 0: logger.warn('candle data rows are empty!') break last_candle = None for candle in candles: wr.writerow([ datetime.fromtimestamp( int(candle[0] / 1000), tz=tz).strftime('%Y-%m-%d %H:%M:%S'), int(candle[0]), '{:.8f}'.format(candle[1]), '{:.8f}'.format(candle[2]), '{:.8f}'.format(candle[3]), '{:.8f}'.format(candle[4]), '{:.2f}'.format(candle[5]), ]) last_candle = candle # 다음번 루프는 마지막 캔들부터 이어서 내려받는다. logger.debug('>>> last_candle >>> %s', last_candle) since = last_candle[0] + next_delta seq += 1 timer_end = timeit.default_timer() logger.debug('# {} Downloaded CandleFile. elapsed: {}'.format( symbol, str(timer_end - timer_start))) return base_dir, filepath
def update_candle(self, df, candle): logger.info('>> %s >> %s', datetime.fromtimestamp(df.index[-1] / 1000), f"O:{candle['open']} H:{candle['high']} L:{candle['low']} C:{candle['close']} V:{candle['volume']}") logger.info('orders > %s', self.data['order']) logger.info('self.s > %s', self.s) # 다음 캔들이 도착할때까지 체결못하면 수정 또는 취소. for order in self.data['order']: # limit 주문만 수정. stop은 손절주문이므로 수정하지 않으며, update_position에서 수정함. if order['ordType'] == 'Limit' and order['leavesQty'] > 0: price = order['price'] order_id = order['orderID'] if order['side'] == 'Sell': if abs(self.s['ask1'] - price) >= 1: # 3틱이상 밀리면 취소. # 1달러보다 벌어지면 취소. self.api.cancel_order(id=order_id, symbol=symbol) elif order['side'] == 'Buy': if abs(self.s['bid1'] - price) >= 1: # 3틱이상 밀리면 취소. self.api.cancel_order(id=order_id, symbol=symbol) hei = heikinashi(df) logger.info('Enter..') self.enter(df, hei) logger.info('Leave..') self.leave(df, hei) logger.info('Done..')
def update_candle(self, df, candle): logger.info(f"시장가격: {df.Close[-1]}") if self.count < 5: self.count += 1 return self.count = 0 diff_sum = sum(df.Close - df.Open) add_qty = 0 logger.info(f"포지션: {self.position}, 차이가격: {diff_sum}") if diff_sum > 0: if self.position == "SLD": # SLD 포지션이면 정리. for pare_order in self.pare_orders: try: self.nexus.api.cancel_order(pare_order["limit_order"]) except ExchangeError: logger.debug("<ignore> 없어진 요청.") try: self.nexus.api.cancel_order(pare_order["stop_limit_order"]) except ExchangeError: logger.debug("<ignore> 없어진 요청.") add_qty = self.limit_order["orderQty"] self.limit_order = None self.pare_orders = [] # 매수 order = self.nexus.api.put_order(order_qty=(self.order_qty + add_qty), # side="Buy", price=df.Close[-1] - 0.5 ) if order["ordStatus"] == "New": stop_limit_order = self.nexus.api.put_order(order_qty=-(self.order_qty + add_qty), # side="Sell", price=df.Close[-1] - 0.5 - self.stop_limit_tick, stop_price=df.Close[-1] - 0.5 - self.stop_limit_tick, type="StopLimit") self.pare_orders.append({ "price": df.Close[-1], "position": "BOT", "limit_order": order, "stop_limit_order": stop_limit_order }) self.is_order_request = True elif diff_sum < 0: if self.position == "BOT": # BOT 포지션이면 정리. for pare_order in self.pare_orders: try: self.nexus.api.cancel_order(pare_order["limit_order"]) except ExchangeError: logger.debug("<ignore> 없어진 요청.") try: self.nexus.api.cancel_order(pare_order["stop_limit_order"]) except ExchangeError: logger.debug("<ignore> 없어진 요청.") add_qty = self.limit_order["orderQty"] self.limit_order = None self.pare_orders = [] # 매도 order = self.nexus.api.put_order(order_qty=-(self.order_qty + add_qty), # side="Sell", price=df.Close[-1] + 0.5) if order["ordStatus"] == "New": stop_limit_order = self.nexus.api.put_order(order_qty=(self.order_qty + add_qty), # side="Buy", price=df.Close[-1] + 0.5 + self.stop_limit_tick, stop_price=df.Close[-1] + 0.5 + self.stop_limit_tick, type="StopLimit") self.pare_orders.append({ "price": df.Close[-1], "position": "SLD", "limit_order": order, "stop_limit_order": stop_limit_order }) self.is_order_request = True
def restart(): logger.info("Restarting the marketmaker...") cmd = [sys.executable] + sys.argv logger.info("Restarting cmd >> %s", cmd) os.execv(sys.executable, cmd)
async def run(self, algo): self.nexus.callback(update_candle=algo._update_candle) algo.api = self.api algo.local_tz = self.local_tz algo.send_message = self.send_message algo.backtest = self.backtest try: logger.info('SYMBOL: %s', self.symbol) logger.info('CANDLE_PERIOD: %s', self.candle_period) logger.info('UCT: %s', datetime.utcnow()) logger.info('ENV[TZ]: %s', os.getenv("TZ")) logger.info('TIMEZONE: %s', self.timezone) logger.info('LocalTime: %s', utils.localtime(datetime.utcnow(), self.local_tz)) logger.info('LOGLEVEL: %s', os.getenv("LOGLEVEL")) logger.info('TZNAME: %s', time.tzname) ip_address = requests.get('https://api.ipify.org?format=json').json()['ip'] logger.info('IP: %s', ip_address) self.send_message(f'{algo.get_name()} Bot started.. {ip_address}') logger.info('Loading...') # 봇 상태로드 algo.load_status() # ready 구현체가 있다면 호출. algo.ready() if not self.backtest: t = threading.Thread(target=self.start_sched, daemon=True) t.start() await self.nexus.load() logger.info('Start!') await self.nexus.start() except KeyboardInterrupt: pass
async def run(self, algo): self.nexus.callback(update_orderbook=algo.update_orderbook, update_candle=algo.update_candle, update_order=algo.update_order, update_position=algo.update_position) # ccxt api 연결. # algo.api = self.nexus.api # algo.data = self.nexus.api.data algo.send_telegram = self.send_telegram try: logger.info('SYMBOL: %s', self.symbol) logger.info('CANDLE_PERIOD: %s', self.candle_period) logger.info('NOW: %s', datetime.now()) logger.info('UCT: %s', datetime.utcnow()) logger.info('ENV[TZ]: %s', os.getenv("TZ")) logger.info('LOGLEVEL: %s', os.getenv("LOGLEVEL")) logger.info('TZNAME: %s', time.tzname) ip_address = requests.get('https://api.ipify.org?format=json').json()['ip'] logger.info('IP: %s', ip_address) await self.send_telegram(f'{algo.get_name()} Bot started.. {ip_address}') logger.info('Loading...') await self.nexus.load() await self.nexus.wait_ready() await self.nexus.start() except KeyboardInterrupt: pass