Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Trading Mode] Implement market making #31

Merged
merged 1 commit into from
Feb 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 42 additions & 4 deletions Evaluator/RealTime/instant_fluctuations_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@

import numpy as np

from config import *
from config import ExchangeConstantsOrderBookInfoColumns, CONFIG_REFRESH_RATE, PriceIndexes, CONFIG_TIME_FRAME, \
START_PENDING_EVAL_NOTE
from evaluator.RealTime.realtime_evaluator import RealTimeTAEvaluator

"""
Expand Down Expand Up @@ -148,7 +149,7 @@ def __init__(self, exchange, symbol):

async def _refresh_data(self):
self.last_candle_data = await self._get_data_from_exchange(self.specific_config[CONFIG_TIME_FRAME],
limit=20, return_list=False)
limit=20, return_list=False)

async def eval_impl(self):
self.eval_note = 0
Expand Down Expand Up @@ -190,7 +191,7 @@ def __init__(self, exchange, symbol):

async def _refresh_data(self):
new_data = await self._get_data_from_exchange(self.specific_config[CONFIG_TIME_FRAME],
limit=20, return_list=False)
limit=20, return_list=False)
self.should_eval = not self._compare_data(new_data, self.last_candle_data)
self.last_candle_data = new_data

Expand Down Expand Up @@ -240,7 +241,7 @@ def __init__(self, exchange, symbol):

async def _refresh_data(self):
new_data = await self._get_data_from_exchange(self.specific_config[CONFIG_TIME_FRAME],
limit=20, return_list=False)
limit=20, return_list=False)
self.should_eval = not self._compare_data(new_data, self.last_candle_data)
self.last_candle_data = new_data

Expand Down Expand Up @@ -274,3 +275,40 @@ def set_default_config(self):

def _should_eval(self):
return self.should_eval


class InstantMarketMakingEvaluator(RealTimeTAEvaluator):
def __init__(self, exchange, symbol):
super().__init__(exchange, symbol)
self.last_best_bid = None
self.last_best_ask = None
self.last_order_book_data = None
self.should_eval = True

async def _refresh_data(self):
self.last_order_book_data = await self._get_order_book_from_exchange(limit=5)

async def eval_impl(self):
self.eval_note = ""
best_bid = self.last_best_bid
best_ask = self.last_best_ask

if self.last_order_book_data is not None:
best_bid = self.last_order_book_data[ExchangeConstantsOrderBookInfoColumns.BIDS.value][-1]
best_ask = self.last_order_book_data[ExchangeConstantsOrderBookInfoColumns.ASKS.value][-1]

if self.last_best_ask != best_ask or self.last_best_bid != best_bid:
self.eval_note = {
ExchangeConstantsOrderBookInfoColumns.BIDS.value: best_bid,
ExchangeConstantsOrderBookInfoColumns.ASKS.value: best_ask
}
await self.notify_evaluator_task_managers(self.__class__.__name__)
self.last_best_ask = best_ask
self.last_best_bid = best_bid

def set_default_config(self):
super().set_default_config()
self.specific_config[CONFIG_REFRESH_RATE] = 60

def _should_eval(self):
return self.should_eval
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"required_time_frames" : ["1m"],
"required_evaluators" : ["InstantMarketMakingEvaluator"]
}
46 changes: 46 additions & 0 deletions Evaluator/Strategies/market_making_startegy_evaluator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""
OctoBot Tentacle

$tentacle_description: {
"name": "market_making_startegy_evaluator",
"type": "Evaluator",
"subtype": "Strategies",
"version": "1.1.0",
"requirements": ["instant_fluctuations_evaluator"],
"config_files": ["SimpleMarketMakingStrategiesEvaluator.json"],
"tests":["test_market_making_strategies_evaluator"]
}
"""

# Drakkar-Software OctoBot-Tentacles
# Copyright (c) Drakkar-Software, All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3.0 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.

from config import EvaluatorMatrixTypes
from evaluator.Strategies import MarketMakingStrategiesEvaluator
from tentacles.Evaluator.RealTime import InstantMarketMakingEvaluator


class SimpleMarketMakingStrategiesEvaluator(MarketMakingStrategiesEvaluator):
DESCRIPTION = "SimpleMarketMakingStrategiesEvaluator uses to pass up to date bid and ask price to MM TM"

INSTANT_MM_CLASS_NAME = InstantMarketMakingEvaluator.get_name()

async def eval_impl(self) -> None:
self.finalize()

