Skip to content

Commit

Permalink
Merge pull request #280 from Drakkar-Software/dev
Browse files Browse the repository at this point in the history
Patch for high frequency mode handling
  • Loading branch information
Guillaume De Saint Martin authored Jul 1, 2018
2 parents 1ca5bd2 + 8932c30 commit 01c19cb
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 23 deletions.
4 changes: 3 additions & 1 deletion config/cst.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from enum import Enum

SHORT_VERSION = "0.1.4"
MINOR_VERSION = "0"
MINOR_VERSION = "1"
VERSION_DEV_PHASE = "beta"
VERSION = "{0}-{1}".format(SHORT_VERSION, VERSION_DEV_PHASE)
LONG_VERSION = "{0}_{1}-{2}".format(SHORT_VERSION, MINOR_VERSION, VERSION_DEV_PHASE)
Expand Down Expand Up @@ -382,6 +382,8 @@ class ExchangeConstantsTickersInfoColumns(Enum):


class ExchangeConstantsMarketStatusColumns(Enum):
TAKER = "taker"
MAKER = "maker"
SYMBOL = "symbol"
ID = "id"
CURRENCY = "base"
Expand Down
7 changes: 7 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
*It is strongly advised to perform an update of your tentacles after updating OctoBot.*

Changelog for 0.1.4_1-beta
====================
*Released date : July 1 2018*

# Concerned issues :
#279 [Trading Modes] prepare bot for high frequency treading mode

Changelog for 0.1.4-beta
====================
*Released date : July 1 2018*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,6 @@ def test_trunc_with_n_decimal_digits():

def test_get_value_or_default(self):
test_dict = {"a": 1, "b": 2, "c": 3}
assert AbstractTradingModeCreator._get_value_or_default(test_dict, "b", default="") == 2
assert AbstractTradingModeCreator._get_value_or_default(test_dict, "d") is math.nan
assert AbstractTradingModeCreator._get_value_or_default(test_dict, "d", default="") == ""
assert AbstractTradingModeCreator.get_value_or_default(test_dict, "b", default="") == 2
assert AbstractTradingModeCreator.get_value_or_default(test_dict, "d") is math.nan
assert AbstractTradingModeCreator.get_value_or_default(test_dict, "d", default="") == ""
135 changes: 135 additions & 0 deletions tests/unit_tests/trading_modes_tests/trading_mode_test_toolkit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
from config.cst import ExchangeConstantsMarketStatusColumns as Ecmsc
from config.cst import EvaluatorStates
from trading.trader.order import *
from trading.trader.portfolio import Portfolio


