diff --git a/.coveralls.yml b/.coveralls.yml index 8426657a6..517a7c45f 100644 --- a/.coveralls.yml +++ b/.coveralls.yml @@ -1,3 +1,3 @@ service_name: travis-pro -repo_token: JQVHIBvfdoD1tVTbKPVz1zWPYF4dfR14i +repo_token: 1cFP5IeydNWl3yFJ9bj7cWegFHbkYdVZe parallel: true # if the CI is running your build in parallel \ No newline at end of file diff --git a/config/cst.py b/config/cst.py index ab9e6163c..1edae018a 100644 --- a/config/cst.py +++ b/config/cst.py @@ -1,7 +1,7 @@ from enum import Enum SHORT_VERSION = "0.1.0" -REV_VERSION = "1" +REV_VERSION = "2" VERSION_DEV_PHASE = "beta" VERSION = "{0}-{1}".format(SHORT_VERSION, VERSION_DEV_PHASE) LONG_VERSION = "{0}_{1}-{2}".format(SHORT_VERSION, REV_VERSION, VERSION_DEV_PHASE) @@ -45,6 +45,7 @@ CONFIG_SIMULATOR = "trader_simulator" CONFIG_STARTING_PORTFOLIO = "starting_portfolio" CONFIG_TRADER_RISK = "risk" +CONFIG_TRADER_MODE = "mode" CONFIG_TRADER_RISK_MIN = 0.05 CONFIG_TRADER_RISK_MAX = 1 ORDER_REFRESHER_TIME = 15 @@ -147,6 +148,15 @@ TENTACLE_DESCRIPTION_LOCALISATION = "localisation" TENTACLE_DESCRIPTION_IS_URL = "is_url" +TENTACLE_TYPES = {"Evaluator": "evaluator", + "Social": "Social", + "RealTime": "RealTime", + "Util": "Util", + "TA": "TA", + "Strategies": "Strategies", + "Trading": "trading", + "Mode": "trader/modes"} + class EvaluatorMatrixTypes(Enum): TA = "TA" @@ -205,7 +215,6 @@ class TimeFrames(Enum): MIN_EVAL_TIME_FRAME = TimeFrames.FIVE_MINUTES - TimeFramesMinutes = { TimeFrames.ONE_MINUTE: 1, TimeFrames.THREE_MINUTES: 3, diff --git a/config/default_config.json b/config/default_config.json index 314b6a51e..dbadda4a9 100644 --- a/config/default_config.json +++ b/config/default_config.json @@ -14,7 +14,9 @@ }, "trader":{ "enabled": false, - "risk": 0.5 + "risk": 0.5, + "reference_market": "BTC", + "mode": "DailyTradingMode" }, "trader_simulator":{ "enabled": true, diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 146a73527..6d5ecade0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,16 @@ +Changelog for 0.1.0_2-beta +==================== +*Released date : June 3 2018* + +**Info** : +- Config : "mode" key added to "trader" + +# Concerned issues : + #198 [Order Creation] Implement new architecture + +# New features : + - Trading modes + Changelog for 0.1.0_1-beta ==================== *Released date : June 2 2018* diff --git a/evaluator/evaluator_final.py b/evaluator/evaluator_final.py deleted file mode 100644 index 4db4598dc..000000000 --- a/evaluator/evaluator_final.py +++ /dev/null @@ -1,144 +0,0 @@ -import logging -from queue import Queue - -from config.cst import EvaluatorStates, INIT_EVAL_NOTE -from evaluator.evaluator_order_creator import EvaluatorOrderCreator -from tools.asynchronous_server import AsynchronousServer -from tools.notifications import EvaluatorNotification -from tools.evaluators_util import check_valid_eval_note - - -class FinalEvaluator(AsynchronousServer): - def __init__(self, symbol_evaluator, exchange, symbol): - super().__init__(self.finalize) - self.symbol_evaluator = symbol_evaluator - self.config = symbol_evaluator.get_config() - self.final_eval = INIT_EVAL_NOTE - self.state = None - self.keep_running = True - self.exchange = exchange - self.symbol = symbol - self.is_computing = False - self.logger = logging.getLogger(self.__class__.__name__) - - # If final_eval not is < X_THRESHOLD --> state = X - self.VERY_LONG_THRESHOLD = -0.95 - self.LONG_THRESHOLD = -0.25 - self.NEUTRAL_THRESHOLD = 0.25 - self.SHORT_THRESHOLD = 0.95 - self.RISK_THRESHOLD = 0.2 - - self.notifier = EvaluatorNotification(self.config) - self.queue = Queue() - - def _set_state(self, new_state): - if new_state != self.state: - # previous_state = self.state - self.state = new_state - self.logger.info("{0} ** NEW FINAL STATE ** : {1}".format(self.symbol, self.state)) - - # if new state is not neutral --> cancel orders and create new else keep orders - if new_state is not EvaluatorStates.NEUTRAL: - - # cancel open orders - if self.symbol_evaluator.get_trader(self.exchange).is_enabled(): - self.symbol_evaluator.get_trader(self.exchange).cancel_open_orders(self.symbol) - if self.symbol_evaluator.get_trader_simulator(self.exchange).is_enabled(): - self.symbol_evaluator.get_trader_simulator(self.exchange).cancel_open_orders(self.symbol) - - # create notification - evaluator_notification = None - if self.notifier.enabled(): - evaluator_notification = self.notifier.notify_state_changed( - self.final_eval, - self.symbol_evaluator.get_crypto_currency_evaluator(), - self.symbol_evaluator.get_symbol(), - self.symbol_evaluator.get_trader(self.exchange), - self.state, - self.symbol_evaluator.get_matrix(self.exchange).get_matrix()) - - # call orders creation method - self.create_final_state_orders(evaluator_notification) - - # create real and/or simulating orders in trader instances - def create_final_state_orders(self, evaluator_notification): - # create orders - - # simulated trader - self._create_order_if_possible(evaluator_notification, - self.symbol_evaluator.get_trader_simulator(self.exchange)) - - # real trader - self._create_order_if_possible(evaluator_notification, - self.symbol_evaluator.get_trader(self.exchange)) - - def _create_order_if_possible(self, evaluator_notification, trader): - if trader.is_enabled(): - with trader.get_portfolio() as pf: - if EvaluatorOrderCreator.can_create_order(self.symbol, self.exchange, self.state, pf): - FinalEvaluator._push_order_notification_if_possible( - self.symbol_evaluator.get_evaluator_order_creator().create_new_order( - self.final_eval, - self.symbol, - self.exchange, - trader, - pf, - self.state), - evaluator_notification) - - @staticmethod - def _push_order_notification_if_possible(order_list, notification): - if order_list: - for order in order_list: - order.get_order_notifier().notify(notification) - - def get_state(self): - return self.state - - def get_final_eval(self): - return self.final_eval - - def _prepare(self): - strategies_analysis_note_counter = 0 - # Strategies analysis - for evaluated_strategies in self.symbol_evaluator.get_strategies_eval_list(self.exchange): - strategy_eval = evaluated_strategies.get_eval_note() - if check_valid_eval_note(strategy_eval): - self.final_eval += strategy_eval * evaluated_strategies.get_pertinence() - strategies_analysis_note_counter += evaluated_strategies.get_pertinence() - - if strategies_analysis_note_counter > 0: - self.final_eval /= strategies_analysis_note_counter - else: - self.final_eval = INIT_EVAL_NOTE - - def _get_delta_risk(self): - return self.RISK_THRESHOLD * self.symbol_evaluator.get_trader(self.exchange).get_risk() - - def _create_state(self): - delta_risk = self._get_delta_risk() - - if self.final_eval < self.VERY_LONG_THRESHOLD + delta_risk: - self._set_state(EvaluatorStates.VERY_LONG) - - elif self.final_eval < self.LONG_THRESHOLD + delta_risk: - self._set_state(EvaluatorStates.LONG) - - elif self.final_eval < self.NEUTRAL_THRESHOLD - delta_risk: - self._set_state(EvaluatorStates.NEUTRAL) - - elif self.final_eval < self.SHORT_THRESHOLD - delta_risk: - self._set_state(EvaluatorStates.SHORT) - - else: - self._set_state(EvaluatorStates.VERY_SHORT) - - def finalize(self): - # reset previous note - self.final_eval = INIT_EVAL_NOTE - - self._prepare() - self._create_state() - - def stop(self): - self.keep_running = False diff --git a/evaluator/evaluator_order_creator.py b/evaluator/evaluator_order_creator.py deleted file mode 100644 index 37f04d0b4..000000000 --- a/evaluator/evaluator_order_creator.py +++ /dev/null @@ -1,350 +0,0 @@ -import logging -import math - -from config.cst import * -from config.cst import ExchangeConstantsMarketStatusColumns as Ecmsc -from tools.symbol_util import split_symbol - - -class EvaluatorOrderCreator: - def __init__(self): - self.MAX_SUM_RESULT = 2 - - self.STOP_LOSS_ORDER_MAX_PERCENT = 0.99 - self.STOP_LOSS_ORDER_MIN_PERCENT = 0.95 - self.STOP_LOSS_ORDER_ATTENUATION = (self.STOP_LOSS_ORDER_MAX_PERCENT - self.STOP_LOSS_ORDER_MIN_PERCENT) - - self.QUANTITY_MIN_PERCENT = 0.1 - self.QUANTITY_MAX_PERCENT = 0.9 - self.QUANTITY_ATTENUATION = (self.QUANTITY_MAX_PERCENT - self.QUANTITY_MIN_PERCENT) / self.MAX_SUM_RESULT - - self.QUANTITY_MARKET_MIN_PERCENT = 0.5 - self.QUANTITY_MARKET_MAX_PERCENT = 1 - self.QUANTITY_BUY_MARKET_ATTENUATION = 0.2 - self.QUANTITY_MARKET_ATTENUATION = (self.QUANTITY_MARKET_MAX_PERCENT - self.QUANTITY_MARKET_MIN_PERCENT) \ - / self.MAX_SUM_RESULT - - self.BUY_LIMIT_ORDER_MAX_PERCENT = 0.995 - self.BUY_LIMIT_ORDER_MIN_PERCENT = 0.98 - self.SELL_LIMIT_ORDER_MIN_PERCENT = 1 + (1 - self.BUY_LIMIT_ORDER_MAX_PERCENT) - self.SELL_LIMIT_ORDER_MAX_PERCENT = 1 + (1 - self.BUY_LIMIT_ORDER_MIN_PERCENT) - self.LIMIT_ORDER_ATTENUATION = (self.BUY_LIMIT_ORDER_MAX_PERCENT - self.BUY_LIMIT_ORDER_MIN_PERCENT)\ - / self.MAX_SUM_RESULT - - @staticmethod - def can_create_order(symbol, exchange, state, portfolio): - currency, market = split_symbol(symbol) - - # get symbol min amount when creating order - symbol_limit = exchange.get_market_status(symbol)[Ecmsc.LIMITS.value] - symbol_min_amount = symbol_limit[Ecmsc.LIMITS_AMOUNT.value][Ecmsc.LIMITS_AMOUNT_MIN.value] - order_min_amount = symbol_limit[Ecmsc.LIMITS_COST.value][Ecmsc.LIMITS_COST_MIN.value] - - # short cases => sell => need this currency - if state == EvaluatorStates.VERY_SHORT or state == EvaluatorStates.SHORT: - return portfolio.get_currency_portfolio(currency) > symbol_min_amount - - # long cases => buy => need money(aka other currency in the pair) to buy this currency - elif state == EvaluatorStates.LONG or state == EvaluatorStates.VERY_LONG: - return portfolio.get_currency_portfolio(market) > order_min_amount - - # other cases like neutral state or unfulfilled previous conditions - return False - - # creates a new order (or multiple split orders), always check EvaluatorOrderCreator.can_create_order() first. - def create_new_order(self, eval_note, symbol, exchange, trader, portfolio, state): - try: - last_prices = exchange.get_recent_trades(symbol) - reference_sum = 0 - - for last_price in last_prices[-ORDER_CREATION_LAST_TRADES_TO_USE:]: - reference_sum += float(last_price["price"]) - - reference = reference_sum / ORDER_CREATION_LAST_TRADES_TO_USE - - currency, market = split_symbol(symbol) - - current_portfolio = portfolio.get_currency_portfolio(currency) - current_market_quantity = portfolio.get_currency_portfolio(market) - - market_quantity = current_market_quantity / reference - - price = reference - symbol_market = exchange.get_market_status(symbol) - - created_orders = [] - - if state == EvaluatorStates.VERY_SHORT: - quantity = self._get_market_quantity_from_risk(eval_note, - trader, - current_portfolio) - for order_quantity, order_price in self._check_and_adapt_order_details_if_necessary(quantity, price, - symbol_market): - market = trader.create_order_instance(order_type=TraderOrderType.SELL_MARKET, - symbol=symbol, - current_price=order_price, - quantity=order_quantity, - price=order_price) - trader.create_order(market, portfolio) - created_orders.append(market) - return created_orders - - elif state == EvaluatorStates.SHORT: - quantity = self._get_limit_quantity_from_risk(eval_note, - trader, - current_portfolio) - limit_price = EvaluatorOrderCreator\ - ._adapt_price(symbol_market, price * self._get_limit_price_from_risk(eval_note, trader)) - stop_price = EvaluatorOrderCreator \ - ._adapt_price(symbol_market, price * self._get_stop_price_from_risk(trader)) - for order_quantity, order_price in self._check_and_adapt_order_details_if_necessary(quantity, - limit_price, - symbol_market): - limit = trader.create_order_instance(order_type=TraderOrderType.SELL_LIMIT, - symbol=symbol, - current_price=price, - quantity=order_quantity, - price=order_price) - updated_limit = trader.create_order(limit, portfolio) - created_orders.append(updated_limit) - - stop = trader.create_order_instance(order_type=TraderOrderType.STOP_LOSS, - symbol=symbol, - current_price=price, - quantity=order_quantity, - price=stop_price, - linked_to=updated_limit) - trader.create_order(stop, portfolio) - return created_orders - - elif state == EvaluatorStates.NEUTRAL: - pass - - # TODO : stop loss - elif state == EvaluatorStates.LONG: - quantity = self._get_limit_quantity_from_risk(eval_note, - trader, - market_quantity) - limit_price = EvaluatorOrderCreator\ - ._adapt_price(symbol_market, price * self._get_limit_price_from_risk(eval_note, trader)) - for order_quantity, order_price in self._check_and_adapt_order_details_if_necessary(quantity, - limit_price, - symbol_market): - limit = trader.create_order_instance(order_type=TraderOrderType.BUY_LIMIT, - symbol=symbol, - current_price=price, - quantity=order_quantity, - price=order_price) - trader.create_order(limit, portfolio) - created_orders.append(limit) - return created_orders - - elif state == EvaluatorStates.VERY_LONG: - quantity = self._get_market_quantity_from_risk(eval_note, - trader, - market_quantity, - True) - for order_quantity, order_price in self._check_and_adapt_order_details_if_necessary(quantity, price, - symbol_market): - market = trader.create_order_instance(order_type=TraderOrderType.BUY_MARKET, - symbol=symbol, - current_price=order_price, - quantity=order_quantity, - price=order_price) - trader.create_order(market, portfolio) - created_orders.append(market) - return created_orders - - # if nothing go returned, return None - return None - - except Exception as e: - logging.getLogger(self.__class__.__name__).error("Failed to create order : {0}".format(e)) - return None - - @staticmethod - def _check_factor(min_val, max_val, factor): - if factor > max_val: - return max_val - elif factor < min_val: - return min_val - else: - return factor - - """ - Starting point : self.SELL_LIMIT_ORDER_MIN_PERCENT or self.BUY_LIMIT_ORDER_MAX_PERCENT - 1 - abs(eval_note) --> confirmation level --> high : sell less expensive / buy more expensive - 1 - trader.get_risk() --> high risk : sell / buy closer to the current price - 1 - abs(eval_note) + 1 - trader.get_risk() --> result between 0 and 2 --> self.MAX_SUM_RESULT - self.QUANTITY_ATTENUATION --> try to contains the result between self.XXX_MIN_PERCENT and self.XXX_MAX_PERCENT - """ - - def _get_limit_price_from_risk(self, eval_note, trader): - if eval_note > 0: - factor = self.SELL_LIMIT_ORDER_MIN_PERCENT + \ - ((1 - abs(eval_note) + 1 - trader.get_risk()) * self.LIMIT_ORDER_ATTENUATION) - return EvaluatorOrderCreator._check_factor(self.SELL_LIMIT_ORDER_MIN_PERCENT, - self.SELL_LIMIT_ORDER_MAX_PERCENT, - factor) - else: - factor = self.BUY_LIMIT_ORDER_MAX_PERCENT - \ - ((1 - abs(eval_note) + 1 - trader.get_risk()) * self.LIMIT_ORDER_ATTENUATION) - return EvaluatorOrderCreator._check_factor(self.BUY_LIMIT_ORDER_MIN_PERCENT, - self.BUY_LIMIT_ORDER_MAX_PERCENT, - factor) - - """ - Starting point : self.STOP_LOSS_ORDER_MAX_PERCENT - trader.get_risk() --> low risk : stop level close to the current price - self.STOP_LOSS_ORDER_ATTENUATION --> try to contains the result between self.STOP_LOSS_ORDER_MIN_PERCENT and self.STOP_LOSS_ORDER_MAX_PERCENT - """ - - def _get_stop_price_from_risk(self, trader): - factor = self.STOP_LOSS_ORDER_MAX_PERCENT - (trader.get_risk() * self.STOP_LOSS_ORDER_ATTENUATION) - return EvaluatorOrderCreator._check_factor(self.STOP_LOSS_ORDER_MIN_PERCENT, - self.STOP_LOSS_ORDER_MAX_PERCENT, - factor) - - """ - Starting point : self.QUANTITY_MIN_PERCENT - abs(eval_note) --> confirmation level --> high : sell/buy more quantity - trader.get_risk() --> high risk : sell / buy more quantity - abs(eval_note) + trader.get_risk() --> result between 0 and 2 --> self.MAX_SUM_RESULT - self.QUANTITY_ATTENUATION --> try to contains the result between self.QUANTITY_MIN_PERCENT and self.QUANTITY_MAX_PERCENT - """ - - def _get_limit_quantity_from_risk(self, eval_note, trader, quantity): - factor = self.QUANTITY_MIN_PERCENT + ((abs(eval_note) + trader.get_risk()) * self.QUANTITY_ATTENUATION) - return EvaluatorOrderCreator._check_factor(self.QUANTITY_MIN_PERCENT, - self.QUANTITY_MAX_PERCENT, - factor) * quantity - - """ - Starting point : self.QUANTITY_MARKET_MIN_PERCENT - abs(eval_note) --> confirmation level --> high : sell/buy more quantity - trader.get_risk() --> high risk : sell / buy more quantity - abs(eval_note) + trader.get_risk() --> result between 0 and 2 --> self.MAX_SUM_RESULT - self.QUANTITY_MARKET_ATTENUATION --> try to contains the result between self.QUANTITY_MARKET_MIN_PERCENT and self.QUANTITY_MARKET_MAX_PERCENT - """ - - def _get_market_quantity_from_risk(self, eval_note, trader, quantity, buy=False): - factor = self.QUANTITY_MARKET_MIN_PERCENT + ( - (abs(eval_note) + trader.get_risk()) * self.QUANTITY_MARKET_ATTENUATION) - - # if buy market --> limit market usage - if buy: - factor *= self.QUANTITY_BUY_MARKET_ATTENUATION - - return EvaluatorOrderCreator._check_factor(self.QUANTITY_MARKET_MIN_PERCENT, - self.QUANTITY_MARKET_MAX_PERCENT, - factor) * quantity - - @staticmethod - def _trunc_with_n_decimal_digits(value, digits): - # force exact representation - return float("{0:.{1}f}".format(math.trunc(value*10**digits)/(10**digits), digits)) - - @staticmethod - def _get_value_or_default(dictionary, key, default=math.nan): - if key in dictionary: - value = dictionary[key] - return value if value is not None else default - return default - - @staticmethod - def _adapt_order_quantity_because_quantity(limiting_value, max_value, quantity_to_adapt, price, symbol_market): - orders = [] - nb_full_orders = limiting_value // max_value - rest_order_quantity = limiting_value % max_value - after_rest_quantity_to_adapt = quantity_to_adapt - if rest_order_quantity > 0: - after_rest_quantity_to_adapt -= rest_order_quantity - valid_last_order_quantity = EvaluatorOrderCreator._adapt_quantity(symbol_market, rest_order_quantity) - orders.append((valid_last_order_quantity, price)) - - other_orders_quantity = (after_rest_quantity_to_adapt + max_value)/(nb_full_orders+1) - valid_other_orders_quantity = EvaluatorOrderCreator._adapt_quantity(symbol_market, other_orders_quantity) - orders += [(valid_other_orders_quantity, price)]*int(nb_full_orders) - return orders - - @staticmethod - def _adapt_order_quantity_because_price(limiting_value, max_value, price, symbol_market): - orders = [] - nb_full_orders = limiting_value // max_value - rest_order_cost = limiting_value % max_value - if rest_order_cost > 0: - valid_last_order_quantity = EvaluatorOrderCreator._adapt_quantity(symbol_market, rest_order_cost/price) - orders.append((valid_last_order_quantity, price)) - - other_orders_quantity = max_value / price - valid_other_orders_quantity = EvaluatorOrderCreator._adapt_quantity(symbol_market, other_orders_quantity) - orders += [(valid_other_orders_quantity, price)] * int(nb_full_orders) - return orders - - @staticmethod - def _adapt_price(symbol_market, price): - maximal_price_digits = EvaluatorOrderCreator._get_value_or_default(symbol_market[Ecmsc.PRECISION.value], - Ecmsc.PRECISION_PRICE.value, - CURRENCY_DEFAULT_MAX_PRICE_DIGITS) - return EvaluatorOrderCreator._trunc_with_n_decimal_digits(price, maximal_price_digits) - - @staticmethod - def _adapt_quantity(symbol_market, quantity): - maximal_volume_digits = EvaluatorOrderCreator._get_value_or_default(symbol_market[Ecmsc.PRECISION.value], - Ecmsc.PRECISION_AMOUNT.value, 0) - return EvaluatorOrderCreator._trunc_with_n_decimal_digits(quantity, maximal_volume_digits) - - """ - Checks and adapts the quantity and price of the order to ensure it's exchange compliant: - - are the quantity and price of the order compliant with the exchange's number of digits requirement - => otherwise quantity and price will be truncated accordingly - - is the price of the currency compliant with the exchange's price interval for this currency - => otherwise order is impossible => returns empty list - - are the order total price and quantity superior or equal to the exchange's minimum order requirement - => otherwise order is impossible => returns empty list - - are the order total price and quantity inferior or equal to the exchange's maximum order requirement - => otherwise order is impossible as is => split order into smaller ones and returns the list - => returns the quantity and price list of possible order(s) - """ - - @staticmethod - def _check_and_adapt_order_details_if_necessary(quantity, price, symbol_market): - symbol_market_limits = symbol_market[Ecmsc.LIMITS.value] - - limit_amount = symbol_market_limits[Ecmsc.LIMITS_AMOUNT.value] - limit_cost = symbol_market_limits[Ecmsc.LIMITS_COST.value] - limit_price = symbol_market_limits[Ecmsc.LIMITS_PRICE.value] - - min_quantity = EvaluatorOrderCreator._get_value_or_default(limit_amount, Ecmsc.LIMITS_AMOUNT_MIN.value) - max_quantity = EvaluatorOrderCreator._get_value_or_default(limit_amount, Ecmsc.LIMITS_AMOUNT_MAX.value) - min_cost = EvaluatorOrderCreator._get_value_or_default(limit_cost, Ecmsc.LIMITS_COST_MIN.value) - max_cost = EvaluatorOrderCreator._get_value_or_default(limit_cost, Ecmsc.LIMITS_COST_MAX.value) - min_price = EvaluatorOrderCreator._get_value_or_default(limit_price, Ecmsc.LIMITS_PRICE_MIN.value) - max_price = EvaluatorOrderCreator._get_value_or_default(limit_price, Ecmsc.LIMITS_PRICE_MAX.value) - - # adapt digits if necessary - valid_quantity = EvaluatorOrderCreator._adapt_quantity(symbol_market, quantity) - valid_price = EvaluatorOrderCreator._adapt_price(symbol_market, price) - - total_order_price = valid_quantity * valid_price - - # check total_order_price not < min_cost and valid_quantity not < min_quantity and max_price > price > min_price - if total_order_price < min_cost or valid_quantity < min_quantity or not (max_price > valid_price > min_price): - # invalid order - return [] - - # check total_order_price not > max_cost and valid_quantity not > max_quantity - elif total_order_price > max_cost or valid_quantity > max_quantity: - # split quantity into smaller orders - nb_orders_according_to_cost = total_order_price / max_cost - nb_orders_according_to_quantity = valid_quantity / max_quantity - if nb_orders_according_to_cost > nb_orders_according_to_quantity: - return EvaluatorOrderCreator._adapt_order_quantity_because_price(total_order_price, max_cost,price, - symbol_market) - else: - return EvaluatorOrderCreator._adapt_order_quantity_because_quantity(valid_quantity, max_quantity, - quantity, price, symbol_market) - - else: - # valid order that can be handled wy the exchange - return [(valid_quantity, valid_price)] diff --git a/evaluator/symbol_evaluator.py b/evaluator/symbol_evaluator.py index 88e735186..b7b9d997c 100644 --- a/evaluator/symbol_evaluator.py +++ b/evaluator/symbol_evaluator.py @@ -1,8 +1,10 @@ -from config.cst import EvaluatorMatrixTypes +import inspect +import logging + +from config.cst import EvaluatorMatrixTypes, CONFIG_TRADER_MODE, CONFIG_TRADER from evaluator.evaluator_creator import EvaluatorCreator -from evaluator.evaluator_final import FinalEvaluator from evaluator.evaluator_matrix import EvaluatorMatrix -from evaluator.evaluator_order_creator import EvaluatorOrderCreator +from trading.trader import modes class SymbolEvaluator: @@ -13,14 +15,15 @@ def __init__(self, config, symbol, crypto_currency_evaluator): self.config = config self.traders = None self.trader_simulators = None + self.logger = logging.getLogger("{0} {1}".format(self.symbol, self.__class__.__name__)) self.evaluator_thread_managers = {} - self.final_evaluators = {} + self.trading_mode_instances = {} self.matrices = {} self.strategies_eval_lists = {} self.finalize_enabled_list = {} - self.evaluator_order_creator = EvaluatorOrderCreator() + self.trading_mode_class = self.get_trading_mode_class() def set_traders(self, trader): self.traders = trader @@ -28,12 +31,21 @@ def set_traders(self, trader): def set_trader_simulators(self, simulator): self.trader_simulators = simulator + def get_trading_mode_class(self): + if CONFIG_TRADER in self.config and CONFIG_TRADER_MODE in self.config[CONFIG_TRADER]: + if any(m[0] == self.config[CONFIG_TRADER][CONFIG_TRADER_MODE] for m in inspect.getmembers(modes)): + return getattr(modes, self.config[CONFIG_TRADER][CONFIG_TRADER_MODE]) + + raise Exception("Please specify a valid trading mode in your config file (trader -> mode)") + def add_evaluator_thread_manager(self, exchange, symbol, time_frame, evaluator_thread): if exchange.get_name() in self.evaluator_thread_managers: self.evaluator_thread_managers[exchange.get_name()][time_frame] = evaluator_thread else: self.evaluator_thread_managers[exchange.get_name()] = {time_frame: evaluator_thread} - self.final_evaluators[exchange.get_name()] = FinalEvaluator(self, exchange, symbol) + self.trading_mode_instances[exchange.get_name()] = self.trading_mode_class(self.config, self, exchange, + symbol) + self.matrices[exchange.get_name()] = EvaluatorMatrix(self.config) self.strategies_eval_lists[exchange.get_name()] = EvaluatorCreator.create_strategies_eval_list(self.config) self.finalize_enabled_list[exchange.get_name()] = False @@ -52,7 +64,7 @@ def finalize(self, exchange): self._check_finalize(exchange) if self.finalize_enabled_list[exchange.get_name()]: - self.final_evaluators[exchange.get_name()].add_to_queue() + self.trading_mode_instances[exchange.get_name()].get_decider().add_to_queue() def _check_finalize(self, exchange): self.finalize_enabled_list[exchange.get_name()] = True @@ -67,10 +79,10 @@ def get_trader_simulator(self, exchange): return self.trader_simulators[exchange.get_name()] def get_final(self, exchange): - return self.final_evaluators[exchange.get_name()] + return self.trading_mode_instances[exchange.get_name()].get_decider() def has_exchange(self, exchange): - return exchange.get_name() in self.final_evaluators + return exchange.get_name() in self.trading_mode_instances def get_matrix(self, exchange): return self.matrices[exchange.get_name()] @@ -84,8 +96,8 @@ def get_config(self): def get_strategies_eval_list(self, exchange): return self.strategies_eval_lists[exchange.get_name()] - def get_evaluator_order_creator(self): - return self.evaluator_order_creator + def get_evaluator_order_creator(self, exchange): + return self.trading_mode_instances[exchange.get_name()].get_creator() def get_symbol(self): return self.symbol diff --git a/tests/static/config.json b/tests/static/config.json index a7e1d8087..4b7f0ae34 100644 --- a/tests/static/config.json +++ b/tests/static/config.json @@ -42,7 +42,8 @@ }, "trader":{ "enabled": false, - "risk": 1 + "risk": 1, + "mode": "DailyTradingMode" }, "trader_simulator":{ "enabled": true, diff --git a/tests/unit_tests/evaluator_tests/test_symbol_evaluator.py b/tests/unit_tests/evaluator_tests/test_symbol_evaluator.py index f1b8b264f..4b5e6221f 100644 --- a/tests/unit_tests/evaluator_tests/test_symbol_evaluator.py +++ b/tests/unit_tests/evaluator_tests/test_symbol_evaluator.py @@ -47,5 +47,5 @@ def _get_tools(): def test_init(): symbol_evaluator, exchange_inst, time_frame, evaluator_thread_manager = _get_tools() - assert symbol_evaluator.evaluator_order_creator + assert symbol_evaluator.trading_mode_class assert symbol_evaluator.evaluator_thread_managers[exchange_inst.get_name()][time_frame] == evaluator_thread_manager diff --git a/tests/unit_tests/trading_modes_tests/__init__.py b/tests/unit_tests/trading_modes_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit_tests/evaluator_tests/test_evaluator_order_creator.py b/tests/unit_tests/trading_modes_tests/test_daily_trading_mode_creator.py similarity index 84% rename from tests/unit_tests/evaluator_tests/test_evaluator_order_creator.py rename to tests/unit_tests/trading_modes_tests/test_daily_trading_mode_creator.py index 46a91ad22..e9079468e 100644 --- a/tests/unit_tests/evaluator_tests/test_evaluator_order_creator.py +++ b/tests/unit_tests/trading_modes_tests/test_daily_trading_mode_creator.py @@ -1,10 +1,10 @@ import ccxt import copy -from evaluator.evaluator_order_creator import EvaluatorOrderCreator from trading.exchanges.exchange_manager import ExchangeManager from config.cst import EvaluatorStates from tests.test_utils.config import load_test_config +from trading.trader.modes import DailyTradingModeCreator from trading.trader.portfolio import Portfolio from trading.trader.order import * from trading.trader.trader_simulator import TraderSimulator @@ -43,31 +43,44 @@ def test_can_create_order(): min_trigger_market = "ADA/BNB" # order from neutral state => false - assert not EvaluatorOrderCreator.can_create_order(symbol, exchange, EvaluatorStates.NEUTRAL, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(symbol, exchange, + EvaluatorStates.NEUTRAL, portfolio) # sell order using a currency with 0 available - assert not EvaluatorOrderCreator.can_create_order(not_owned_symbol, exchange, EvaluatorStates.SHORT, portfolio) - assert not EvaluatorOrderCreator.can_create_order(not_owned_symbol, exchange, EvaluatorStates.VERY_SHORT, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(not_owned_symbol, exchange, + EvaluatorStates.SHORT, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(not_owned_symbol, exchange, + EvaluatorStates.VERY_SHORT, portfolio) # sell order using a currency with < min available - assert not EvaluatorOrderCreator.can_create_order(min_trigger_symbol, exchange, EvaluatorStates.SHORT, portfolio) - assert not EvaluatorOrderCreator.can_create_order(min_trigger_symbol, exchange, EvaluatorStates.VERY_SHORT, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(min_trigger_symbol, exchange, + EvaluatorStates.SHORT, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(min_trigger_symbol, exchange, + EvaluatorStates.VERY_SHORT, portfolio) # sell order using a currency with > min available - assert EvaluatorOrderCreator.can_create_order(not_owned_market, exchange, EvaluatorStates.SHORT, portfolio) - assert EvaluatorOrderCreator.can_create_order(not_owned_market, exchange, EvaluatorStates.VERY_SHORT, portfolio) + assert DailyTradingModeCreator(None).can_create_order(not_owned_market, exchange, + EvaluatorStates.SHORT, portfolio) + assert DailyTradingModeCreator(None).can_create_order(not_owned_market, exchange, + EvaluatorStates.VERY_SHORT, portfolio) # buy order using a market with 0 available - assert not EvaluatorOrderCreator.can_create_order(not_owned_market, exchange, EvaluatorStates.LONG, portfolio) - assert not EvaluatorOrderCreator.can_create_order(not_owned_market, exchange, EvaluatorStates.VERY_LONG, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(not_owned_market, exchange, + EvaluatorStates.LONG, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(not_owned_market, exchange, + EvaluatorStates.VERY_LONG, portfolio) # buy order using a market with < min available - assert not EvaluatorOrderCreator.can_create_order(min_trigger_market, exchange, EvaluatorStates.LONG, portfolio) - assert not EvaluatorOrderCreator.can_create_order(min_trigger_market, exchange, EvaluatorStates.VERY_LONG, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(min_trigger_market, exchange, + EvaluatorStates.LONG, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(min_trigger_market, exchange, + EvaluatorStates.VERY_LONG, portfolio) # buy order using a market with > min available - assert EvaluatorOrderCreator.can_create_order(not_owned_symbol, exchange, EvaluatorStates.LONG, portfolio) - assert EvaluatorOrderCreator.can_create_order(not_owned_symbol, exchange, EvaluatorStates.VERY_LONG, portfolio) + assert DailyTradingModeCreator(None).can_create_order(not_owned_symbol, exchange, + EvaluatorStates.LONG, portfolio) + assert DailyTradingModeCreator(None).can_create_order(not_owned_symbol, exchange, + EvaluatorStates.VERY_LONG, portfolio) def test_can_create_order_unknown_symbols(): @@ -78,21 +91,32 @@ def test_can_create_order_unknown_symbols(): unknown_everything = "VI?/*s?" # buy order with unknown market - assert not EvaluatorOrderCreator.can_create_order(unknown_market, exchange, EvaluatorStates.LONG, portfolio) - assert not EvaluatorOrderCreator.can_create_order(unknown_market, exchange, EvaluatorStates.VERY_LONG, portfolio) - assert EvaluatorOrderCreator.can_create_order(unknown_market, exchange, EvaluatorStates.SHORT, portfolio) - assert EvaluatorOrderCreator.can_create_order(unknown_market, exchange, EvaluatorStates.VERY_SHORT, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(unknown_market, exchange, + EvaluatorStates.LONG, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(unknown_market, exchange, + EvaluatorStates.VERY_LONG, portfolio) + assert DailyTradingModeCreator(None).can_create_order(unknown_market, exchange, + EvaluatorStates.SHORT, portfolio) + assert DailyTradingModeCreator(None).can_create_order(unknown_market, exchange, + EvaluatorStates.VERY_SHORT, portfolio) # sell order with unknown symbol - assert not EvaluatorOrderCreator.can_create_order(unknown_symbol, exchange, EvaluatorStates.SHORT, portfolio) - assert not EvaluatorOrderCreator.can_create_order(unknown_symbol, exchange, EvaluatorStates.VERY_SHORT, portfolio) - assert EvaluatorOrderCreator.can_create_order(unknown_symbol, exchange, EvaluatorStates.LONG, portfolio) - assert EvaluatorOrderCreator.can_create_order(unknown_symbol, exchange, EvaluatorStates.VERY_LONG, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(unknown_symbol, exchange, + EvaluatorStates.SHORT, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(unknown_symbol, exchange, + EvaluatorStates.VERY_SHORT, portfolio) + assert DailyTradingModeCreator(None).can_create_order(unknown_symbol, exchange, + EvaluatorStates.LONG, portfolio) + assert DailyTradingModeCreator(None).can_create_order(unknown_symbol, exchange, + EvaluatorStates.VERY_LONG, portfolio) # neutral state with unknown symbol, market and everything - assert not EvaluatorOrderCreator.can_create_order(unknown_symbol, exchange, EvaluatorStates.NEUTRAL, portfolio) - assert not EvaluatorOrderCreator.can_create_order(unknown_market, exchange, EvaluatorStates.NEUTRAL, portfolio) - assert not EvaluatorOrderCreator.can_create_order(unknown_everything, exchange, EvaluatorStates.NEUTRAL, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(unknown_symbol, exchange, + EvaluatorStates.NEUTRAL, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(unknown_market, exchange, + EvaluatorStates.NEUTRAL, portfolio) + assert not DailyTradingModeCreator(None).can_create_order(unknown_everything, exchange, + EvaluatorStates.NEUTRAL, portfolio) def _check_order_limits(order, market_status): @@ -144,7 +168,7 @@ def _check_linked_order(order, linked_order, order_type, order_price, market_sta def test_valid_create_new_order(): config, exchange, trader, symbol = _get_tools() portfolio = trader.get_portfolio() - order_creator = EvaluatorOrderCreator() + order_creator = DailyTradingModeCreator(None) market_status = exchange.get_market_status(symbol) @@ -263,7 +287,7 @@ def test_valid_create_new_order(): def test_invalid_create_new_order(): config, exchange, trader, symbol = _get_tools() portfolio = trader.get_portfolio() - order_creator = EvaluatorOrderCreator() + order_creator = DailyTradingModeCreator(None) # portfolio: "BTC": 10 "USD": 1000 min_trigger_market = "ADA/BNB" @@ -294,7 +318,7 @@ def test_invalid_create_new_order(): def test_split_create_new_order(): config, exchange, trader, symbol = _get_tools() portfolio = trader.get_portfolio() - order_creator = EvaluatorOrderCreator() + order_creator = DailyTradingModeCreator(None) last_btc_price = 6943.01 market_status = exchange.get_market_status(symbol) @@ -513,7 +537,7 @@ def _check_portfolio(portfolio, initial_portfolio, orders, only_positivity=False def test_create_order_using_a_lot_of_different_inputs_with_portfolio_reset(): config, exchange, trader, symbol = _get_tools() portfolio = trader.get_portfolio() - order_creator = EvaluatorOrderCreator() + order_creator = DailyTradingModeCreator(None) gradient_step = 0.001 nb_orders = 1 market_status = exchange.get_market_status(symbol) @@ -569,7 +593,7 @@ def _fill_orders(orders, trader): def test_create_order_using_a_lot_of_different_inputs_without_portfolio_reset(): config, exchange, trader, symbol = _get_tools() portfolio = trader.get_portfolio() - order_creator = EvaluatorOrderCreator() + order_creator = DailyTradingModeCreator(None) gradient_step = 0.001 nb_orders = "unknown" market_status = exchange.get_market_status(symbol) diff --git a/tests/unit_tests/evaluator_tests/test_evaluator_final.py b/tests/unit_tests/trading_modes_tests/test_daily_trading_mode_decider.py similarity index 92% rename from tests/unit_tests/evaluator_tests/test_evaluator_final.py rename to tests/unit_tests/trading_modes_tests/test_daily_trading_mode_decider.py index 4c06fde0e..3989030f2 100644 --- a/tests/unit_tests/evaluator_tests/test_evaluator_final.py +++ b/tests/unit_tests/trading_modes_tests/test_daily_trading_mode_decider.py @@ -2,9 +2,10 @@ from trading.exchanges.exchange_manager import ExchangeManager from evaluator.symbol_evaluator import SymbolEvaluator +from trading.trader.modes import DailyTradingModeDecider, DailyTradingMode from trading.trader.trader_simulator import TraderSimulator from evaluator.cryptocurrency_evaluator import CryptocurrencyEvaluator -from evaluator.evaluator_final import FinalEvaluator +from trading.trader.modes.abstract_mode_decider import AbstractTradingModeDecider from evaluator.evaluator_creator import EvaluatorCreator from tests.test_utils.config import load_test_config from evaluator.Util.advanced_manager import AdvancedManager @@ -31,7 +32,11 @@ def _get_tools(): symbol_evaluator.set_trader_simulators(exchange_traders) symbol_evaluator.set_traders(exchange_traders2) symbol_evaluator.strategies_eval_lists[exchange_inst.get_name()] = EvaluatorCreator.create_strategies_eval_list(config) - final_evaluator = FinalEvaluator(symbol_evaluator, exchange_inst, symbol) + + trading_mode = DailyTradingMode(config, symbol_evaluator, exchange_inst, symbol) + final_evaluator = trading_mode.get_decider() + symbol_evaluator.trading_mode_instances[exchange_inst.get_name()] = trading_mode + trader_inst.portfolio.portfolio["USDT"] = { Portfolio.TOTAL: 2000, Portfolio.AVAILABLE: 2000 @@ -85,7 +90,7 @@ def test_create_state(): delta_risk = final_evaluator._get_delta_risk() for i in range(-100, 100, 1): final_evaluator.final_eval = i/100 - final_evaluator._create_state() + final_evaluator.create_state() if final_evaluator.final_eval < final_evaluator.VERY_LONG_THRESHOLD + delta_risk: assert final_evaluator.state == EvaluatorStates.VERY_LONG elif final_evaluator.final_eval < final_evaluator.LONG_THRESHOLD + delta_risk: @@ -104,7 +109,7 @@ def test_prepare(): assert final_evaluator.state == EvaluatorStates.SHORT assert len(trader_inst.order_manager.order_list) == 2 # has stop loss final_evaluator.final_eval = None - final_evaluator._prepare() + final_evaluator.set_final_eval() assert final_evaluator.state == EvaluatorStates.SHORT # ensure did not change EvaluatorStates assert len(trader_inst.order_manager.order_list) == 2 # ensure did not change orders assert final_evaluator.final_eval == INIT_EVAL_NOTE diff --git a/tools/tentacle_manager.py b/tools/tentacle_manager.py index 367ddd8bd..7c1ff4d9c 100644 --- a/tools/tentacle_manager.py +++ b/tools/tentacle_manager.py @@ -4,9 +4,10 @@ import requests -from config.cst import TENTACLES_PUBLIC_LIST, TENTACLES_DEFAULT_BRANCH, TENTACLES_PUBLIC_REPOSITORY, TENTACLE_DESCRIPTION,\ +from config.cst import TENTACLES_PUBLIC_LIST, TENTACLES_DEFAULT_BRANCH, TENTACLES_PUBLIC_REPOSITORY, \ + TENTACLE_DESCRIPTION, \ GITHUB_RAW_CONTENT_URL, CONFIG_EVALUATOR, EVALUATOR_DEFAULT_FOLDER, CONFIG_TENTACLES_KEY, GITHUB_BASE_URL, GITHUB, \ - TENTACLE_DESCRIPTION_LOCALISATION, TENTACLE_DESCRIPTION_IS_URL, EVALUATOR_ADVANCED_FOLDER + TENTACLE_DESCRIPTION_LOCALISATION, TENTACLE_DESCRIPTION_IS_URL, EVALUATOR_ADVANCED_FOLDER, TENTACLE_TYPES class TentacleManager: @@ -52,7 +53,7 @@ def _get_package_description(url_or_path, try_to_adapt=False): package_url_or_path = str(url_or_path) # if its an url: download with requests.get and return text if package_url_or_path.startswith("https://") \ - or package_url_or_path.startswith("http://") \ + or package_url_or_path.startswith("http://") \ or package_url_or_path.startswith("ftp://"): if try_to_adapt: if not package_url_or_path.endswith("/"): @@ -92,34 +93,59 @@ def _get_package_from_url(url): return package_file - def _apply_module(self, module_type, module_name, module_version, module_file, target_folder): - file_dir = "{0}/{1}/{2}".format(CONFIG_EVALUATOR, module_type, target_folder) + def _apply_module(self, module_type, module_subtype, module_version, module_file, target_folder, module_name): + if module_subtype in TENTACLE_TYPES and (module_subtype or module_subtype in TENTACLE_TYPES): + # create path from types + if module_subtype: + file_dir = "{0}/{1}/{2}".format(TENTACLE_TYPES[module_type], + TENTACLE_TYPES[module_subtype], + target_folder) + else: + file_dir = "{0}/{1}".format(TENTACLE_TYPES[module_type], + target_folder) + + # Install package in evaluator + with open("{0}/{1}.py".format(file_dir, module_name), "w") as installed_package: + installed_package.write(module_file) + + # Update local __init__ + new_line_in_init = "from .{0} import *\n".format(module_name) + init_content = "" + init_file = "{0}/{1}/{2}/__init__.py".format(TENTACLE_TYPES[module_type], + TENTACLE_TYPES[module_subtype], + target_folder) + + if os.path.isfile(init_file): + with open(init_file, "r") as init_file_r: + init_content = init_file_r.read() + + # check if line already exists + if init_content.find(new_line_in_init) == -1: + with open(init_file, "w") as init_file_w: + # add new package to init + init_file_w.write(init_content + new_line_in_init) + + self.logger.info("{0} {1} successfully installed in: {2}" + .format(module_name, module_version, file_dir)) - # Install package in evaluator - with open("{0}/{1}.py".format(file_dir, module_name), "w") as installed_package: - installed_package.write(module_file) - - # Update local __init__ - new_line_in_init = "from .{0} import *\n".format(module_name) - init_content = "" - init_file = "{0}/{1}/{2}/__init__.py".format(CONFIG_EVALUATOR, module_type, target_folder) - - if os.path.isfile(init_file): - with open(init_file, "r") as init_file_r: - init_content = init_file_r.read() - - # check if line already exists - if init_content.find(new_line_in_init) == -1: - with open(init_file, "w") as init_file_w: - # add new package to init - init_file_w.write(init_content + new_line_in_init) - - self.logger.info("{0} {1} successfully installed in: {2}" - .format(module_name, module_version, file_dir)) + else: + raise Exception("Tentacle type not found") def install_module(self, package, module_name, package_localisation, is_url, target_folder): package_type = package[module_name]["type"] - module_loc = "{0}/{1}/{2}.py".format(package_localisation, package_type, module_name) + package_subtype = package[module_name]["subtype"] + + # create path from types + if package_subtype: + module_loc = "{0}/{1}/{2}/{3}.py".format(package_localisation, + package_type, + package_subtype, + module_name) + else: + module_loc = "{0}/{1}/{2}.py".format(package_localisation, + package_type, + module_name) + module_version = package[module_name]["version"] if is_url: @@ -128,7 +154,7 @@ def install_module(self, package, module_name, package_localisation, is_url, tar with open(module_loc, "r") as module: module_file = module.read() - self._apply_module(package_type, module_name, module_version, module_file, target_folder) + self._apply_module(package_type, package_subtype, module_version, module_file, target_folder, module_name) def _try_to_install_package(self, package, target_folder): package_description = package[TENTACLE_DESCRIPTION] diff --git a/trading/trader/modes/Advanced/.keep b/trading/trader/modes/Advanced/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/trading/trader/modes/Default/.keep b/trading/trader/modes/Default/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/trading/trader/modes/__init__.py b/trading/trader/modes/__init__.py new file mode 100644 index 000000000..89b5d6058 --- /dev/null +++ b/trading/trader/modes/__init__.py @@ -0,0 +1,2 @@ +from .Default import * +from .Advanced import * \ No newline at end of file diff --git a/trading/trader/modes/abstract_mode_creator.py b/trading/trader/modes/abstract_mode_creator.py new file mode 100644 index 000000000..0ac30290d --- /dev/null +++ b/trading/trader/modes/abstract_mode_creator.py @@ -0,0 +1,179 @@ +import math +from abc import * + +from config.cst import * +from config.cst import ExchangeConstantsMarketStatusColumns as Ecmsc +from tools.symbol_util import split_symbol + + +class AbstractTradingModeCreator: + __metaclass__ = ABCMeta + + def __init__(self, trading_mode): + self.trading_mode = trading_mode + self.MAX_SUM_RESULT = 2 + + self.STOP_LOSS_ORDER_MAX_PERCENT = 0.99 + self.STOP_LOSS_ORDER_MIN_PERCENT = 0.95 + self.STOP_LOSS_ORDER_ATTENUATION = (self.STOP_LOSS_ORDER_MAX_PERCENT - self.STOP_LOSS_ORDER_MIN_PERCENT) + + self.QUANTITY_MIN_PERCENT = 0.1 + self.QUANTITY_MAX_PERCENT = 0.9 + self.QUANTITY_ATTENUATION = (self.QUANTITY_MAX_PERCENT - self.QUANTITY_MIN_PERCENT) / self.MAX_SUM_RESULT + + self.QUANTITY_MARKET_MIN_PERCENT = 0.5 + self.QUANTITY_MARKET_MAX_PERCENT = 1 + self.QUANTITY_BUY_MARKET_ATTENUATION = 0.2 + self.QUANTITY_MARKET_ATTENUATION = (self.QUANTITY_MARKET_MAX_PERCENT - self.QUANTITY_MARKET_MIN_PERCENT) \ + / self.MAX_SUM_RESULT + + self.BUY_LIMIT_ORDER_MAX_PERCENT = 0.995 + self.BUY_LIMIT_ORDER_MIN_PERCENT = 0.98 + self.SELL_LIMIT_ORDER_MIN_PERCENT = 1 + (1 - self.BUY_LIMIT_ORDER_MAX_PERCENT) + self.SELL_LIMIT_ORDER_MAX_PERCENT = 1 + (1 - self.BUY_LIMIT_ORDER_MIN_PERCENT) + self.LIMIT_ORDER_ATTENUATION = (self.BUY_LIMIT_ORDER_MAX_PERCENT - self.BUY_LIMIT_ORDER_MIN_PERCENT)\ + / self.MAX_SUM_RESULT + + @staticmethod + def check_factor(min_val, max_val, factor): + if factor > max_val: + return max_val + elif factor < min_val: + return min_val + else: + return factor + + @staticmethod + def adapt_price(symbol_market, price): + maximal_price_digits = AbstractTradingModeCreator._get_value_or_default(symbol_market[Ecmsc.PRECISION.value], + Ecmsc.PRECISION_PRICE.value, + CURRENCY_DEFAULT_MAX_PRICE_DIGITS) + return AbstractTradingModeCreator._trunc_with_n_decimal_digits(price, maximal_price_digits) + + """ + Checks and adapts the quantity and price of the order to ensure it's exchange compliant: + - are the quantity and price of the order compliant with the exchange's number of digits requirement + => otherwise quantity and price will be truncated accordingly + - is the price of the currency compliant with the exchange's price interval for this currency + => otherwise order is impossible => returns empty list + - are the order total price and quantity superior or equal to the exchange's minimum order requirement + => otherwise order is impossible => returns empty list + - are the order total price and quantity inferior or equal to the exchange's maximum order requirement + => otherwise order is impossible as is => split order into smaller ones and returns the list + => returns the quantity and price list of possible order(s) + """ + + @staticmethod + def check_and_adapt_order_details_if_necessary(quantity, price, symbol_market): + symbol_market_limits = symbol_market[Ecmsc.LIMITS.value] + + limit_amount = symbol_market_limits[Ecmsc.LIMITS_AMOUNT.value] + limit_cost = symbol_market_limits[Ecmsc.LIMITS_COST.value] + limit_price = symbol_market_limits[Ecmsc.LIMITS_PRICE.value] + + min_quantity = AbstractTradingModeCreator._get_value_or_default(limit_amount, Ecmsc.LIMITS_AMOUNT_MIN.value) + max_quantity = AbstractTradingModeCreator._get_value_or_default(limit_amount, Ecmsc.LIMITS_AMOUNT_MAX.value) + min_cost = AbstractTradingModeCreator._get_value_or_default(limit_cost, Ecmsc.LIMITS_COST_MIN.value) + max_cost = AbstractTradingModeCreator._get_value_or_default(limit_cost, Ecmsc.LIMITS_COST_MAX.value) + min_price = AbstractTradingModeCreator._get_value_or_default(limit_price, Ecmsc.LIMITS_PRICE_MIN.value) + max_price = AbstractTradingModeCreator._get_value_or_default(limit_price, Ecmsc.LIMITS_PRICE_MAX.value) + + # adapt digits if necessary + valid_quantity = AbstractTradingModeCreator._adapt_quantity(symbol_market, quantity) + valid_price = AbstractTradingModeCreator.adapt_price(symbol_market, price) + + total_order_price = valid_quantity * valid_price + + # check total_order_price not < min_cost and valid_quantity not < min_quantity and max_price > price > min_price + if total_order_price < min_cost or valid_quantity < min_quantity or not (max_price > valid_price > min_price): + # invalid order + return [] + + # check total_order_price not > max_cost and valid_quantity not > max_quantity + elif total_order_price > max_cost or valid_quantity > max_quantity: + # split quantity into smaller orders + nb_orders_according_to_cost = total_order_price / max_cost + nb_orders_according_to_quantity = valid_quantity / max_quantity + if nb_orders_according_to_cost > nb_orders_according_to_quantity: + return AbstractTradingModeCreator._adapt_order_quantity_because_price(total_order_price, max_cost, price, + symbol_market) + else: + return AbstractTradingModeCreator._adapt_order_quantity_because_quantity(valid_quantity, max_quantity, + quantity, price, symbol_market) + + else: + # valid order that can be handled wy the exchange + return [(valid_quantity, valid_price)] + + @staticmethod + # Can be overwritten + def can_create_order(symbol, exchange, state, portfolio): + currency, market = split_symbol(symbol) + + # get symbol min amount when creating order + symbol_limit = exchange.get_market_status(symbol)[Ecmsc.LIMITS.value] + symbol_min_amount = symbol_limit[Ecmsc.LIMITS_AMOUNT.value][Ecmsc.LIMITS_AMOUNT_MIN.value] + order_min_amount = symbol_limit[Ecmsc.LIMITS_COST.value][Ecmsc.LIMITS_COST_MIN.value] + + # short cases => sell => need this currency + if state == EvaluatorStates.VERY_SHORT or state == EvaluatorStates.SHORT: + return portfolio.get_currency_portfolio(currency) > symbol_min_amount + + # long cases => buy => need money(aka other currency in the pair) to buy this currency + elif state == EvaluatorStates.LONG or state == EvaluatorStates.VERY_LONG: + return portfolio.get_currency_portfolio(market) > order_min_amount + + # other cases like neutral state or unfulfilled previous conditions + return False + + @abstractmethod + def create_new_order(self, eval_note, symbol, exchange, trader, portfolio, state): + raise NotImplementedError("create_new_order not implemented") + + @staticmethod + def _adapt_quantity(symbol_market, quantity): + maximal_volume_digits = AbstractTradingModeCreator._get_value_or_default(symbol_market[Ecmsc.PRECISION.value], + Ecmsc.PRECISION_AMOUNT.value, 0) + return AbstractTradingModeCreator._trunc_with_n_decimal_digits(quantity, maximal_volume_digits) + + @staticmethod + def _trunc_with_n_decimal_digits(value, digits): + # force exact representation + return float("{0:.{1}f}".format(math.trunc(value*10**digits)/(10**digits), digits)) + + @staticmethod + def _get_value_or_default(dictionary, key, default=math.nan): + if key in dictionary: + value = dictionary[key] + return value if value is not None else default + return default + + @staticmethod + def _adapt_order_quantity_because_quantity(limiting_value, max_value, quantity_to_adapt, price, symbol_market): + orders = [] + nb_full_orders = limiting_value // max_value + rest_order_quantity = limiting_value % max_value + after_rest_quantity_to_adapt = quantity_to_adapt + if rest_order_quantity > 0: + after_rest_quantity_to_adapt -= rest_order_quantity + valid_last_order_quantity = AbstractTradingModeCreator._adapt_quantity(symbol_market, rest_order_quantity) + orders.append((valid_last_order_quantity, price)) + + other_orders_quantity = (after_rest_quantity_to_adapt + max_value)/(nb_full_orders+1) + valid_other_orders_quantity = AbstractTradingModeCreator._adapt_quantity(symbol_market, other_orders_quantity) + orders += [(valid_other_orders_quantity, price)]*int(nb_full_orders) + return orders + + @staticmethod + def _adapt_order_quantity_because_price(limiting_value, max_value, price, symbol_market): + orders = [] + nb_full_orders = limiting_value // max_value + rest_order_cost = limiting_value % max_value + if rest_order_cost > 0: + valid_last_order_quantity = AbstractTradingModeCreator._adapt_quantity(symbol_market, rest_order_cost / price) + orders.append((valid_last_order_quantity, price)) + + other_orders_quantity = max_value / price + valid_other_orders_quantity = AbstractTradingModeCreator._adapt_quantity(symbol_market, other_orders_quantity) + orders += [(valid_other_orders_quantity, price)] * int(nb_full_orders) + return orders diff --git a/trading/trader/modes/abstract_mode_decider.py b/trading/trader/modes/abstract_mode_decider.py new file mode 100644 index 000000000..c853af237 --- /dev/null +++ b/trading/trader/modes/abstract_mode_decider.py @@ -0,0 +1,91 @@ +import logging +from abc import * +from queue import Queue + +from config.cst import INIT_EVAL_NOTE +from tools.asynchronous_server import AsynchronousServer +from tools.notifications import EvaluatorNotification + + +class AbstractTradingModeDecider(AsynchronousServer): + __metaclass__ = ABCMeta + + def __init__(self, trading_mode, symbol_evaluator, exchange, symbol): + super().__init__(self.finalize) + self.trading_mode = trading_mode + self.symbol_evaluator = symbol_evaluator + self.config = symbol_evaluator.get_config() + self.final_eval = INIT_EVAL_NOTE + self.state = None + self.keep_running = True + self.exchange = exchange + self.symbol = symbol + self.is_computing = False + self.logger = logging.getLogger(self.__class__.__name__) + + # If final_eval not is < X_THRESHOLD --> state = X + self.VERY_LONG_THRESHOLD = -0.95 + self.LONG_THRESHOLD = -0.25 + self.NEUTRAL_THRESHOLD = 0.25 + self.SHORT_THRESHOLD = 0.95 + self.RISK_THRESHOLD = 0.2 + + self.notifier = EvaluatorNotification(self.config) + self.queue = Queue() + + # create real and/or simulating orders in trader instances + def create_final_state_orders(self, evaluator_notification): + # simulated trader + self._create_order_if_possible(evaluator_notification, + self.symbol_evaluator.get_trader_simulator(self.exchange)) + + # real trader + self._create_order_if_possible(evaluator_notification, + self.symbol_evaluator.get_trader(self.exchange)) + + def get_state(self): + return self.state + + def get_final_eval(self): + return self.final_eval + + def finalize(self): + # reset previous note + self.final_eval = INIT_EVAL_NOTE + + self.set_final_eval() + self.create_state() + + def stop(self): + self.keep_running = False + + # called first by finalize => when any notification appears + @abstractmethod + def set_final_eval(self): + raise NotImplementedError("_set_final_eval not implemented") + + # called after _set_final_eval by finalize => when any notification appears + @abstractmethod + def create_state(self): + raise NotImplementedError("_create_state not implemented") + + # for each trader call the creator to check if order creation is possible and create it + def _create_order_if_possible(self, evaluator_notification, trader): + if trader.is_enabled(): + with trader.get_portfolio() as pf: + if self.trading_mode.get_creator().can_create_order(self.symbol, self.exchange, self.state, pf): + self._push_order_notification_if_possible( + self.symbol_evaluator.get_evaluator_order_creator(self.exchange).create_new_order( + self.final_eval, + self.symbol, + self.exchange, + trader, + pf, + self.state), + evaluator_notification) + + @staticmethod + def _push_order_notification_if_possible(order_list, notification): + if order_list: + for order in order_list: + order.get_order_notifier().notify(notification) diff --git a/trading/trader/modes/abstract_trading_mode.py b/trading/trader/modes/abstract_trading_mode.py new file mode 100644 index 000000000..41610560b --- /dev/null +++ b/trading/trader/modes/abstract_trading_mode.py @@ -0,0 +1,23 @@ +from abc import * + + +class AbstractTradingMode: + __metaclass__ = ABCMeta + + def __init__(self, config): + self.config = config + + self.decider = None + self.creator = None + + def set_decider(self, decider): + self.decider = decider + + def set_creator(self, creator): + self.creator = creator + + def get_creator(self): + return self.creator + + def get_decider(self): + return self.decider