def finalize(self):
self.eval_note = self.matrix[EvaluatorMatrixTypes.REAL_TIME][
SimpleMarketMakingStrategiesEvaluator.INSTANT_MM_CLASS_NAME]
5 changes: 5 additions & 0 deletions Trading/Mode/MarketMakerTradingMode.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"delta_ask": 0.00001,
"delta_bid": 0.00001,
"required_strategies": ["SimpleMarketMakingStrategiesEvaluator"]
}
23 changes: 11 additions & 12 deletions Trading/Mode/daily_trading_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@


class DailyTradingMode(AbstractTradingMode):

DESCRIPTION = "DailyTradingMode is a low risk trading mode adapted to flat markets."

def __init__(self, config, exchange):
Expand Down Expand Up @@ -248,10 +247,10 @@ async def create_new_order(self, eval_note, symbol, exchange, trader, portfolio,
raise e

except Exception as e:
get_logger(self.__class__.__name__).error(f"Failed to create order : {e}. "
f"Order: "
f"{current_order.get_string_info() if current_order else None}")
get_logger(self.__class__.__name__).exception(e)
self.logger.error(f"Failed to create order : {e}. "
f"Order: "
f"{current_order.get_string_info() if current_order else None}")
self.logger.exception(e)
return None


Expand Down Expand Up @@ -320,12 +319,12 @@ async def _set_state(self, new_state):
# create notification
if self.symbol_evaluator.matrices:
await self.notifier.notify_alert(
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())
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
await self.create_final_state_orders(self.notifier, self.trading_mode.get_only_creator_key(self.symbol))
202 changes: 202 additions & 0 deletions Trading/Mode/market_maker_trading_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
"""
OctoBot Tentacle

$tentacle_description: {
"name": "market_maker_trading_mode",
"type": "Trading",
"subtype": "Mode",
"version": "1.1.0",
"requirements": ["instant_fluctuations_evaluator", "market_making_startegy_evaluator"],
"config_files": ["MarketMakerTradingMode.json"]
}
"""
# Drakkar-Software OctoBot-Tentacles
# Copyright (c) Drakkar-Software, All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3.0 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.
from ccxt import InsufficientFunds

from config import ExchangeConstantsOrderBookInfoColumns, TraderOrderType, ExchangeConstantsMarketPropertyColumns
from trading.trader.modes import AbstractTradingModeCreator, AbstractTradingModeDecider
from trading.trader.modes.abstract_trading_mode import AbstractTradingMode


class MarketMakerTradingMode(AbstractTradingMode):
DESCRIPTION = "MarketMakerTradingMode"

DELTA_ASK = "delta_ask"
DELTA_BID = "delta_bid"

def __init__(self, config, exchange):
super().__init__(config, exchange)

self.trading_mode_config = MarketMakerTradingMode.get_trading_mode_config()

def create_deciders(self, symbol, symbol_evaluator):
self.add_decider(symbol, MarketMakerTradingModeDecider(self, symbol_evaluator, self.exchange))

def create_creators(self, symbol, _):
self.add_creator(symbol, MarketMakerTradingModeCreator(self))


class MarketMakerTradingModeCreator(AbstractTradingModeCreator):
LIMIT_ORDER_ATTENUATION = 10
FEES_ATTENUATION = 2

def __init__(self, trading_mode):
super().__init__(trading_mode)

self.config_delta_ask = 0 # percent
self.config_delta_bid = 0 # percent
self.fees = {}

if MarketMakerTradingMode.DELTA_ASK not in trading_mode.trading_mode_config or \
MarketMakerTradingMode.DELTA_BID not in trading_mode.trading_mode_config:
self.logger.error(f"Can't create any trade : some configuration is missing "
f"in {MarketMakerTradingMode.get_config_file_name()}, "
f"please check {MarketMakerTradingMode.DELTA_ASK} and {MarketMakerTradingMode.DELTA_BID}")
else:
self.config_delta_ask = trading_mode.trading_mode_config[MarketMakerTradingMode.DELTA_ASK]
self.config_delta_bid = trading_mode.trading_mode_config[MarketMakerTradingMode.DELTA_BID]

def verify_and_adapt_delta_with_fees(self, symbol):
if symbol in self.fees:
return self.fees[symbol]

exchange_fees = self.trading_mode.exchange.get_fees(symbol)
delta_ask = self.config_delta_ask
delta_bid = self.config_delta_bid

# check ask -> limit_orders -> MAKER ? not sure -> max
common_fees = max(exchange_fees[ExchangeConstantsMarketPropertyColumns.TAKER.value],
exchange_fees[ExchangeConstantsMarketPropertyColumns.FEE.value],
exchange_fees[ExchangeConstantsMarketPropertyColumns.MAKER.value])

if delta_ask < (common_fees / self.FEES_ATTENUATION):
delta_ask = common_fees / self.FEES_ATTENUATION

if delta_bid < (common_fees / self.FEES_ATTENUATION):
delta_bid = common_fees / self.FEES_ATTENUATION

self.fees[symbol] = delta_ask, delta_bid
return self.fees[symbol]

def _get_quantity_from_risk(self, trader, quantity):
return quantity * trader.get_risk() / self.LIMIT_ORDER_ATTENUATION

@staticmethod
async def can_create_order(symbol, exchange, state, portfolio):
return True

async def create_new_order(self, eval_note, symbol, exchange, trader, portfolio, state):
current_order = None

try:
delta_ask, delta_bid = self.verify_and_adapt_delta_with_fees(symbol)
best_bid_price = eval_note[ExchangeConstantsOrderBookInfoColumns.BIDS.value][0]
best_ask_price = eval_note[ExchangeConstantsOrderBookInfoColumns.ASKS.value][0]

current_symbol_holding, current_market_quantity, market_quantity, price, symbol_market = \
await self.get_pre_order_data(exchange, symbol, portfolio)

created_orders = []

# Create SHORT order
quantity = self._get_quantity_from_risk(trader, current_symbol_holding)
quantity = self.add_dusts_to_quantity_if_necessary(quantity, price,
symbol_market, current_symbol_holding)
limit_price = best_ask_price - (best_ask_price * delta_ask)

for order_quantity, order_price in self.check_and_adapt_order_details_if_necessary(quantity,
limit_price,
symbol_market):
current_order = trader.create_order_instance(order_type=TraderOrderType.SELL_LIMIT,
symbol=symbol,
current_price=price,
quantity=order_quantity,
price=order_price)
updated_limit = await trader.create_order(current_order, portfolio)
created_orders.append(updated_limit)

await trader.create_order(current_order, portfolio)

# Create LONG order
quantity = self._get_quantity_from_risk(trader, market_quantity)
quantity = self.add_dusts_to_quantity_if_necessary(quantity, price,
symbol_market, current_symbol_holding)

limit_price = best_bid_price + (best_bid_price * delta_bid)

for order_quantity, order_price in self.check_and_adapt_order_details_if_necessary(quantity,
limit_price,
symbol_market):
current_order = trader.create_order_instance(order_type=TraderOrderType.BUY_LIMIT,
symbol=symbol,
current_price=price,
quantity=order_quantity,
price=order_price)
await trader.create_order(current_order, portfolio)
created_orders.append(current_order)

return created_orders

except InsufficientFunds as e:
raise e

except Exception as e:
self.logger.error(f"Failed to create order : {e}. "
f"Order: "
f"{current_order.get_string_info() if current_order else None}")
self.logger.exception(e)
return None


class MarketMakerTradingModeDecider(AbstractTradingModeDecider):
@classmethod
def get_should_cancel_loaded_orders(cls):
return True

def check_valid_market_making_note(self, eval_note) -> bool:
if ExchangeConstantsOrderBookInfoColumns.BIDS.value not in eval_note or \
ExchangeConstantsOrderBookInfoColumns.ASKS.value not in eval_note:
self.logger.warning("Incorrect eval_note format, can't create any order.")
return False
return True

def set_final_eval(self):
for evaluated_strategies in self.symbol_evaluator.get_strategies_eval_list(self.exchange):
strategy_eval = evaluated_strategies.get_eval_note()
if self.check_valid_market_making_note(strategy_eval):
self.final_eval = strategy_eval
return self.check_valid_market_making_note(self.final_eval)

async def create_state(self):
# previous_state = self.state
self.logger.info(f"{self.symbol} ** REPLACING MARKET MAKING ORDERS **")

# cancel open orders
await self.cancel_symbol_open_orders()

# create notification
if self.symbol_evaluator.matrices:
await self.notifier.notify_alert(
"",
self.symbol_evaluator.get_crypto_currency_evaluator(),
self.symbol_evaluator.get_symbol(),
self.symbol_evaluator.get_trader(self.exchange),
"REPLACING.ORDERS",
self.symbol_evaluator.get_matrix(self.exchange).get_matrix())

# call orders creation method
await self.create_final_state_orders(self.notifier, self.trading_mode.get_only_creator_key(self.symbol))
Loading