def check_order_limits(order, market_status):
symbol_market_limits = market_status[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 = limit_amount[Ecmsc.LIMITS_AMOUNT_MIN.value]
max_quantity = limit_amount[Ecmsc.LIMITS_AMOUNT_MAX.value]
min_cost = limit_cost[Ecmsc.LIMITS_COST_MIN.value]
max_cost = limit_cost[Ecmsc.LIMITS_COST_MAX.value]
min_price = limit_price[Ecmsc.LIMITS_PRICE_MIN.value]
max_price = limit_price[Ecmsc.LIMITS_PRICE_MAX.value]
maximal_price_digits = market_status[Ecmsc.PRECISION.value][Ecmsc.PRECISION_PRICE.value]
maximal_volume_digits = market_status[Ecmsc.PRECISION.value][Ecmsc.PRECISION_AMOUNT.value]
order_cost = order.origin_price*order.origin_quantity

assert order_cost <= max_cost
assert order_cost >= min_cost
assert order.origin_price <= max_price
assert order.origin_price >= min_price
assert str(order.origin_price)[::-1].find(".") <= maximal_price_digits
assert order.origin_quantity <= max_quantity
assert order.origin_quantity >= min_quantity
assert str(order.origin_quantity)[::-1].find(".") <= maximal_volume_digits


def check_linked_order(order, linked_order, order_type, order_price, market_status):
assert linked_order.exchange == order.exchange
assert linked_order.trader == order.trader
assert linked_order.order_notifier == order.order_notifier
assert linked_order.order_type == order_type
assert linked_order.created_last_price == order.created_last_price
assert linked_order.origin_price == order_price
assert linked_order.linked_orders[0] == order
assert linked_order.created_last_price == order.created_last_price
assert linked_order.currency == order.currency
assert linked_order.currency_total_fees == order.currency_total_fees
assert linked_order.market_total_fees == order.market_total_fees
assert linked_order.filled_price == order.filled_price
assert linked_order.filled_quantity == order.filled_quantity
assert linked_order.linked_to == order
assert linked_order.status == order.status
assert linked_order.symbol == order.symbol
check_order_limits(order, market_status)


def check_orders(orders, evaluation, state, nb_orders, market_status):

if state == EvaluatorStates.NEUTRAL:
assert orders is None
else:
if math.isnan(evaluation):
assert orders is None
elif math.isnan(evaluation):
assert orders is None
elif state not in EvaluatorStates:
assert orders is None
else:
assert (not orders and nb_orders == 0) or (len(orders) == nb_orders) \
or ((len(orders) == 0 or len(orders) == 1) and nb_orders == "unknown")
if orders:
order = orders[0]
assert order.status == OrderStatus.OPEN
assert order.is_simulated is True
assert order.linked_to is None
assert order.currency_total_fees == 0
assert order.market_total_fees == 0
assert order.filled_price == 0
assert order.filled_quantity == order.origin_quantity

if state == EvaluatorStates.VERY_SHORT:
assert isinstance(order, SellMarketOrder)
assert order.side == TradeOrderSide.SELL
assert order.order_type == TraderOrderType.SELL_MARKET
elif state == EvaluatorStates.SHORT:
assert isinstance(order, SellLimitOrder)
assert order.side == TradeOrderSide.SELL
assert order.order_type == TraderOrderType.SELL_LIMIT
elif state == EvaluatorStates.VERY_LONG:
assert isinstance(order, BuyMarketOrder)
assert order.side == TradeOrderSide.BUY
assert order.order_type == TraderOrderType.BUY_MARKET
elif state == EvaluatorStates.LONG:
assert isinstance(order, BuyLimitOrder)
assert order.side == TradeOrderSide.BUY
assert order.order_type == TraderOrderType.BUY_LIMIT

check_order_limits(order, market_status)


def check_portfolio(portfolio, initial_portfolio, orders, only_positivity=False):
if orders:
orders_market_amount = 0
orders_currency_amount = 0
market = orders[0].market
order_symbol = orders[0].currency
for order in orders:
assert order.market == market
assert order.currency == order_symbol
if order.side == TradeOrderSide.BUY:
orders_market_amount += order.origin_quantity * order.origin_price
else:
orders_currency_amount += order.origin_quantity
for symbol in portfolio.portfolio:
assert portfolio.portfolio[symbol][Portfolio.TOTAL] >= 0
assert portfolio.portfolio[symbol][Portfolio.AVAILABLE] >= 0
if not only_positivity:
if order_symbol == symbol:
assert initial_portfolio[symbol][Portfolio.TOTAL] == portfolio.portfolio[symbol][
Portfolio.TOTAL]
assert "{:f}".format(
initial_portfolio[symbol][Portfolio.AVAILABLE] - orders_currency_amount) == \
"{:f}".format(portfolio.portfolio[symbol][Portfolio.AVAILABLE])
elif market == symbol:
assert initial_portfolio[market][Portfolio.TOTAL] == portfolio.portfolio[market][
Portfolio.TOTAL]
assert "{:f}".format(initial_portfolio[market][Portfolio.AVAILABLE] - orders_market_amount) \
== "{:f}".format(portfolio.portfolio[market][Portfolio.AVAILABLE])


def fill_orders(orders, trader):
if orders:
assert trader.get_order_manager().order_list
for order in orders:
order.filled_price = order.origin_price
order.filled_quantity = order.origin_quantity
trader.notify_order_close(order)
check_portfolio(trader.portfolio, None, orders, True)
assert len(trader.get_order_manager().order_list) == 0
7 changes: 7 additions & 0 deletions trading/exchanges/exchange_dispatcher.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from trading.exchanges.exchange_symbol_data import SymbolData
from trading import AbstractExchange
from trading.exchanges.exchange_personal_data import ExchangePersonalData
from config.cst import ExchangeConstantsMarketStatusColumns as ecmsc


class ExchangeDispatcher(AbstractExchange):
Expand Down Expand Up @@ -158,3 +159,9 @@ def stop(self):

def get_uniform_timestamp(self, timestamp):
return self.exchange.get_uniform_timestamp(timestamp)

# returns (taker, maker) tuple
def get_fees(self, symbol):
#TODO temporary implementation waiting for more accurate fee management
market_status = self.exchange.get_market_status(symbol)
return market_status[ecmsc.TAKER.value], market_status[ecmsc.MAKER.value]
10 changes: 8 additions & 2 deletions trading/exchanges/exchange_simulator/exchange_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,14 @@ def _extract_indexes(self, array, index, factor=1, max_value=None):
return array[index:index + max_count]

def _extract_data_with_limit(self, symbol, time_frame):
return self._extract_indexes(self.data[symbol][time_frame.value], self.time_frame_get_times[time_frame.value])
to_use_timeframe = time_frame.value if time_frame is not None else next(iter(self.data[symbol]))
return self._extract_indexes(self.data[symbol][to_use_timeframe],
self.time_frame_get_times[to_use_timeframe])

def get_symbol_prices(self, symbol, time_frame, limit=None, return_list=True):
result = self._extract_data_with_limit(symbol, time_frame)
self.time_frame_get_times[time_frame.value] += 1
if time_frame is not None:
self.time_frame_get_times[time_frame.value] += 1
self.get_symbol_data(symbol).update_symbol_candles(time_frame, result, replace_all=True)

def get_recent_trades(self, symbol):
Expand Down Expand Up @@ -213,6 +216,9 @@ def get_all_currencies_price_ticker(self):

def get_market_status(self, symbol):
return {
# fees
ExchangeConstantsMarketStatusColumns.TAKER.value: 0.001,
ExchangeConstantsMarketStatusColumns.MAKER.value: 0.001,
# number of decimal digits "after the dot"
ExchangeConstantsMarketStatusColumns.PRECISION.value: {
ExchangeConstantsMarketStatusColumns.PRECISION_AMOUNT.value: 8,
Expand Down
6 changes: 5 additions & 1 deletion trading/exchanges/exchange_symbol_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def __init__(self, symbol):

# candle functions
def update_symbol_candles(self, time_frame, new_symbol_candles_data, replace_all=False):
if time_frame not in self.symbol_candles or replace_all:
if time_frame is not None and time_frame not in self.symbol_candles or replace_all:
self.symbol_candles[time_frame] = CandleData(new_symbol_candles_data)

else:
Expand Down Expand Up @@ -60,6 +60,8 @@ def update_recent_trades(self, new_recent_trades_data):
def get_candle_data(self, time_frame):
if time_frame in self.symbol_candles:
return self.symbol_candles[time_frame]
elif time_frame is None:
return self.symbol_candles[next(iter(self.symbol_candles))]
return None

# ticker functions
Expand All @@ -82,6 +84,8 @@ def _has_candle_changed(candle_data, start_candle_time):
def candles_are_initialized(self, time_frame):
if time_frame in self.symbol_candles and self.symbol_candles[time_frame].is_initialized:
return True
elif time_frame is None:
return True
return False

def price_ticker_is_initialized(self):
Expand Down
28 changes: 14 additions & 14 deletions trading/trader/modes/abstract_mode_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ def check_factor(min_val, max_val, 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)
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)

@staticmethod
Expand All @@ -39,8 +39,8 @@ def get_additional_dusts_to_quantity_if_necessary(quantity, price, symbol_market
limit_amount = symbol_market_limits[Ecmsc.LIMITS_AMOUNT.value]
limit_cost = symbol_market_limits[Ecmsc.LIMITS_COST.value]

min_quantity = AbstractTradingModeCreator._get_value_or_default(limit_amount, Ecmsc.LIMITS_AMOUNT_MIN.value)
min_cost = AbstractTradingModeCreator._get_value_or_default(limit_cost, Ecmsc.LIMITS_COST_MIN.value)
min_quantity = AbstractTradingModeCreator.get_value_or_default(limit_amount, Ecmsc.LIMITS_AMOUNT_MIN.value)
min_cost = AbstractTradingModeCreator.get_value_or_default(limit_cost, Ecmsc.LIMITS_COST_MIN.value)

if remaining_max_total_order_price < min_cost or remaining_portfolio_amount < min_quantity:
return remaining_portfolio_amount
Expand Down Expand Up @@ -68,12 +68,12 @@ def check_and_adapt_order_details_if_necessary(quantity, price, symbol_market):
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)
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)
Expand Down Expand Up @@ -152,8 +152,8 @@ def create_new_order(self, eval_note, symbol, exchange, trader, portfolio, state

@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)
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
Expand All @@ -162,7 +162,7 @@ def _trunc_with_n_decimal_digits(value, digits):
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):
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
Expand Down
1 change: 1 addition & 0 deletions trading/trader/modes/abstract_trading_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def __init__(self, config, symbol_evaluator, exchange):
self.deciders = {}
self.deciders_without_keys = []
self.strategy_instances_by_classes = {}
self.symbol = symbol_evaluator.get_symbol()
self._init_strategies_instances(symbol_evaluator.get_strategies_eval_list(exchange))

@staticmethod
Expand Down
4 changes: 2 additions & 2 deletions trading/trader/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def update_order_status(self):
else:
# ONLY FOR SIMULATION
self.status = OrderStatus.FILLED
self.filled_price = float(self.last_prices[-1]["price"])
self.filled_price = self.created_last_price
self.filled_quantity = self.origin_quantity
self.executed_time = time.time()

Expand Down Expand Up @@ -277,7 +277,7 @@ def update_order_status(self):
else:
# ONLY FOR SIMULATION
self.status = OrderStatus.FILLED
self.filled_price = float(self.last_prices[-1]["price"])
self.filled_price = self.created_last_price
self.filled_quantity = self.origin_quantity
self.executed_time = time.time()

Expand Down

0 comments on commit 01c19cb

Please sign in to comment.