def determine_maximum_volume_by_balance(pair_id, deal_type, volume, price, balance): """ :param pair_id: :param deal_type: :param volume: :param price: :param balance: :return: Decimal object representing exact volume """ base_currency_id, dst_currency_id = split_currency_pairs(pair_id) if deal_type == DEAL_TYPE.SELL: # What is maximum volume we can SELL at exchange if not balance.do_we_have_enough(dst_currency_id, volume): volume = balance.available_balance[dst_currency_id] elif deal_type == DEAL_TYPE.BUY: # what is maximum volume we can buy at exchange if not balance.do_we_have_enough(base_currency_id, volume * price): volume = (MAX_VOLUME_COEFFICIENT * balance.available_balance[base_currency_id]) / price else: assert deal_type not in [DEAL_TYPE.BUY, DEAL_TYPE.SELL] return volume
def compute_loss(trades_to_order_by_pair): orders_by_arbitrage_id = defaultdict(list) # 1 stage group by arbitrage id for order, trades in trades_to_order_by_pair: orders_by_arbitrage_id[order.arbitrage_id].append((order, trades)) orders_by_pair = defaultdict(list) cnt = 0 for arbitrage_id in orders_by_arbitrage_id: if len(orders_by_arbitrage_id[arbitrage_id]) != 1: continue order, trades_list = orders_by_arbitrage_id[arbitrage_id][0] msg = "can't find pair order - {o}".format(o=order) log_to_file(msg, "missing_" + get_pair_name_by_id(order.pair_id) + ".txt") cnt += 1 orders_by_pair[order.pair_id].append((order, trades_list)) loss_details = defaultdict(list) loss_details_total = Counter() for pair_id in orders_by_pair: loss_by_coin, loss_by_base_coin = compute_loss_by_pair( orders_by_pair[pair_id]) base_currency_id, dst_currency_id = split_currency_pairs(pair_id) loss_details[base_currency_id].append( LossDetails(base_currency_id, dst_currency_id, pair_id, loss_by_coin, loss_by_base_coin)) loss_details_total[base_currency_id] += loss_by_base_coin return loss_details, loss_details_total
def do_we_have_enough_by_pair(self, pair_id, exchange_id, volume, price): # i.e. we are going to BUY volume of dst_currency by price # using base_currency so we have to adjust volume base_currency_id, dst_currency_id = split_currency_pairs(pair_id) return self.do_we_have_enough(base_currency_id, exchange_id, volume * price)
def subtract_balance_by_pair(self, order_book, volume, price): # i.e. we SELL dst_currency_id for src_currency_id src_currency_id, dst_currency_id = split_currency_pairs(order_book.pair_id) self.subtract_balance(dst_currency_id, order_book.exchange_id, volume) self.add_balance(src_currency_id, order_book.exchange_id, volume * price) # <<<==== bitcoin! self.update_time(order_book.exchange_id, order_book.timest)
def round_volume_by_huobi_rules(volume, pair_id): pair_name = get_currency_pair_to_huobi(pair_id) base_currency_id, dst_currency_id = split_currency_pairs(pair_id) if pair_name in PRECISION_NUMBER[base_currency_id]: return truncate_float(volume, PRECISION_NUMBER[base_currency_id][pair_name]) return volume
def compute_min_cap_from_ticker(pair_id, ticker): min_price = DECIMAL_ZERO if ticker is not None: min_price = max(min_price, ticker.ask) base_currency_id, dst_currency_id = split_currency_pairs(pair_id) if min_price != DECIMAL_ZERO: return MIN_VOLUME_COEFFICIENT[base_currency_id] / min_price return DECIMAL_ZERO
def determine_minimum_volume(first_order_book, second_order_book, balance_state): """ we are going to SELL something at first exchange we are going to BUY something at second exchange using BASE_CURRENCY This method determine maximum available volume of DST_CURRENCY on 1ST exchanges This method determine maximum available volume according to amount of available BASE_CURRENCY on 2ND exchanges :param first_order_book: :param second_order_book: :param balance_state: :return: Decimal object representing exact number """ # 1st stage: What is minimum amount of volume according to order book min_volume = min(first_order_book.bid[FIRST].volume, second_order_book.ask[LAST].volume) if min_volume <= 0: msg = "determine_minimum_volume - something severely wrong - NEGATIVE min price: {pr}".format( pr=min_volume) print_to_console(msg, LOG_ALL_ERRORS) log_to_file(msg, ERROR_LOG_FILE_NAME) assert min_volume <= 0 base_currency_id, dst_currency_id = split_currency_pairs( first_order_book.pair_id) # 2nd stage: What is maximum volume we can SELL at first exchange if not balance_state.do_we_have_enough( dst_currency_id, first_order_book.exchange_id, min_volume): min_volume = balance_state.get_available_volume_by_currency( dst_currency_id, first_order_book.exchange_id) # 3rd stage: what is maximum volume we can buy if not balance_state.do_we_have_enough_by_pair( first_order_book.pair_id, second_order_book.exchange_id, min_volume, second_order_book.ask[LAST].price): min_volume = (MAX_VOLUME_COEFFICIENT * balance_state.get_available_volume_by_currency( base_currency_id, second_order_book.exchange_id) ) / second_order_book.ask[LAST].price return min_volume
def compute_new_min_cap_from_tickers(pair_id, tickers): min_price = DECIMAL_ZERO for ticker in tickers: if ticker is not None: try: # FIXME NOTE: in case of errors we may get string with error instead of Ticker object # need to fix process_async_to_list at ConnectionPool min_price = max(min_price, ticker.ask) except: msg = "Msg bad ticker value = {}!".format(ticker) log_to_file(msg, ERROR_LOG_FILE_NAME) base_currency_id, dst_currency_id = split_currency_pairs(pair_id) if min_price != DECIMAL_ZERO: return MIN_VOLUME_COEFFICIENT[base_currency_id] / min_price return DECIMAL_ZERO
def adjust_currency_balance(first_order_book, second_order_book, threshold, balance_threshold, action_to_perform, balance_state, deal_cap, type_of_deal, worker_pool, msg_queue): deal_status = STATUS.FAILURE, None pair_id = first_order_book.pair_id src_currency_id, dst_currency_id = split_currency_pairs(pair_id) src_exchange_id = first_order_book.exchange_id dst_exchange_id = second_order_book.exchange_id if balance_state.is_there_disbalance(dst_currency_id, src_exchange_id, dst_exchange_id, balance_threshold) and \ is_no_pending_order(pair_id, src_exchange_id, dst_exchange_id): max_volume = Decimal(0.5) * abs( balance_state.get_available_volume_by_currency( dst_currency_id, dst_exchange_id) - balance_state. get_available_volume_by_currency(dst_currency_id, src_exchange_id)) # FIXME NOTE: side effect here deal_cap.update_max_volume_cap(max_volume) log_currency_disbalance_present(src_exchange_id, dst_exchange_id, pair_id, dst_currency_id, balance_threshold, max_volume, threshold) deal_status = search_for_arbitrage(first_order_book, second_order_book, threshold, balance_threshold, action_to_perform, balance_state, deal_cap, type_of_deal, worker_pool, msg_queue) else: log_currency_disbalance_heart_beat(src_exchange_id, dst_exchange_id, dst_currency_id, balance_threshold) return deal_status
def init_deals_with_logging_speedy(trade_pairs, difference, file_name, processor, msg_queue): # FIXME move after deal placement ? global overall_profit_so_far overall_profit_so_far += trade_pairs.current_profit base_currency_id, dst_currency_id = split_currency_pairs( trade_pairs.deal_1.pair_id) msg = """We try to send following deals to exchange. <b>Expected profit in {base_coin}:</b> <i>{cur}</i>. <b>Overall:</b> <i>{tot}</i> <b>Difference in percents:</b> <i>{diff}</i> Deal details: {deal} """.format(base_coin=get_currency_name_by_id(base_currency_id), cur=float_to_str(trade_pairs.current_profit), tot=float_to_str(overall_profit_so_far), diff=difference, deal=str(trade_pairs)) msg_queue.add_message(DEAL_INFO_MSG, msg) log_to_file(msg, file_name) if not YES_I_KNOW_WHAT_AM_I_DOING: die_hard("init_deals_with_logging_speedy called for {f}".format( f=trade_pairs)) parallel_deals = [] for order in [trade_pairs.deal_1, trade_pairs.deal_2]: method_for_url = dao.get_method_for_create_url_trade_by_exchange_id( order) # key, pair_name, price, amount key = get_key_by_exchange(order.exchange_id) pair_name = get_currency_pair_name_by_exchange_id( order.pair_id, order.exchange_id) post_details = method_for_url(key, pair_name, order.price, order.volume) constructor = return_with_no_change wu = WorkUnit(post_details.final_url, constructor, order) wu.add_post_details(post_details) parallel_deals.append(wu) res = processor.process_async_post(parallel_deals, DEAL_MAX_TIMEOUT) if res is None: log_to_file( "For TradePair - {tp} result is {res}".format(tp=trade_pairs, res=res), file_name) log_to_file( "For TradePair - {tp} result is {res}".format(tp=trade_pairs, res=res), ERROR_LOG_FILE_NAME) return # check for errors only for entry in res: json_response, order = entry if "ERROR" in json_response: msg = """ <b>ERROR: </b>NONE During deal placement: {u1} Details: {err_msg} """.format(u1=order, err_msg=json_response) msg_queue.add_order(FAILED_ORDERS_MSG, order) else: msg = """ For trade {trade} Response is {resp} """.format(trade=order, resp=json_response) print_to_console(msg, LOG_ALL_ERRORS) msg_queue.add_message(DEBUG_INFO_MSG, msg) log_to_file(msg, file_name) for order in [trade_pairs.deal_1, trade_pairs.deal_2]: msg_queue.add_order(ORDERS_MSG, order)
orders, history_trades = prepare_data(pg_conn, start_time, end_time) missing_orders, failed_orders, orders_with_trades = group_trades_by_orders( orders, history_trades) # 2 stage - bucketing all that crap by pair_id trades_to_order = defaultdict(list) for order, trade_list in orders_with_trades: trades_to_order[order.pair_id].append((order, trade_list)) total_profit_by_base = Counter() profit_details = defaultdict(list) for pair_id in trades_to_order: profit_pair, profit_base = compute_profit_by_pair( pair_id, trades_to_order[pair_id]) base_currency_id, dst_currency_id = split_currency_pairs(pair_id) total_profit_by_base[base_currency_id] += profit_base profit_details[base_currency_id].append( ProfitDetails(base_currency_id, dst_currency_id, pair_id, profit_pair, profit_base)) loss_details, total_loss_by_base = compute_loss(orders_with_trades) save_report(start_time, end_time, total_profit_by_base, profit_details, missing_orders, failed_orders, loss_details, total_loss_by_base, orders, history_trades)