bot_id - multiple bots connected to a single database server
This commit is contained in:
parent
1edbc494ee
commit
4c62589ebc
@ -43,6 +43,7 @@ SUPPORTED_FIAT = [
|
|||||||
CONF_SCHEMA = {
|
CONF_SCHEMA = {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
|
'bot_id': {'type': 'integer', 'minimum': 0},
|
||||||
'max_open_trades': {'type': 'integer', 'minimum': 0},
|
'max_open_trades': {'type': 'integer', 'minimum': 0},
|
||||||
'ticker_interval': {'type': 'string', 'enum': list(TICKER_INTERVAL_MINUTES.keys())},
|
'ticker_interval': {'type': 'string', 'enum': list(TICKER_INTERVAL_MINUTES.keys())},
|
||||||
'stake_currency': {'type': 'string', 'enum': ['BTC', 'XBT', 'ETH', 'USDT', 'EUR', 'USD']},
|
'stake_currency': {'type': 'string', 'enum': ['BTC', 'XBT', 'ETH', 'USDT', 'EUR', 'USD']},
|
||||||
|
@ -122,6 +122,7 @@ class FreqtradeBot(object):
|
|||||||
stake_amount = self.config['stake_amount']
|
stake_amount = self.config['stake_amount']
|
||||||
minimal_roi = self.config['minimal_roi']
|
minimal_roi = self.config['minimal_roi']
|
||||||
ticker_interval = self.config['ticker_interval']
|
ticker_interval = self.config['ticker_interval']
|
||||||
|
max_open_trades = self.config['max_open_trades']
|
||||||
exchange_name = self.config['exchange']['name']
|
exchange_name = self.config['exchange']['name']
|
||||||
strategy_name = self.config.get('strategy', '')
|
strategy_name = self.config.get('strategy', '')
|
||||||
self.rpc.send_msg({
|
self.rpc.send_msg({
|
||||||
@ -130,10 +131,12 @@ class FreqtradeBot(object):
|
|||||||
f'*Stake per trade:* `{stake_amount} {stake_currency}`\n'
|
f'*Stake per trade:* `{stake_amount} {stake_currency}`\n'
|
||||||
f'*Minimum ROI:* `{minimal_roi}`\n'
|
f'*Minimum ROI:* `{minimal_roi}`\n'
|
||||||
f'*Ticker Interval:* `{ticker_interval}`\n'
|
f'*Ticker Interval:* `{ticker_interval}`\n'
|
||||||
f'*Strategy:* `{strategy_name}`'
|
f'*Strategy:* `{strategy_name}`\n'
|
||||||
|
f'*Maximum Open Trades:* `{max_open_trades}`'
|
||||||
})
|
})
|
||||||
if self.config.get('dynamic_whitelist', False):
|
if self.config.get('dynamic_whitelist', False):
|
||||||
top_pairs = 'top ' + str(self.config.get('dynamic_whitelist', 20))
|
top_pairs = 'top ' + str(self.config.get('dynamic_whitelist', 20)) +\
|
||||||
|
' highest volume'
|
||||||
specific_pairs = ''
|
specific_pairs = ''
|
||||||
else:
|
else:
|
||||||
top_pairs = 'whitelisted'
|
top_pairs = 'whitelisted'
|
||||||
@ -181,7 +184,8 @@ class FreqtradeBot(object):
|
|||||||
self.config['exchange']['pair_whitelist'] = final_list
|
self.config['exchange']['pair_whitelist'] = final_list
|
||||||
|
|
||||||
# Query trades from persistence layer
|
# Query trades from persistence layer
|
||||||
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
trades = Trade.query.filter(Trade.bot_id == self.config.get('bot_id', 0)).\
|
||||||
|
filter(Trade.is_open.is_(True)).all()
|
||||||
|
|
||||||
# First process current opened trades
|
# First process current opened trades
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
@ -288,7 +292,8 @@ class FreqtradeBot(object):
|
|||||||
avaliable_amount = self.exchange.get_balance(self.config['stake_currency'])
|
avaliable_amount = self.exchange.get_balance(self.config['stake_currency'])
|
||||||
|
|
||||||
if stake_amount == constants.UNLIMITED_STAKE_AMOUNT:
|
if stake_amount == constants.UNLIMITED_STAKE_AMOUNT:
|
||||||
open_trades = len(Trade.query.filter(Trade.is_open.is_(True)).all())
|
open_trades = len(Trade.query.filter(Trade.bot_id == self.config.get('bot_id', 0)).
|
||||||
|
filter(Trade.is_open.is_(True)).all())
|
||||||
if open_trades >= self.config['max_open_trades']:
|
if open_trades >= self.config['max_open_trades']:
|
||||||
logger.warning('Can\'t open a new trade: max number of trades is reached')
|
logger.warning('Can\'t open a new trade: max number of trades is reached')
|
||||||
return None
|
return None
|
||||||
@ -354,7 +359,8 @@ class FreqtradeBot(object):
|
|||||||
whitelist = copy.deepcopy(self.config['exchange']['pair_whitelist'])
|
whitelist = copy.deepcopy(self.config['exchange']['pair_whitelist'])
|
||||||
|
|
||||||
# Remove currently opened and latest pairs from whitelist
|
# Remove currently opened and latest pairs from whitelist
|
||||||
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
|
for trade in Trade.query.filter(Trade.bot_id == self.config.get('bot_id', 0)).\
|
||||||
|
filter(Trade.is_open.is_(True)).all():
|
||||||
if trade.pair in whitelist:
|
if trade.pair in whitelist:
|
||||||
whitelist.remove(trade.pair)
|
whitelist.remove(trade.pair)
|
||||||
logger.debug('Ignoring %s in pair whitelist', trade.pair)
|
logger.debug('Ignoring %s in pair whitelist', trade.pair)
|
||||||
@ -553,7 +559,8 @@ class FreqtradeBot(object):
|
|||||||
buy_timeoutthreashold = arrow.utcnow().shift(minutes=-buy_timeout).datetime
|
buy_timeoutthreashold = arrow.utcnow().shift(minutes=-buy_timeout).datetime
|
||||||
sell_timeoutthreashold = arrow.utcnow().shift(minutes=-sell_timeout).datetime
|
sell_timeoutthreashold = arrow.utcnow().shift(minutes=-sell_timeout).datetime
|
||||||
|
|
||||||
for trade in Trade.query.filter(Trade.open_order_id.isnot(None)).all():
|
for trade in Trade.query.filter(Trade.bot_id == self.config.get('bot_id', 0)).\
|
||||||
|
filter(Trade.open_order_id.isnot(None)).all():
|
||||||
try:
|
try:
|
||||||
# FIXME: Somehow the query above returns results
|
# FIXME: Somehow the query above returns results
|
||||||
# where the open_order_id is in fact None.
|
# where the open_order_id is in fact None.
|
||||||
|
@ -57,7 +57,7 @@ def init(config: Dict) -> None:
|
|||||||
|
|
||||||
# Clean dry_run DB if the db is not in-memory
|
# Clean dry_run DB if the db is not in-memory
|
||||||
if config.get('dry_run', False) and db_url != 'sqlite://':
|
if config.get('dry_run', False) and db_url != 'sqlite://':
|
||||||
clean_dry_run_db()
|
clean_dry_run_db(config.get('bot_id', 0))
|
||||||
|
|
||||||
|
|
||||||
def has_column(columns, searchname: str) -> bool:
|
def has_column(columns, searchname: str) -> bool:
|
||||||
@ -129,6 +129,21 @@ def check_migrate(engine) -> None:
|
|||||||
inspector = inspect(engine)
|
inspector = inspect(engine)
|
||||||
cols = inspector.get_columns('trades')
|
cols = inspector.get_columns('trades')
|
||||||
|
|
||||||
|
# backwards compatible
|
||||||
|
if not has_column(cols, 'open_rate_requested'):
|
||||||
|
engine.execute("alter table trades add open_rate_requested float")
|
||||||
|
if not has_column(cols, 'close_rate_requested'):
|
||||||
|
engine.execute("alter table trades add close_rate_requested float")
|
||||||
|
if not has_column(cols, 'stop_loss'):
|
||||||
|
engine.execute("alter table trades add stop_loss float")
|
||||||
|
if not has_column(cols, 'initial_stop_loss'):
|
||||||
|
engine.execute("alter table trades add initial_stop_loss float")
|
||||||
|
if not has_column(cols, 'max_rate'):
|
||||||
|
engine.execute("alter table trades add max_rate float")
|
||||||
|
if not has_column(cols, 'bot_id'):
|
||||||
|
engine.execute("alter table trades add bot_id integer")
|
||||||
|
engine.execute("create index bot_id_idx ON trades (bot_id);")
|
||||||
|
|
||||||
|
|
||||||
def cleanup() -> None:
|
def cleanup() -> None:
|
||||||
"""
|
"""
|
||||||
@ -138,12 +153,13 @@ def cleanup() -> None:
|
|||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
|
|
||||||
|
|
||||||
def clean_dry_run_db() -> None:
|
def clean_dry_run_db(bot_id: int = 0) -> None:
|
||||||
"""
|
"""
|
||||||
Remove open_order_id from a Dry_run DB
|
Remove open_order_id from a Dry_run DB
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
for trade in Trade.query.filter(Trade.open_order_id.isnot(None)).all():
|
for trade in Trade.query.filter(Trade.bot_id == bot_id).\
|
||||||
|
filter(Trade.open_order_id.isnot(None)).all():
|
||||||
# Check we are updating only a dry_run order not a prod one
|
# Check we are updating only a dry_run order not a prod one
|
||||||
if 'dry_run' in trade.open_order_id:
|
if 'dry_run' in trade.open_order_id:
|
||||||
trade.open_order_id = None
|
trade.open_order_id = None
|
||||||
@ -156,6 +172,7 @@ class Trade(_DECL_BASE):
|
|||||||
__tablename__ = 'trades'
|
__tablename__ = 'trades'
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
|
bot_id = Column(Integer, default=0, index=True)
|
||||||
exchange = Column(String, nullable=False)
|
exchange = Column(String, nullable=False)
|
||||||
pair = Column(String, nullable=False, index=True)
|
pair = Column(String, nullable=False, index=True)
|
||||||
is_open = Column(Boolean, nullable=False, default=True, index=True)
|
is_open = Column(Boolean, nullable=False, default=True, index=True)
|
||||||
|
@ -62,6 +62,7 @@ class RPC(object):
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
self._freqtrade = freqtrade
|
self._freqtrade = freqtrade
|
||||||
|
self.bot_id = freqtrade.config.get('bot_id', 0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@ -82,7 +83,8 @@ class RPC(object):
|
|||||||
a remotely exposed function
|
a remotely exposed function
|
||||||
"""
|
"""
|
||||||
# Fetch open trade
|
# Fetch open trade
|
||||||
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
trades = Trade.query.filter(Trade.bot_id == self.bot_id).\
|
||||||
|
filter(Trade.is_open.is_(True)).all()
|
||||||
if self._freqtrade.state != State.RUNNING:
|
if self._freqtrade.state != State.RUNNING:
|
||||||
raise RPCException('trader is not running')
|
raise RPCException('trader is not running')
|
||||||
elif not trades:
|
elif not trades:
|
||||||
@ -116,7 +118,8 @@ class RPC(object):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
def _rpc_status_table(self) -> DataFrame:
|
def _rpc_status_table(self) -> DataFrame:
|
||||||
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
trades = Trade.query.filter(Trade.bot_id == self.bot_id).\
|
||||||
|
filter(Trade.is_open.is_(True)).all()
|
||||||
if self._freqtrade.state != State.RUNNING:
|
if self._freqtrade.state != State.RUNNING:
|
||||||
raise RPCException('trader is not running')
|
raise RPCException('trader is not running')
|
||||||
elif not trades:
|
elif not trades:
|
||||||
@ -188,7 +191,8 @@ class RPC(object):
|
|||||||
def _rpc_trade_statistics(
|
def _rpc_trade_statistics(
|
||||||
self, stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]:
|
self, stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]:
|
||||||
""" Returns cumulative profit statistics """
|
""" Returns cumulative profit statistics """
|
||||||
trades = Trade.query.order_by(Trade.id).all()
|
trades = Trade.query.filter(Trade.bot_id == self.bot_id).\
|
||||||
|
order_by(Trade.id).all()
|
||||||
|
|
||||||
profit_all_coin = []
|
profit_all_coin = []
|
||||||
profit_all_percent = []
|
profit_all_percent = []
|
||||||
@ -357,12 +361,13 @@ class RPC(object):
|
|||||||
|
|
||||||
if trade_id == 'all':
|
if trade_id == 'all':
|
||||||
# Execute sell for all open orders
|
# Execute sell for all open orders
|
||||||
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
|
for trade in Trade.query.filter(Trade.bot_id == self.bot_id).\
|
||||||
|
filter(Trade.is_open.is_(True)).all():
|
||||||
_exec_forcesell(trade)
|
_exec_forcesell(trade)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Query for trade
|
# Query for trade
|
||||||
trade = Trade.query.filter(
|
trade = Trade.query.filter(Trade.bot_id == self.bot_id).filter(
|
||||||
sql.and_(
|
sql.and_(
|
||||||
Trade.id == trade_id,
|
Trade.id == trade_id,
|
||||||
Trade.is_open.is_(True)
|
Trade.is_open.is_(True)
|
||||||
@ -400,4 +405,5 @@ class RPC(object):
|
|||||||
if self._freqtrade.state != State.RUNNING:
|
if self._freqtrade.state != State.RUNNING:
|
||||||
raise RPCException('trader is not running')
|
raise RPCException('trader is not running')
|
||||||
|
|
||||||
return Trade.query.filter(Trade.is_open.is_(True)).all()
|
return Trade.query.filter(Trade.bot_id == self.bot_id).\
|
||||||
|
filter(Trade.is_open.is_(True)).all()
|
||||||
|
Loading…
Reference in New Issue
Block a user