コード例 #1
0
    def test_failed_leg1(self):
        ex = ccxtExchangeWrapper.load_from_id("binance")  #type: ccxtExchangeWrapper

        ex.set_offline_mode("test_data/markets.json", "test_data/tickers_maker.csv")

        ex.load_markets()
        ex.fetch_tickers()
        tickers = ex.fetch_tickers()  # second fetch contains good triangle

        om = ActionOrderManager(ex)

        om.offline_order_updates = 10

        good_triangle = self.good_triangle

        maker = SingleTriArbMakerDeal(currency1=good_triangle["cur1"],
                                      currency2=good_triangle["cur2"],
                                      currency3=good_triangle["cur3"],
                                      price1=good_triangle["leg1-price"],
                                      price2=good_triangle["leg2-price"],
                                      price3=good_triangle["leg3-price"],
                                      start_amount=0.01,
                                      min_amount_currency1=0.003,
                                      symbol1=good_triangle["symbol1"],
                                      symbol2=good_triangle["symbol2"],
                                      symbol3=good_triangle["symbol3"],
                                      commission=0.00075,
                                      commission_maker=0.0006,
                                      threshold=1.001,
                                      max_order1_updates=2000,
                                      max_order2_updates=2000,
                                      max_order3_updates=2000,
                                      uuid="test"
                                      )

        maker.update_state(tickers)
        om.add_order(maker.order1)

        om.proceed_orders()
        maker.update_state(tickers)

        maker.order1.force_close()

        om.proceed_orders()
        maker.update_state(tickers)

        self.assertEqual("Failed", maker.status)

        self.assertEqual(0, maker.leg2_recovery_amount)
        self.assertEqual(0, maker.leg2_recovery_target)

        self.assertEqual(0, maker.leg3_recovery_amount)
        self.assertEqual(0, maker.leg3_recovery_target)

        self.assertEqual(0, maker.filled_start_amount)
        self.assertEqual(0, maker.result_amount)
        self.assertEqual(0, maker.gross_profit)
コード例 #2
0
    def test_fok_threshold_order_manager_canceled_on_threshold(self):
        ex = ccxtExchangeWrapper.load_from_id("binance") # type: ccxtExchangeWrapper
        ex.set_offline_mode("test_data/markets.json", "test_data/tickers.csv")

        ex._offline_tickers = {

           0: {"ETH/BTC": {"ask": 1, "bid": 0.99}},
           1: {"ETH/BTC": {"ask": 1, "bid": 0.95}},
           2: {"ETH/BTC": {"ask": 1, "bid": 0.99}},
           3: {"ETH/BTC": {"ask": 1, "bid": 0.99}},
           4: {"ETH/BTC": {"ask": 1, "bid": 0.99}},
           5: {"ETH/BTC": {"ask": 1, "bid": 0.99}},
           6: {"ETH/BTC": {"ask": 1, "bid": 0.99}},
           7: {"ETH/BTC": {"ask": 1, "bid": 0.95}},
        }

        tickers = ex.fetch_tickers("ETH/BTC")

        taker_price = core.get_symbol_order_price_from_tickers("ETH","BTC", tickers)["price"]

        order = FokThresholdTakerPriceOrder.create_from_start_amount("ETH/BTC", "ETH", 1, "BTC", taker_price,
                                                                     taker_price_threshold=-0.01,
                                                                     threshold_check_after_updates=5)

        om = ActionOrderManager(ex)
        om.request_trades = False

        om.add_order(order)

        while len(om.get_open_orders()) > 0:
            om.proceed_orders()

        closed_order = om.get_closed_orders()[0]  # type: FokThresholdTakerPriceOrder
        trade_order = closed_order.orders_history[0]  # type: TradeOrder

        self.assertEqual("closed", closed_order.status)
        self.assertEqual("canceled", trade_order.status)
        self.assertEqual(["#below_threshold"], order.tags)

        # updates of order:
        # 1 - order created
        # 2 .. 5 - order updated without requesting tickers
        # 6 - request ticker
        # 7 - receives ticker and send command to cancel. 2nd ticker is "bad" because first was used to get price
        #   - check's if it was canceled
        # 8 - updates trades order with the canceled response

        self.assertEqual(8, trade_order.update_requests_count)
コード例 #3
0
    def test_trade_order_report_from_trade_order_after_fill(self):

        order = ActionOrder.create_from_start_amount("ETH/BTC", "BTC", 1,
                                                     "ETH", 0.01)

        ew = ccxtExchangeWrapper.load_from_id(
            "binace")  # type: ccxtExchangeWrapper
        ew.set_offline_mode("test_data/markets.json", "test_data/tickers.csv")
        om = ActionOrderManager(ew)

        om.add_order(order)
        while len(om.get_open_orders()) > 0:
            om.proceed_orders()

        trade_order = om.get_closed_orders()[0].orders_history[
            0]  # type: TradeOrder

        order_report = TradeOrderReport.from_trade_order(
            trade_order,
            datetime.datetime.now(tz=pytz.timezone("UTC")),
            supplementary={"order_no": 1})

        self.assertEqual(order_report.symbol, trade_order.symbol)
        self.assertEqual(order_report.side, trade_order.side)
        self.assertEqual(order_report.amount, trade_order.amount)
        self.assertEqual(order_report.filled, trade_order.filled)
        self.assertEqual(order_report.cost, trade_order.cost)

        self.assertListEqual(order_report.trades, trade_order.trades)

        self.assertDictEqual(order_report.timestamp_closed,
                             trade_order.timestamp_closed)
        self.assertDictEqual(order_report.timestamp_open,
                             trade_order.timestamp_open)

        self.assertEqual(order_report.start_currency,
                         trade_order.start_currency)
        self.assertEqual(order_report.start_currency,
                         trade_order.start_currency)

        self.assertEqual(
            "fill", order_report.supplementary["parent_action_order"]["state"])
        self.assertEqual(1, order_report.supplementary["order_no"])
コード例 #4
0
    def test_fok_threshold_order_manager_canceled_on_updates_count(self):
        ex = ccxtExchangeWrapper.load_from_id("binance")  # type: ccxtExchangeWrapper
        ex.set_offline_mode("test_data/markets.json", "test_data/tickers.csv")

        ex._offline_tickers = {

            0: {"ETH/BTC": {"ask": 1, "bid": 0.99}},
            1: {"ETH/BTC": {"ask": 1, "bid": 0.96}},
            2: {"ETH/BTC": {"ask": 1, "bid": 0.96}},
            3: {"ETH/BTC": {"ask": 1, "bid": 0.96}},
            4: {"ETH/BTC": {"ask": 1, "bid": 0.96}},
            5: {"ETH/BTC": {"ask": 1, "bid": 0.96}},
            6: {"ETH/BTC": {"ask": 1, "bid": 0.96}},
            7: {"ETH/BTC": {"ask": 1, "bid": 0.96}},
        }

        tickers = ex.fetch_tickers("ETH/BTC")

        taker_price = core.get_symbol_order_price_from_tickers("ETH", "BTC", tickers)["price"]

        order = FokThresholdTakerPriceOrder.create_from_start_amount("ETH/BTC", "ETH", 1, "BTC", taker_price,
                                                                     max_order_updates=10,
                                                                     taker_price_threshold=-0.05,
                                                                     threshold_check_after_updates=5)

        om = ActionOrderManager(ex)
        om.request_trades = False

        om.add_order(order)

        while len(om.get_open_orders()) > 0:
            om.proceed_orders()

        closed_order = om.get_closed_orders()[0]  # type: FokThresholdTakerPriceOrder
        trade_order = closed_order.orders_history[0]  # type: TradeOrder

        self.assertEqual("closed", closed_order.status)
        self.assertEqual("canceled", trade_order.status)
        self.assertEqual(list(), order.tags)

        self.assertEqual(11, trade_order.update_requests_count)
