diff --git a/freqtrade/constants.py b/freqtrade/constants.py index b30add71b..ff19de061 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -43,6 +43,7 @@ SUPPORTED_FIAT = [ CONF_SCHEMA = { 'type': 'object', 'properties': { + 'bot_id': {'type': 'integer', 'minimum': 0}, 'max_open_trades': {'type': 'integer', 'minimum': 0}, 'ticker_interval': {'type': 'string', 'enum': list(TICKER_INTERVAL_MINUTES.keys())}, 'stake_currency': {'type': 'string', 'enum': ['BTC', 'XBT', 'ETH', 'USDT', 'EUR', 'USD']}, diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a2090d267..8f77e9887 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -122,6 +122,7 @@ class FreqtradeBot(object): stake_amount = self.config['stake_amount'] minimal_roi = self.config['minimal_roi'] ticker_interval = self.config['ticker_interval'] + max_open_trades = self.config['max_open_trades'] exchange_name = self.config['exchange']['name'] strategy_name = self.config.get('strategy', '') self.rpc.send_msg({ @@ -130,10 +131,12 @@ class FreqtradeBot(object): f'*Stake per trade:* `{stake_amount} {stake_currency}`\n' f'*Minimum ROI:* `{minimal_roi}`\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): - top_pairs = 'top ' + str(self.config.get('dynamic_whitelist', 20)) + top_pairs = 'top ' + str(self.config.get('dynamic_whitelist', 20)) +\ + ' highest volume' specific_pairs = '' else: top_pairs = 'whitelisted' @@ -181,7 +184,8 @@ class FreqtradeBot(object): self.config['exchange']['pair_whitelist'] = final_list # 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 for trade in trades: @@ -288,7 +292,8 @@ class FreqtradeBot(object): avaliable_amount = self.exchange.get_balance(self.config['stake_currency']) 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']: logger.warning('Can\'t open a new trade: max number of trades is reached') return None @@ -354,7 +359,8 @@ class FreqtradeBot(object): whitelist = copy.deepcopy(self.config['exchange']['pair_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: whitelist.remove(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 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: # FIXME: Somehow the query above returns results # where the open_order_id is in fact None. diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 80d49b895..99c2486b3 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -57,7 +57,7 @@ def init(config: Dict) -> None: # Clean dry_run DB if the db is not in-memory 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: @@ -129,6 +129,21 @@ def check_migrate(engine) -> None: inspector = inspect(engine) 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: """ @@ -138,12 +153,13 @@ def cleanup() -> None: 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 :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 if 'dry_run' in trade.open_order_id: trade.open_order_id = None @@ -156,6 +172,7 @@ class Trade(_DECL_BASE): __tablename__ = 'trades' id = Column(Integer, primary_key=True) + bot_id = Column(Integer, default=0, index=True) exchange = Column(String, nullable=False) pair = Column(String, nullable=False, index=True) is_open = Column(Boolean, nullable=False, default=True, index=True) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 80bac0dd4..02a2aa779 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -62,6 +62,7 @@ class RPC(object): :return: None """ self._freqtrade = freqtrade + self.bot_id = freqtrade.config.get('bot_id', 0) @property def name(self) -> str: @@ -82,7 +83,8 @@ class RPC(object): a remotely exposed function """ # 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: raise RPCException('trader is not running') elif not trades: @@ -116,7 +118,8 @@ class RPC(object): return results 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: raise RPCException('trader is not running') elif not trades: @@ -188,7 +191,8 @@ class RPC(object): def _rpc_trade_statistics( self, stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]: """ 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_percent = [] @@ -357,12 +361,13 @@ class RPC(object): if trade_id == 'all': # 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) return # Query for trade - trade = Trade.query.filter( + trade = Trade.query.filter(Trade.bot_id == self.bot_id).filter( sql.and_( Trade.id == trade_id, Trade.is_open.is_(True) @@ -400,4 +405,5 @@ class RPC(object): if self._freqtrade.state != State.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()