コード例 #5
0
    def test_cancel_by_time(self):
        fok_order_time = FokThresholdTakerPriceOrder("ADA/ETH", 1000, 0.32485131 / 1000, "sell", max_order_updates=10,
                                                time_to_cancel=0.1)

        order_command = fok_order_time.update_from_exchange({})

        ex = ccxtExchangeWrapper.load_from_id("binance")  # type: ccxtExchangeWrapper
        ex.set_offline_mode("test_data/markets.json", "test_data/tickers.csv")

        om = ActionOrderManager(ex)
        om.request_trades = False

        om.add_order(fok_order_time)
        om.proceed_orders()
        time.sleep(0.11)

        om.proceed_orders()
        om.proceed_orders()

        self.assertEqual(fok_order_time.status, "closed")
        self.assertIn("#timeout", fok_order_time.tags)
コード例 #6
0
ファイル: tribot.py プロジェクト: ztomsy/tribot
class TriBot(Bot):
    # attributes of bot to be saved in reports. intended to be as a configuration parameters with config_ prefix

    CONFIG_PARAMETERS = ["share_balance_to_bid", "commission", "threshold",
                         "threshold_order_book", "max_trades_updates", "order_update_total_requests",
                         "order_update_requests_for_time_out", "order_update_time_out",
                         "max_oder_books_fetch_attempts", "max_order_update_attempts", "request_sleep", "lap_time",
                         "max_requests_per_lap", "sleep_on_tickers_error", "force_start_amount", "force_best_tri",
                         "override_depth_amount", "skip_order_books", "recover_factor", "not_request_trades",
                         "cancel_price_threshold", "fullthrottle"]

    def __init__(self, default_config: str, log_filename=None):

        self.session_uuid = str(uuid.uuid4())
        self.fetch_number = 0
        self.errors = 0

        self.config_filename = default_config

        self.exchange_id = ""
        self.server_id = ""
        self.script_id = ""
        self.start_currency = list()
        self.ignore_currency = list()
        self.allowed_assets = list()

        self.share_balance_to_bid = float()
        self.min_amounts = dict()
        self.commission = float()
        self.threshold = float()
        self.threshold_order_book = float()
        self.balance_bid_thresholds = dict()
        self.api_key = dict()

        self.cancel_price_threshold = 0.0  # relative to taker's price threshold to cancel the order

        self.recover_factor = 0.0  # multiplier applied to target recovery amount

        self.max_trades_updates = 0
        self.not_request_trades = False  # if to request trades

        self.order_update_total_requests = 0
        self.order_update_requests_for_time_out = 0
        self.order_update_time_out = 0
        self.last_update_time = datetime(1, 1, 1, 1, 1, 1)

        self.orders_settings = None

        self.max_oder_books_fetch_attempts = 0
        self.max_order_update_attempts = 0
        self.request_sleep = 0.0  # sleep time between requests in seconds

        self.fullthrottle = dict()  # fullthrottle mode settings. dict of {"enabled":True, "start_at":10}
        self.state = "go"  # or could be "wait" in fullthrottle mode

        self.start_ft_timestamp = 0.0

        self.timer = ...  # type: timer.Timer

        self.lap_time = float()
        self.max_requests_per_lap = 0.0
        self.sleep_on_tickers_error = 0.0  # sleeping time when exception on receiving tickers

        self.test_balance = float()

        # start amount parameters
        self.force_start_amount = float()
        self.force_best_tri = bool()
        self.override_depth_amount = float()
        self.skip_order_books = False

        self.verbose = False
        self.debug = bool()
        self.run_once = False
        self.noauth = False

        self.offline = False
        self.offline_tickers_file = "test_data/tickers.csv"
        self.offline_order_books_file = ""
        self.offline_markets_file = "test_data/markets.json"
        self.offline_deal_uuid = ""
        self.offline_run_test = False  # if to run the test

        self.recovery_server = ""

        self.tickers_file = str()

        self.logger = logging
        self.log_filename = log_filename

        self.logger = self.init_logging(self.log_filename)

        self.LOG_DEBUG = logging.DEBUG
        self.LOG_INFO = logging.INFO
        self.LOG_ERROR = logging.ERROR
        self.LOG_CRITICAL = logging.CRITICAL

        self.report_all_deals_filename = str()
        self.report_tickers_filename = str()
        self.report_deals_filename = str()
        self.report_prev_tickers_filename = str()

        self.report_dir = str()
        self.deals_file_id = int()

        self.reporter = None  # type: TkgReporter

        # ;((((
        # SQL Alchemy could not work together with Mongo!!!

        # self.mongo = dict()  # mongo configuration
        # self.mongo_reporter = None  # type: MongoReporter
        # prev config on mongo
        # "mongo": {
        #     "note": "Should be disabled",
        #     "enabled": false,
        #     "#host": "mongodb://*****:*****@localhost:27017/?authSource=tkg_dev",
        #     "host": "mongodb://<LOGIN>:<PASSWORD>@tkg-reporting-shard-00-00-izwsm.mongodb.net:27017,tkg-reporting-shard-00-01-izwsm.mongodb.net:27017,tkg-reporting-shard-00-02-izwsm.mongodb.net:27017/test?ssl=true&replicaSet=TKG-Reporting-shard-0&authSource=admin&retryWrites=true",
        #     "db": "tkg_dev",
        #     "tables": {
        #         "trade_orders": "trade_orders",
        #         "tri_results": "tri_results"
        #     }
        # },

        self.influxdb = None
        self.sqla = dict()  # sqla configuration
        self.sqla_reporter = None  # type: SqlaReporter

        self.exchange = ...  # type: ccxtExchangeWrapper

        self.basic_triangles = list()
        self.basic_triangles_count = int()

        self.all_triangles = list()

        self.markets = dict()
        self.tickers = dict()
        self.deal_uuid = ""

        self.tri_list = list()
        self.tri_list_good = list()

        # self.recovery_data = list()

        self.balance = float()

        self.time = timer.Timer
        self.last_proceed_report = dict()

        self.order_manager = None  # type: ActionOrderManager

        # load config from json

    def get_cli_parameters(self, args):
        return get_cli_parameters(args)

    # parse cli
    def set_from_cli(self, args):

        cli_args = self.get_cli_parameters(args)

        for i in cli_args.__dict__:
            attr_val = getattr(cli_args, i)
            if attr_val is not None:
                setattr(self, i, attr_val)

    #
    # init logging
    #

    def init_logging(self, file_log=None):

        log_formatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s]  %(message)s")
        logger = logging.getLogger("tribot")

        if file_log is not None:
            file_handler = logging.FileHandler(file_log)
            file_handler.setFormatter(log_formatter)
            logger.addHandler(file_handler)

        console_handler = logging.StreamHandler(sys.stdout)
        console_handler.setFormatter(log_formatter)
        logger.addHandler(console_handler)

        logger.setLevel(logging.INFO)

        return logger

    def set_log_level(self, log_level):

        self.logger.setLevel(log_level)

    def log(self, level, msg, msg_list=None):
        if msg_list is None:
            self.logger.log(level, msg)
        else:
            self.logger.log(level, msg)
            for line in msg_list:
                self.logger.log(level, "... " + str(line))

    def init_reports(self, directory):

        try:
            os.stat(directory)

        except:
            os.mkdir(directory)
            print("New directory created:", directory)

        # self.deals_file_id = utils.get_next_report_filename(directory, self.report_deals_filename)

        # self.report_deals_filename = self.report_deals_filename % (directory, self.deals_file_id)
        # self.report_prev_tickers_filename = self.report_prev_tickers_filename % (directory, self.deals_file_id)
        self.report_dir = directory

    def init_timer(self):
        self.timer = timer.Timer()
        self.timer.max_requests_per_lap = self.max_requests_per_lap
        self.timer.lap_time = self.lap_time

    def init_exchange(self):
        # exchange = getattr(ccxt, self.exchange_id)
        # self.exchange = exchange({'apiKey': self.api_key["apiKey"], 'secret': self.api_key["secret"] })
        # self.exchange.load_markets()
        if not self.noauth:
            self.exchange = ccxtExchangeWrapper.load_from_id(self.exchange_id, self.api_key["apiKey"],
                                                             self.api_key["secret"])  # type: ccxtExchangeWrapper
        else:
            self.exchange = ccxtExchangeWrapper.load_from_id(self.exchange_id)  # type: ccxtExchangeWrapper

        # enabling throttling
        self.exchange.enable_requests_throttle(self.lap_time, self.max_requests_per_lap)

    def init_offline_mode(self):
        self.exchange.set_offline_mode(self.offline_markets_file, self.offline_tickers_file)

        if self.offline_order_books_file:
            self.exchange.load_offline_order_books_from_csv(self.offline_order_books_file)

    def init_test_run(self):

        self.log(self.LOG_INFO, "Init offline test. Will set run once to TRUE")

        self.deal_uuid = "test"
        self.exchange_id = "test"
        self.run_once = True

        self.init_reports("_" + self.exchange_id + "/")

        path = "_{}/".format(self.exchange_id)
        files = glob.glob(path + "test*")

        for f in files:
            self.log(self.LOG_INFO, "Deleting test file {}".format(f))

            try:
                os.remove(f)
            except Exception as e:
                self.log(self.LOG_ERROR, "Could not delete  file {}".format(f))
                self.log(self.LOG_ERROR, "Exception: {}".format(type(e).__name__))
                self.log(self.LOG_ERROR, "Exception body:", e.args)

    def init_order_manager(self):
        self.order_manager = ActionOrderManager(self.exchange, self.max_order_update_attempts,
                                                self.max_order_update_attempts,
                                                self.request_sleep)

        self.order_manager.log = self.log
        self.order_manager.LOG_INFO = self.LOG_INFO
        self.order_manager.LOG_ERROR = self.LOG_ERROR
        self.order_manager.LOG_DEBUG = self.LOG_DEBUG
        self.order_manager.LOG_CRITICAL = self.LOG_CRITICAL

        if self.not_request_trades:
            self.order_manager.request_trades = False

        # setting for offline mode
        if self.offline:
            self.exchange.trades_in_offline_order_update = False

    def load_markets(self):
        markets = self.exchange.load_markets()
        if len(self.allowed_assets) == 0:
            self.markets = markets

        else:
            self.markets = {k: v for k, v in markets.items() if (v["base"] in self.allowed_assets and
                                                                 v["quote"] in self.allowed_assets)}

    def set_triangles(self):

        self.basic_triangles = ta.get_basic_triangles_from_markets(self.markets)
        self.all_triangles = ta.get_all_triangles(self.basic_triangles, self.start_currency)

        # return True

    def proceed_triangles(self):
        if len(self.tri_list) == 0:
            self.tri_list = ta.fill_triangles(self.all_triangles, self.start_currency, self.tickers, self.commission)
        else:
            self.tri_list = ta.update_triangles(self.tri_list, tickers=self.tickers, commission=self.commission)

        return self.tri_list

    def load_balance(self):

        if self.test_balance > 0:
            self.balance = self.test_balance
            return self.test_balance
        else:
            if self.offline:
                self.exchange.set_offline_balance(
                    {'free': {
                        'BTC': 1,
                        'USD': 123.00,
                        "ETH": 10},
                        "BTC": {"free": 1},
                        "USDT": {"free": 123},
                        "ETH": {"free": 10}
                    }
                )

            self.balance = self.exchange.fetch_free_balance()[self.start_currency[0]]
            return self.balance

    #
    # get maximum balance to bid in respect to thresholds set in config
    #
    def max_balance_to_bid_from_thresholds(self, currency=None, balance=None, result=None, ob_result=None):

        currency = self.start_currency[0] if currency is None else currency
        balance = self.balance if balance is None else balance
        result = self.tri_list_good[0]["result"] if result is None else result
        ob_result = self.tri_list_good[0]["ob_result"] if ob_result is None else ob_result

        # balance_results_thresholds_config = self.balance_bid_thresholds[currency]

        balance_results_thresholds_config = dict()

        for l in self.balance_bid_thresholds[currency]:
            balance_results_thresholds_config[float(l["max_bid"])] = l

        balance_thresholds = sorted(list(balance_results_thresholds_config.keys()))

        try:
            i = bisect.bisect_left(balance_thresholds, balance)
        except IndexError:
            i = 0

        if i >= len(balance_thresholds):
            i = len(balance_thresholds) - 1

        while i >= 0:
            if result >= balance_results_thresholds_config[balance_thresholds[i]]['result_threshold'] and \
                    ob_result >= balance_results_thresholds_config[balance_thresholds[i]]["orderbook_result_threshold"]:
                to_bid = min(balance, balance_thresholds[i])
                return to_bid

            else:
                i = i - 1
        return None

    def get_order_books_async(self, symbols: list):
        """
        returns the dict of {"symbol": OrderBook} in offline mode the order book is single line - ticker price and big
         amount
        :param symbols: list of symbols to get orderbooks
        :return: returns the dict of {"symbol": OrderBook}
        """
        i = 0
        ob_array = list()

        while len(ob_array) < 3 and i < self.max_oder_books_fetch_attempts:
            i += 1
            try:
                ob_array = self.exchange.get_order_books_async(symbols, 10)
            except Exception as e:
                self.log(self.LOG_ERROR, "Error while fetching order books exchange_id:{} session_uuid:{}"
                                         " fetch_num:{}:".
                         format(self.exchange_id, self.session_uuid, self.fetch_number))
                self.log(self.LOG_ERROR, "Exception: {}".format(type(e).__name__))
                self.log(self.LOG_ERROR, "Exception body:", e.args)

                self.log(self.LOG_ERROR, "Sleeping before next request")
                time.sleep(self.request_sleep)

        if len(ob_array) < 3:
            raise Exception("Could not fetch all order books. Fetched: {}".format(len(ob_array)))

        order_books = dict()
        for ob in ob_array:
            order_books[ob["symbol"]] = OrderBook(ob["symbol"], ob["asks"], ob["bids"])

            if self.offline and "from_ticker" in ob and ob["from_ticker"]:
                self.log(self.LOG_INFO, "Order Book for {} created from TICKER".format(ob["symbol"]))

        return order_books

    def fetch_tickers(self):
        self.fetch_number += 1
        self.tickers = self.exchange.fetch_tickers()
        return self.tickers

    def check_good_triangles(self, triangle: dict):

        threshold = self.threshold
        ignore_currencies = self.ignore_currency

        if ignore_currencies is None:
            if triangle['result'] is not None and triangle['result'] > threshold:
                return True

        if ignore_currencies is not None:
            if triangle["cur1"] not in ignore_currencies \
                    and triangle["cur2"] not in ignore_currencies \
                    and triangle["cur3"] not in ignore_currencies:
                if triangle['result'] is not None and triangle['result'] > threshold:
                    return True
        return False

    def get_good_triangles(self, triangles_with_results=None):
        """
        :return: sorted by result list of good triangles
        """
        # tri_list = list(filter(lambda x: x['result'] > 0, self.tri_list))
        tri_list = list()

        if triangles_with_results is not None:
            tri_list = triangles_with_results
        else:
            tri_list = self.tri_list

        tri_list = sorted(tri_list, key=lambda k: k['result'], reverse=True)

        # threshold = threshold if threshold is not None else self.threshold

        tri_list_good = list(
            filter(self.check_good_triangles,
                   tri_list))

        # self.tri_list_good = tri_list_good
        self.last_proceed_report = dict()
        self.last_proceed_report["best_result"] = tri_list[0]

        if triangles_with_results is not None:
            self.tri_list = tri_list_good

        return tri_list_good

    def start_amount_to_bid(self, working_triangle: dict, order_books: dict, force_best_tri=False,
                            force_start_amount: float = 0.0, skip_order_books=False):

        """
        Returns the amount to bid in first leg in accordance to parameters.

        :param working_triangle: working triangle dict, should contain following fields: result, ob_result, symbol1,
         symbol2, symbol3,  leg{1,2,3}-order
        :param order_books: dict of order books, where keys are leg numbers and values are order_books objects for
        corresponding symbols. Ex:  {1: order_books[working_triangle["symbol1"]],
                                                            2: order_books[working_triangle["symbol2"]],
                                                            3: order_books[working_triangle["symbol3"]]}

        :param force_best_tri: bot option
        :param force_start_amount: bot option
        :return: amount to bid or None in case of error
        """
        if skip_order_books:
            working_triangle["ob_result"] = 100000

        if not force_start_amount and not force_best_tri:
            bal_to_bid = self.max_balance_to_bid_from_thresholds(self.start_currency[0], self.balance,
                                                                 working_triangle["result"],
                                                                 working_triangle["ob_result"])
            if bal_to_bid is None:
                return None

        elif force_best_tri and not force_start_amount:
            bal_to_bid = self.balance

            # we take max balances from initial thresholds
            bal_to_bid = self.max_balance_to_bid_from_thresholds(self.start_currency[0], self.balance,
                                                                 self.threshold,
                                                                 self.threshold_order_book)

        if force_start_amount:
            bal_to_bid = force_start_amount

        return bal_to_bid

    def finalize_start_amount(self, start_amount):
        """
        check if need to restrict max bid to share_balance_to_bid

        :param start_amount:
        :return:
        """

        if start_amount > self.balance * self.share_balance_to_bid:
            final_start_amount = self.balance * self.share_balance_to_bid
            self.log(self.LOG_INFO,
                     "Start amount is greater than max allowed by share_balance_to_bid={}. Decrease to{} ".format(
                         self.share_balance_to_bid, start_amount))
        else:
            final_start_amount = start_amount

        return final_start_amount

    # getting the maximum amount to bid for the first trade from the settings and order book result

    def restrict_amount_to_bid_from_order_book(self, start_amount, working_triangle, order_books, force_best_tri=False):

        if force_best_tri:
            order_book_threshold = 0  # for filtering the results on order_book_threshold
        else:
            order_book_threshold = self.threshold_order_book / (1 - self.commission) ** 3

        self.log(self.LOG_INFO, "Getting start bid with from the Order book (order_book_threshold={})....".format(
            order_book_threshold))
        max_possible = ta.get_maximum_start_amount(self.exchange, working_triangle,
                                                   {1: order_books[working_triangle["symbol1"]],
                                                    2: order_books[working_triangle["symbol2"]],
                                                    3: order_books[working_triangle["symbol3"]]},
                                                   start_amount, 10,
                                                   self.min_amounts[self.start_currency[0]],
                                                   order_book_threshold)
        if max_possible is None:
            self.log(self.LOG_INFO,
                     "No good results when getting maximum bid from OB.... Skipping")
            # working_triangle["status"] = "OB STOP"
            return None, None, None

        start_amount = max_possible["amount"]
        expected_result = max_possible["result"]
        ob_result = expected_result * ((1 - self.commission) ** 3)
        if start_amount > self.min_amounts[self.start_currency[0]]:
            self.log(self.LOG_INFO,
                     "Increase start amount: {} (was {})".format(start_amount,
                                                                 self.min_amounts[self.start_currency[0]]))
        return expected_result, ob_result, start_amount

    def log_order_create(self, order_manager: OrderManagerFok):
        self.log(self.LOG_INFO, "Tick {}: Order {} created. Filled dest curr:{} / {} ".format(
            order_manager.order.update_requests_count,
            order_manager.order.id,
            order_manager.order.filled_dest_amount,
            order_manager.order.amount_dest))

    # here is the sleep between updates is implemented! needed to be fixed
    def log_order_update(self, order: TradeOrder):
        self.log(self.LOG_INFO, "Order {} update req# {}/{} (to timer {}). Status:{}. Filled amount:{} / {} ".format(
            order.id,
            order.update_requests_count,
            self.order_update_total_requests,
            self.order_update_requests_for_time_out,
            order.status,
            order.filled,
            order.amount))

        # now_order = datetime.now()
        #
        # if order.status == "open" and \
        #         order.update_requests_count >= self.order_update_requests_for_time_out:
        #
        #     if order.update_requests_count >= self.order_update_total_requests:
        #         self.log(self.LOG_INFO, "...last update will no sleep")
        #
        #     else:
        #         self.log(self.LOG_INFO, "...reached the number of order updates for timeout")
        #
        #         if (now_order - self.last_update_time).total_seconds() < self.order_update_time_out:
        #             self.log(self.LOG_INFO, "...sleeping while order update for {}".format(self.order_update_time_out))
        #             time.sleep(self.order_update_time_out)
        #
        #         self.last_update_time = datetime.now()

    def log_on_order_update_error(self, order_manager, exception):
        self.log(self.LOG_ERROR, "Error updating  order_id: {}".format(order_manager.order.id))
        self.log(self.LOG_ERROR, "Exception: {}".format(type(exception).__name__))

        for ll in exception.args:
            self.log(self.LOG_ERROR, type(exception).__name__ + ll)

        return True

    def assign_updates_functions_for_order_manager(self):
        OrderManagerFok.on_order_create = lambda _order_manager: self.log_order_create(_order_manager)
        OrderManagerFok.on_order_update = lambda _order_manager: self.log_order_update(_order_manager)
        OrderManagerFok.on_order_update_error = lambda _order_manager, _exception: self.log_on_order_update_error(
            _order_manager, _exception)

    def do_trade(self, leg, symbol, start_currency, dest_currency, amount, side, price, amount_cancel_threshold):
        """
        proceed with the trade and return TradeOrder

        :param symbol: str
        :param start_currency: str
        :param dest_currency: str
        :param amount: float
        :param side: str
        :param price: float
        :return: TradeOrder
        """
        _amount_cancel_threshold = copy.copy(amount_cancel_threshold)

        self.log(self.LOG_INFO, "Amount cancel threshold {}. Price {}".format(_amount_cancel_threshold,
                                                                                             price))

        order_amount = core.base_amount_for_target_currency(start_currency, amount, symbol, price)

        if order_amount <= _amount_cancel_threshold:
            _amount_cancel_threshold = 0
            self.log(self.LOG_INFO, "Order amount {} less cancel threshold {}. CT will be 0".format(
                order_amount, _amount_cancel_threshold))

        # order = TradeOrder.create_limit_order_from_start_amount(symbol, start_currency, amount, dest_currency, price)
        if self.cancel_price_threshold == 0.0:
            self.log(self.LOG_INFO, "Proceeding order without  threshold")

            order = FokOrder.create_from_start_amount(symbol, start_currency, amount, dest_currency, price,
                                                      max_order_updates=self.order_update_total_requests,
                                                      time_to_cancel=utils.dict_value_from_path(
                                                          self.orders_settings, [str(leg), "time_to_cancel"]),
                                                      cancel_threshold=_amount_cancel_threshold)
        else:
            self.log(self.LOG_INFO, "Proceeding order with taker price threshold from ticker{}".format(
                self.cancel_price_threshold))

            order = FokThresholdTakerPriceOrder.create_from_start_amount(
                symbol, start_currency, amount, dest_currency, price,
                max_order_updates=self.order_update_total_requests, taker_price_threshold=self.cancel_price_threshold,
                threshold_check_after_updates=self.order_update_requests_for_time_out - 2,

                time_to_cancel=utils.dict_value_from_path(self.orders_settings, [str(leg), "time_to_cancel"]),
                cancel_threshold=_amount_cancel_threshold)

        trade_order = copy.deepcopy(order.get_active_order())
        trade_order.tags = ""

        # if self.offline:
        #     o = self.exchange.create_order_offline_data(order, 10)
        #     self.exchange._offline_order = copy.copy(o)
        #     self.exchange._offline_trades = copy.copy(o["trades"])
        #     self.exchange._offline_order_update_index = 0
        #     self.exchange._offline_order_cancelled = False

        # cancel_threshold = self.min_order_amount(symbol, price)

        # order_manager = OrderManagerFok(order, None, updates_to_kill=self.order_update_total_requests,
        #                                 max_cancel_attempts=self.order_update_total_requests,
        #                                 max_order_update_attempts=self.max_order_update_attempts,
        #                                 request_sleep=self.request_sleep)

        self.order_manager.add_order(order)

        while len(self.order_manager.get_open_orders()) > 0:
            active_trade_order = self.order_manager.get_open_orders()[0].get_active_order()
            # self.log_order_update(active_trade_order)

            print("Order {} seconds from start# {}. Status:{}. Filled amount:{} / {} ".format(
            order.id,
            time.time() - order.timestamp,
            order.status,
            order.filled,
            order.amount))

            if active_trade_order.update_requests_count <= self.order_update_requests_for_time_out:
                self.log(self.LOG_INFO, "number of order updates less than order_update_requests_for_time_out")
            else:

                # check timer
                sleep_time = self.exchange.requests_throttle.sleep_time()
                print("Current period time {cur_period_time}/{period}. "
                      "Requests in curtent period {cur_requests_in_period}/{req_in_per}. ".format(
                        cur_period_time=self.exchange.requests_throttle._current_period_time,
                        period=self.exchange.requests_throttle.period,
                        cur_requests_in_period=self.exchange.requests_throttle.total_requests_current_period,
                        req_in_per=self.exchange.requests_throttle.requests_per_period))

                print("Sleeping for {}s".format(sleep_time))
                time.sleep(sleep_time)

            self.order_manager.proceed_orders()

        try:

            trade_order = self.order_manager.get_closed_orders()[0].orders_history[0]

            if self.order_manager.get_closed_orders()[0].tags is not None and \
                    len(self.order_manager.get_closed_orders()[0].tags) > 0:

                trade_order.tags = " ".join(self.order_manager.get_closed_orders()[0].tags)
            else:
                trade_order.tags = ""

        except Exception as exception:
            self.log(self.LOG_ERROR, "Error extracting trade order!")
            self.log(self.LOG_ERROR, "Exception: {}".format(type(exception).__name__))

            for ll in exception.args:
                self.log(self.LOG_ERROR, type(exception).__name__ + ll)

            # trade_order = None

        return trade_order

    def get_trade_results(self, order: TradeOrder):

        results = list()
        i = 0
        while bool(results) is not True and i < self.max_trades_updates:
            self.log(self.LOG_INFO, "getting trades #{}".format(i))
            try:
                results = self.exchange.get_trades_results(order)
            except Exception as e:
                self.log(self.LOG_ERROR, type(e).__name__)
                self.log(self.LOG_ERROR, e.args)
                self.log(self.LOG_INFO, "retrying to get trades... after sleep for {}s".format(self.request_sleep))

                time.sleep(self.request_sleep)
                self.log(self.LOG_INFO, "sleep done")
            i += 1

        return results

    def order2_best_recovery_start_amount(self, filled_start_currency_amount, order2_amount, order2_filled):
        res = 0.0
        if order2_amount > 0:
            res = filled_start_currency_amount - (order2_filled / order2_amount) * filled_start_currency_amount
            res = res * self.recover_factor
        return res

    def order3_best_recovery_start_amount(self, filled_start_currency_amount, order2_amount, order2_filled,
                                          order3_amoumt,
                                          order3_filled):
        res = 0.0
        if order2_amount > 0 and order3_amoumt > 0:
            res = filled_start_currency_amount * (order2_filled / order2_amount) * (1 - (order3_filled / order3_amoumt))
            res = res * self.recover_factor
        return res

    def create_recovery_data(self, deal_uuid, start_cur: str, dest_cur: str, start_amount: float,
                             best_dest_amount: float, leg: int) -> dict:
        recovery_dict = dict()
        recovery_dict["deal-uuid"] = deal_uuid
        recovery_dict["symbol"] = core.get_symbol(start_cur, dest_cur, self.markets)
        recovery_dict["start_cur"] = start_cur
        recovery_dict["dest_cur"] = dest_cur
        recovery_dict["start_amount"] = start_amount
        recovery_dict["best_dest_amount"] = best_dest_amount
        recovery_dict["leg"] = leg  # order leg to recover from
        recovery_dict["timestamp"] = time.time()

        return recovery_dict

    def print_recovery_data(self, recovery_data):
        self.log(self.LOG_INFO, "leg {}".format(recovery_data["leg"]))
        self.log(self.LOG_INFO, "Recover leg {}: {} {} -> {} {} ".
                 format(recovery_data["leg"], recovery_data["start_cur"], recovery_data["start_amount"],
                        recovery_data["dest_cur"], recovery_data["best_dest_amount"]))

    def send_recovery_request(self, recovery_data: dict):
        self.log(self.LOG_INFO, "Sending recovery request...")
        try:

            resp = rest_server.rest_call_json(
                "{}:{}/order/".format(self.recovery_server["host"], self.recovery_server["port"]),
                recovery_data, "PUT")

        except Exception as e:
            self.log(self.LOG_ERROR, "Could not send recovery request")
            self.log(self.LOG_ERROR, "Exception: {}".format(type(e).__name__))
            self.log(self.LOG_ERROR, "Exception body:", e.args)
            return False

        self.log(self.LOG_INFO, "Response: {}".format(resp))
        return resp

    def get_report_fields(self):
        report_fields = list([
            "server-id", "exchange-id", "session-uuid", "fetch-number", "deal-uuid", "dbg", "triangle", "status",
            "start-qty", "start-filled", "finish-qty", "result-fact-diff", "for-recover",
            "leg2-recover-amount", "leg2-recover-target", "leg3-recover-amount", "leg3-recover-target",
            "result", "ob_result",
            "leg1-order-result", "leg1-filled", "leg1-price-fact", "leg1-ob-price", "leg1-price", "leg1-fee",
            "leg2-order-result", "leg2-filled", "leg2-price-fact", "leg2-ob-price", "leg2-price", "leg2-fee",
            "leg3-order-result", "leg3-filled", "leg3-price-fact", "leg3-ob-price", "leg3-price", "leg3-fee",
            "leg1-order-updates", "leg2-order-updates", "leg3-order-updates",
            "order1-internal_id", "order2-internal_id", "order3-internal_id",
            "cur1", "cur2", "cur3", "leg1-order", 'leg2-order', 'leg3-order', 'symbol1', 'symbol2', 'symbol3',
            "time_fetch", "time_proceed", "time_from_start", "errors", "time_after_deals", "balance", "timestamp",
            "timestamp_finish", "leg1-tags", "leg2-tags", "leg3-tags"])

        for a in self.CONFIG_PARAMETERS:
            report_fields.append("_config_" + a)

        return report_fields

    def get_config_report(self):
        """
        collect config report fields where keys are set in CONFIG_PARAMETERS
        :return:
        """
        report = collections.OrderedDict()

        for f in self.CONFIG_PARAMETERS:
            if hasattr(self, f):
                report["_config_{}".format(f)] = getattr(self, f)

        report["_config_fullthrottle"] = self.fullthrottle["enabled"]

        return report

    def get_deal_report(self, working_triangle: dict, recovery_data, order1: TradeOrder, order2: TradeOrder = None,
                        order3: TradeOrder = None, price1=None, price2=None, price3=None):

        report_fields = self.get_report_fields()

        report = collections.OrderedDict()

        wt = copy.copy(working_triangle)

        # adding report data which are not in working triangle
        try:
            timer_report = self.timer.timestamps()
            wt["timestamp"] = timer_report["time_from_start"]
            wt["timestamp_finish"] = timer_report["time_after_deals"]
        finally:
            pass

        wt["server-id"] = self.server_id
        wt["exchange-id"] = self.exchange_id
        wt["dbg"] = self.debug
        wt["live"] = self.force_best_tri
        wt["session-uuid"] = self.session_uuid
        wt["errors"] = self.errors
        wt["fetch_number"] = self.fetch_number
        wt["balance"] = self.balance

        wt["start-qty"] = float(order1.amount_start) if order1 is not None else 0.0
        wt["start-filled"] = float(order1.filled_start_amount) if order1 is not None else 0.0

        wt["status"] = "InRecovery" if len(recovery_data) > 0 else working_triangle["status"]

        wt["leg1-order-status"] = order1.status if order1 is not None else None
        wt["leg2-order-status"] = order2.status if order2 is not None else None
        wt["leg3-order-status"] = order3.status if order3 is not None else None

        wt["leg1-tags"] = order1.tags if order1 is not None else None
        wt["leg2-tags"] = order2.tags if order2 is not None else None
        wt["leg3-tags"] = order3.tags if order3 is not None else None

        wt["order1-internal_id"] = order1.internal_id if order1 is not None else None
        wt["order2-internal_id"] = order2.internal_id if order2 is not None else None
        wt["order3-internal_id"] = order3.internal_id if order3 is not None else None

        wt["leg1-filled"] = order1.filled / order1.amount if order1 is not None and order1.amount != 0 else 0.0
        wt["leg2-filled"] = order2.filled / order2.amount if order2 is not None and order2.amount != 0 else 0.0
        wt["leg3-filled"] = order3.filled / order3.amount if order3 is not None and order3.amount != 0 else 0.0

        wt["leg1-order-updates"] = order1.update_requests_count if order1 is not None else None
        wt["leg2-order-updates"] = order2.update_requests_count if order2 is not None else None
        wt["leg3-order-updates"] = order3.update_requests_count if order3 is not None else None

        wt["leg1-price-fact"] = order1.cost / order1.filled if order1 is not None and order1.filled != 0 else 0.0
        wt["leg2-price-fact"] = order2.cost / order2.filled if order2 is not None and order2.filled != 0 else 0.0
        wt["leg3-price-fact"] = order3.cost / order3.filled if order3 is not None and order3.filled != 0 else 0.0

        wt["leg1-ob-price"] = price1
        wt["leg2-ob-price"] = price2
        wt["leg3-ob-price"] = price3

        wt["leg1-fee"], wt["leg2-fee"], wt["leg3-fee"] = (order.fees[order.dest_currency]["amount"]
                                                          if order is not None and order.dest_currency in order.fees
                                                          else None
                                                          for order in (order1, order2, order3))

        if order3 is not None and order1 is not None and order3.filled_dest_amount > 0:
            wt["finish-qty"] = order3.filled_dest_amount - order3.fees[order3.dest_currency]["amount"]
        else:
            wt["finish-qty"] = 0.0

        wt["result-fact-diff"] = float(wt["finish-qty"] - order1.filled_start_amount) if order1 is not None else 0.0
        wt["result-fact"] = wt["finish-qty"] / order1.filled_start_amount if order1 is not None and \
                                                                             order1.filled_start_amount != 0 else 0.0

        total_recover_amount = float(0.0)

        # collect recovery data
        for r in recovery_data:
            total_recover_amount += r["best_dest_amount"]
            wt["leg{}-recover-amount".format(r["leg"])] = r["start_amount"]  # amount of CUR in leg sent to recover
            wt["leg{}-recover-target".format(r["leg"])] = r["best_dest_amount"]  # amount of target CUR1 for recover
        wt["for-recover"] = total_recover_amount

        # collect timer data
        time_report = self.timer.results_dict()
        for f in time_report:
            report_fields.append(f)
            wt[f] = time_report[f]

        # copy working triangle data into report
        for f in report_fields:
            if f in wt:
                report[f] = wt[f]

        # insert config report

        report.update(self.get_config_report())

        return report

    def sqla_report_from_report_dict(self, report: dict):

        report_sqla = DealReport(
            timestamp=datetime.fromtimestamp(report["timestamp_finish"], tz=pytz.timezone('UTC')),
            timestamp_start=datetime.fromtimestamp(report["timestamp"], tz=pytz.timezone('UTC')),
            exchange=report["exchange-id"],
            instance=report["server-id"],
            server=report["server-id"],
            deal_type="triarb",
            deal_uuid=report["deal-uuid"],
            status=report["status"],
            currency=report["cur1"],
            start_amount=report["start-filled"],
            result_amount=report["finish-qty"],
            gross_profit=report["result-fact-diff"],
            net_profit=0.0,
            config=self.get_config_report(),
            deal_data=report
        )

        return report_sqla

    def get_orders_dict_report(self, order1: TradeOrder, order2: TradeOrder = None,
                               order3: TradeOrder = None):

        report = list()

        if order1 is not None:
            report.append(order1.report())

        if order2 is not None:
            report.append(order2.report())

        if order3 is not None:
            report.append(order3.report())

        return report

    def sqla_orders_report(self, deal_uuid, order1: TradeOrder, order2: TradeOrder = None,
                           order3: TradeOrder = None):

        timestamp_now = datetime.now(tz=pytz.timezone("UTC"))
        report = list()

        if order1 is not None:
            report.append(TradeOrderReport.from_trade_order(order1, timestamp_now, deal_uuid=deal_uuid,
                                                            supplementary={"leg": 1, "deal_state": "main"}))

        if order2 is not None:
            report.append(TradeOrderReport.from_trade_order(order2, timestamp_now, deal_uuid=deal_uuid,
                                                            supplementary={"leg": 2, "deal_state": "main"}))

        if order3 is not None:
            report.append(TradeOrderReport.from_trade_order(order3, timestamp_now, deal_uuid=deal_uuid,
                                                            supplementary={"leg": 3, "deal_state": "main"}))

        return report

    def log_report(self, report):
        for r in self.get_report_fields():
            self.log(self.LOG_INFO, "{} = {}".format(r, report[r] if r in report else "None"))

    def send_remote_report(self, report, orders_dict_report=None, sqla_orders_report: list = None):

        if self.sqla is not None and "enabled" in self.sqla and self.sqla["enabled"]:
            try:
                self.log(self.LOG_INFO, "Sending report to sqla....")

                if self.sqla_reporter.session is None:
                    self.sqla_reporter.new_session()
                    self.log(self.LOG_INFO, ".. new SQL Session created ")

                deal_report = self.sqla_report_from_report_dict(report)
                self.sqla_reporter.session.add(deal_report)

                for o in sqla_orders_report:
                    self.sqla_reporter.session.add(o)

                self.sqla_reporter.session.commit()

                self.log(self.LOG_INFO, ".... done")

            except Exception as e:
                self.log(self.LOG_ERROR, "SQLA: Error sending report")
                self.log(self.LOG_ERROR, "Exception: {}".format(type(e).__name__))
                self.log(self.LOG_ERROR, "Exception body:", e.args)

                print("Exception in user code:")
                print("-" * 60)
                traceback.print_exc(file=sys.stdout)
                print("-" * 60)

                self.sqla_reporter.session.rollback()

    def reload_balance(self, result_fact_diff: float = 0.0):

        prev_balance = copy.copy(self.balance)
        i = 0

        while i < self.max_trades_updates and not self.offline:
            try:
                self.load_balance()
                self.log(self.LOG_INFO, "Updating balance.. {}/{}".format(i, self.max_trades_updates))

                if self.balance > (prev_balance + result_fact_diff) * 0.9:
                    self.log(self.LOG_INFO, "Balance Updated: {} ( was: {})".format(self.balance, prev_balance))
                    return True

            except Exception as e:
                self.log(self.LOG_ERROR, "Error while fetching balance {}".format(self.exchange_id))
                self.log(self.LOG_ERROR, "Exception: {}".format(type(e).__name__))
                self.log(self.LOG_ERROR, "Exception body:", e.args)
                self.log(self.LOG_INFO, "Sleeping for {}s".format(self.request_sleep))
                time.sleep(self.request_sleep)
            i += 1
            self.log(self.LOG_INFO, "Balance is not updated...")

        return False

    def save_order_books(self, deal_uuid: str, order_books):

        ob_file_header = ["deal-uuid",
                          "symbol", "ask", "ask-qty", "bid", "bid-qty"]

        order_book_storage = deal_uuid + "_ob"

        # deal_prefix = list([deal_uuid])

        storage = DataStorage("_{}/".format(self.exchange_id))
        storage.register(order_book_storage, ob_file_header)

        table_to_save = list()

        for i in order_books:
            ob_rows = order_books[i].csv_rows(10, True)
            table_to_save.extend(list(map(lambda x: [deal_uuid] + [order_books[i].symbol] + x, ob_rows)))

        storage.save_all(order_book_storage, table_to_save)

        return True

    def save_single_deal_csv(self, deal):
        write_header = False

        file_deals = "_{}/{}.csv".format(self.exchange_id, deal["deal-uuid"])

        if not os.path.isfile(file_deals):
            write_header = True

        with open(file_deals, 'a', newline='') as csvfile:
            writer = csv.DictWriter(csvfile, fieldnames=self.get_report_fields(), extrasaction="ignore")
            if write_header:
                writer.writeheader()

            writer.writerow(deal)

    @staticmethod
    def check_time_to_launch(start_at, timestamp):

        seconds_from_timestamp = datetime.fromtimestamp(timestamp).second
        if seconds_from_timestamp in start_at:
            return True

        return False

    def update_state(self, current_state: str, timestamp: float, fullthrottle_enabled: bool, start_at: list,
                     lap_time: float, prev_throttle_start_timestamp: float):

        self.state = current_state

        if fullthrottle_enabled:
            # self.state = "wait"

            # timestamp_int_str = "{:.0f}".format(timestamp // 1)  # str int part of timestamp
            # len_of_start_at = len(start_at[0])

            if current_state == "wait" and self.check_time_to_launch(start_at, timestamp) and \
                    (timestamp - prev_throttle_start_timestamp > lap_time or prev_throttle_start_timestamp == 0):
                self.state = "go"
                # return self.state

            if current_state == "go" and timestamp - prev_throttle_start_timestamp > lap_time:
                self.state = "wait"
                # return self.state

        else:
            self.state = "go"
            # return self.state

        return self.state

    @staticmethod
    def print_logo(product=""):
        print('TTTTTTTTTT    K    K     GGGGG')
        print('    T         K   K     G')
        print('    T         KKKK      G')
        print('    T         K  K      G  GG')
        print('    T         K   K     G    G')
        print('    T         K    K     GGGGG')
        print('-' * 36)
        print('          %s               ' % product)
        print('-' * 36)
コード例 #7
0
    def test_recovery(self):
        ex = ccxtExchangeWrapper.load_from_id("binance")  #type: ccxtExchangeWrapper

        ex.set_offline_mode("test_data/markets.json", "test_data/tickers_maker.csv")

        ex.load_markets()
        ex.fetch_tickers()
        tickers = ex.fetch_tickers()  # second fetch contains good triangle

        om = ActionOrderManager(ex)

        om.offline_order_updates = 10

        good_triangle = self.good_triangle

        maker = SingleTriArbMakerDeal(currency1=good_triangle["cur1"],
                                      currency2=good_triangle["cur2"],
                                      currency3=good_triangle["cur3"],
                                      price1=good_triangle["leg1-price"],
                                      price2=good_triangle["leg2-price"],
                                      price3=good_triangle["leg3-price"],
                                      start_amount=0.01,
                                      min_amount_currency1=0.003,
                                      symbol1=good_triangle["symbol1"],
                                      symbol2=good_triangle["symbol2"],
                                      symbol3=good_triangle["symbol3"],
                                      commission=0.00075,
                                      commission_maker=0.0006,
                                      threshold=1.001,
                                      max_order1_updates=2000,
                                      max_order2_updates=2000,
                                      max_order3_updates=2000,
                                      recover_factor_order2=1.2,
                                      recover_factor_order3=1.3,
                                      uuid="test"
                                      )

        maker.update_state(tickers)
        om.add_order(maker.order1)

        while len(om.get_open_orders()) > 0:
            # !!!!
            # first we proceed the orders and we update the deal manager with new updated orders
            om.proceed_orders()
            maker.update_state(tickers)

        om.add_order(maker.order2)
        om.proceed_orders()  # this will create order

        while maker.order2.get_active_order().update_requests_count < 6:
            om.proceed_orders()  # this will fill order once for 1/10 of amount
            maker.update_state(tickers)

        print("")
        print("Force Cancel")
        print("")

        # let's force to close the order
        maker.order2.force_close()
        om.proceed_orders()

        maker.update_state(tickers)

        self.assertEqual("closed", maker.order2.status)
        self.assertLess(maker.order2.filled, maker.order2.amount)
        self.assertLess(0, maker.leg2_recovery_amount)
        self.assertLess(0, maker.leg2_recovery_target)

        self.assertEqual(maker.leg2_recovery_amount,  maker.order2.start_amount - maker.order2.filled_start_amount)
        self.assertAlmostEqual(0.5 * maker.order2.amount, maker.leg2_recovery_amount, 6)

        self.assertAlmostEqual(1/2, maker.order2.filled / maker.order2.amount, 6)
        self.assertAlmostEqual(0.005*1.2, maker.leg2_recovery_target, 6)

        # proceed to leg3

        self.assertEqual("order3_create", maker.state)
        om.add_order(maker.order3)

        while maker.order3.get_active_order().update_requests_count < 6:
            om.proceed_orders()  # this will fill order once for 1/10 of amount
            maker.update_state(tickers)

        # let's force to close the order
        maker.order3.force_close()
        om.proceed_orders()

        maker.update_state(tickers)

        self.assertEqual(maker.leg3_recovery_amount,  maker.order3.start_amount - maker.order3.filled_start_amount)

        self.assertAlmostEqual(1 / 2, maker.order3.filled / maker.order3.amount, 6)
        self.assertAlmostEqual(0.0025*1.3, maker.leg3_recovery_target, 6)
        self.assertAlmostEqual(0.5 * maker.order3.amount, maker.leg3_recovery_amount, 6)

        self.assertEqual("finished", maker.state)

        self.assertEqual("InRecovery", maker.status)
        self.assertEqual(0.01, maker.filled_start_amount)

        self.assertAlmostEqual((0.01/4) * good_triangle["result"], maker.result_amount, 4)
        self.assertAlmostEqual((0.01 / 4) * good_triangle["result"] - 0.01, maker.gross_profit, 4)
コード例 #8
0
    def test_run_ok(self):

        ex = ccxtExchangeWrapper.load_from_id("binance")  # type: ccxtExchangeWrapper
        ex.set_offline_mode("test_data/markets.json", "test_data/tickers_maker.csv")
        ex.load_markets()
        ex.fetch_tickers()
        tickers = ex.fetch_tickers()  # second fetch contains good triangle

        om = ActionOrderManager(ex)

        good_triangle = self.good_triangle

        maker = SingleTriArbMakerDeal(currency1=good_triangle["cur1"],
                                      currency2=good_triangle["cur2"],
                                      currency3=good_triangle["cur3"],
                                      price1=good_triangle["leg1-price"],
                                      price2=good_triangle["leg2-price"],
                                      price3=good_triangle["leg3-price"],
                                      start_amount=0.01,
                                      min_amount_currency1=0.003,
                                      symbol1=good_triangle["symbol1"],
                                      symbol2=good_triangle["symbol2"],
                                      symbol3=good_triangle["symbol3"],
                                      commission=0.00075,
                                      commission_maker=0.0006,
                                      threshold=1.001,
                                      max_order1_updates=2000,
                                      max_order2_updates=2000,
                                      max_order3_updates=2000,
                                      )

        self.assertEqual("new", maker.state)

        maker.update_state(tickers)

        self.assertEqual("order1_create", maker.state)

        self.assertEqual("TRX/BTC", maker.order1.symbol)
        self.assertEqual(good_triangle["leg1-price"], maker.order1.price)
        self.assertEqual("buy", maker.order1.side)

        om.add_order(maker.order1)
        self.assertEqual("open", maker.order1.status)

        om.proceed_orders()
        self.assertEqual("open", maker.order1.status)

        maker.update_state(tickers)
        self.assertEqual("order1", maker.state)

        while len(om.get_open_orders()) > 0:

            # !!!!
            # first we proceed the orders and we update the deal manager with new updated orders
            om.proceed_orders()
            maker.update_state(tickers)

        # order1 is closed
        # second order creation phase
        self.assertEqual("order2_create", maker.state)

        # adding order2 and proceed
        om.add_order(maker.order2)
        om.proceed_orders()
        maker.update_state(tickers)

        self.assertEqual("order2", maker.state)

        while len(om.get_open_orders()) > 0:
            om.proceed_orders()
            maker.update_state(tickers)

        self.assertEqual("order3_create", maker.state)
        self.assertEqual(0, maker.leg2_recovery_amount)
        self.assertEqual(0, maker.leg2_recovery_target)

        om.add_order(maker.order3)
        om.proceed_orders()
        maker.update_state(tickers)

        self.assertEqual("order3", maker.state)

        while len(om.get_open_orders()) > 0:
            om.proceed_orders()
            maker.update_state(tickers)

        self.assertEqual("finished", maker.state)

        self.assertEqual(0, maker.leg3_recovery_amount)
        self.assertEqual(0, maker.leg3_recovery_target)

        self.assertEqual("OK", maker.status)

        self.assertEqual(0.01, maker.filled_start_amount)

        self.assertAlmostEqual((0.01) * good_triangle["result"], maker.result_amount, 4)
        self.assertAlmostEqual((0.01) * good_triangle["result"] - 0.01, maker.gross_profit, 4)