From 6748e897a54b41fbe4e465247b1b464278155647 Mon Sep 17 00:00:00 2001 From: creslin Date: Tue, 31 Jul 2018 20:21:01 +0000 Subject: [PATCH] Custom_Orders - Demo. Early demonstration of Strategy controlling Order Type on the Exchange, Stop / Stop_limit / Market / limit etc The stake The price to submit Demo allows multiple orders to be placed, so ladding buys down to a fib Or buying and setting stop-loss Or Take profit in stages etc. Included a strategy demo with stubs for limit, market, take_profit and stop_limit orders Obviously very early code. Submitted as buys, stops etc are working / showing in GDAX. and we have hte order IDs to update trade table. --- freqtrade/custom_orders_strat.py | 296 +++++++++++++++++++++++++++++++ freqtrade/exchange/__init__.py | 75 +++++++- freqtrade/freqtradebot.py | 107 ++++++----- freqtrade/strategy/interface.py | 93 ++++++---- 4 files changed, 487 insertions(+), 84 deletions(-) create mode 100644 freqtrade/custom_orders_strat.py diff --git a/freqtrade/custom_orders_strat.py b/freqtrade/custom_orders_strat.py new file mode 100644 index 000000000..5a054c37a --- /dev/null +++ b/freqtrade/custom_orders_strat.py @@ -0,0 +1,296 @@ +# --- Do not remove these libs --- +from freqtrade.strategy.interface import IStrategy +from pandas import DataFrame +import talib.abstract as ta + +from datetime import datetime +import time +import timeit + + +# -------------------------------- +class customorders(IStrategy): + """ + author@: Creslin + + """ + minimal_roi = { + "0": 0.20 + } + # Optimal stoploss designed for the strategy + stoploss = -0.01 + + # Optimal ticker interval for the strategy + ticker_interval = '5m' + + def get_ticker_indicator(self): + return int(self.ticker_interval[:-1]) + + def populate_indicators(self, dataframe: DataFrame) -> DataFrame: + # + + # dataframe['rsi'] = ta.RSI(dataframe, timeperiod=7) + time.sleep(1) + + return dataframe + + # # # If using Berlins expose pairs to strategy + # def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: + # + # # RSI + # dataframe['rsi'] = ta.RSI(dataframe, timeperiod=7) + # + # return dataframe + + ##### + # EXAMPLE Strat - buys every even minute sells every odd + #### + def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: + if int(datetime.now().strftime('%M')) % 2 == 0: #buy = 1 every even minute + print('----------------we are in strategy', datetime.now().time()) + b: float = 9999999.99 + print( "buy = 1") + else: + b = 0.00000001 + dataframe.loc[ + ( + # L-RSI When below 0.01 / vfi -26 + #(dataframe['lrsi'] < 0.5) & + #(dataframe['vfi'] < -4) + (dataframe['close'] < b ) + ), + 'buy'] = 1 + return dataframe + + ##### + # EXAMPLE Strat - buys every even minute sells every odd + #### + def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: + if int(datetime.now().strftime('%M'))% 2 != 0: ## sell = 1 every ODD minute + print('----------------we are in strategy', datetime.now().time()) + s: float = 9999999.99 + print("sell = 1") + else: + s = 0.00000001 + dataframe.loc[ + ( + # vfi 0.99 + #(dataframe['vfi'] > 0.41) + (dataframe['close'] < s ) + ), + 'sell'] = 1 + return dataframe + + def stop_stops_plugin(self, dataframe: DataFrame, pair: str) -> DataFrame: + """ + stop_stops_pligin. To stop a buy where X trades from Y have stopped out. + i.e to remove a pair from being bought that is on a losing streak. + + Only processed if buy=1 + """ + """ + Called before a trade executes if exists, + :param dataframe: + :param pair: + :return: + """ + + from freqtrade.persistence import Trade + import pandas as pd + df = dataframe + + # Only check buys, else return DF untouched + + if df.iloc[-1]['buy'] == 1: + + print("STOP_STOPS STOP_STOPS STOP_STOPS STOP_STOP STOP_STOPS STOP_STOP STOP_STOPS") + + from pandas import set_option + set_option('display.max_rows', 2000) + set_option('display.max_columns', 8) + + def closed_pair_trades(pair): + """ + To return list of closed trades for a pair + enriched with open/closd dates, profit, and stake + + :param pair: + :return: df_c pair open, close, profit, stake + """ + pair = pair + df_c = pd.DataFrame(columns=['pair', 'profit', 'open_date', 'close_date', 'stake']) + + pair_trades = Trade.query.filter(Trade.pair.is_(pair)).all() + for pair_trade in pair_trades: + if pair_trade.is_open == False: + # print("closed trade for", pair, "closed with ", pair_trade.close_profit ) + + p = pair + pcp = pair_trade.close_profit + od = pair_trade.open_date + cd = pair_trade.close_date + sa = pair_trade.stake_amount + + df_c = df_c.append([{'pair':p, 'profit':pcp, 'open_date':od, + 'close_date':cd, 'stake':sa}], ignore_index=True) + return df_c + + def has_past_perf(df_c, lost_the_last=1, out_of=1): + """ + Return if pair lost_the_last X trades out_of Y trades + :param df_c: dataframe of closed trades for the pair + :param lost_the_last: number of bad trades to look for + :param out_of: within the last number over trades closed + :return: bool + """ + lost_the_last =lost_the_last + out_of = out_of + df_c = df_c + + df_c_tail = df_c.tail(out_of) + lost_from_last_count = df_c_tail[df_c_tail['profit'] < 0].shapec + if lost_from_last_count[0] >= lost_the_last: + return True + else: + return False + + # load this pairs closed trades into a df + # df_c = closed_pair_trades(pair=pair) + # print("df_c", df_c) + + # Get bool if on pairs past profitable trades count, cancel the buy if limit hit + # past_perf = has_past_perf(df_c, lost_the_last=9, out_of=10) + # if past_perf: + # #set buy to 0 + # print("STOP STOP SETTING BUY TO ZERO ") + # df.loc['buy'] = 0 + + dataframe = df + return dataframe + + #def money_mgt_plugin(self, dataframe: DataFrame, pair: str) -> DataFrame: + def money_mgt_plugin(self, dataframe: DataFrame, pair: str) -> DataFrame: + """ + pre_trade_mgt. + To allow money mamagement and or other checks to be put against dataframe + with buy or sell set to "1" + + Returns a list of custom order trades. + Each custom order to include, above the needed fields for exchange + + dict['order_type'] = 'a_open_limit' + + order_types are later ordered and executed in alphabetical order. + order to enter market are executed before stops and take profits are played in. + Supported types are: + - a_open_limit # enter market with limit order + - a_open_market # enter market with market order + - b_stop_limit # set a stop_limit order + - b_stop_market # set a stop_market order + - c_take_limit # set a take profit limit order + - c_take_market # set a take profit market order + + Multipe orders can be processed but an asset may only have the sum of its open value + in either stop or take profit orders. + + i.e we can open a $500 for 500 tokens position and have any combination of + stop or take profit orders at any prices that COMBINED do not break 500 tokens. + + "one" common scenario may be an 'a_open_limit' or 'a_open_market' order + and 'b_stop_limit' or 'b_stop_market' order to protect capital. + + """ + print("MONEY_MGT MONEY_MGT MONEY_MGT MONEY_MGT MONEY_MGT MONEY_MGT MONEY_MGT MONEY_MGT MONEY_MGT") + df = dataframe + + # If buy = 1 and Exchange is GDAX + if df.iloc[-1]['buy'] == 1: + exchange = self.config['exchange']['name'] + + # GDAX Stop-Limit Order + if exchange == "gdax": + + custom_orders = [] + limit = {} + take_profit = {} + + # Take profit Market example "b_take_market" + # open market order example "a_open_market" + + # Open limit order example + amount = 0.5 + limit_price = df.iloc[-1]['close'] * 1.05 # pay 5% for testing order are made + + limit = {} + limit["order_type"] = "a_open_limit" + limit["symbol"] = pair + limit["type"] = "limit" + limit["side"] = "buy" + limit["amount"] = amount + limit["price"] = limit_price + params: dict = {} + limit["params"] = params + + custom_orders.append(limit) + + # Market Example + market_amount = 1 + + market = {} + market_price = 1.05 # pay 5% for testing order are made + market["order_type"] = "a_open_market" + market["symbol"] = pair + market["type"] = "market" + market["side"] = "buy" + market["amount"] = market_amount + params: dict = {} + market["params"] = params + + custom_orders.append(market) + + # Stop Limit order + stop_limit = {} + stop_limit_amount = 0.01 + stop_limit_price = df.iloc[-1]['close'] * 0.90 # take 10% hard code for testing + stop_price = df.iloc[-1]['close'] * 0.95 # trigger at 7% loss, hard code for testing + + stop_limit['order_type'] = 'b_stop_limit' + stop_limit['symbol'] = pair + stop_limit['type'] = 'limit' + stop_limit['side'] = 'sell' + stop_limit['amount'] = stop_limit_amount + stop_limit['price'] = stop_limit_price + params: dict = {} + params['stop'] = 'loss' + params['stop_price'] = stop_price + stop_limit['params'] = params + + custom_orders.append(stop_limit) + + # Take profit Limit example + take_profit_limit_price = df.iloc[-1]['close'] * 1.2 # take_proft at 20% - for demo + take_profit_amount = 0.01 + + take_profit["order_type"] = "c_take_profit_limit" + take_profit["order_template"] = (pair, amount, take_profit_limit_price) + take_profit["symbol"] = pair + take_profit["type"] = "limit" + take_profit["side"] = "sell" + take_profit["amount"] = take_profit_amount + take_profit["price"] = take_profit_limit_price + take_profit_params: dict = {} + take_profit["params"] = take_profit_params + + # custom_orders.append(take_profit) + + #todo Add library of trade templates for binance, polo, gdax... + #todo Code to check have all the fields and sense check + + # Cancel Legacy buy/sell signals if there are custom orders. + # Set buy / sell = 0 if we have custom orders + dataframe.loc['buy'] = 0 + dataframe.loc['sell'] = 0 + + return dataframe, custom_orders + + return dataframe, None diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 423e38246..4cdafc333 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -91,7 +91,7 @@ class Exchange(object): 'secret': exchange_config.get('secret'), 'password': exchange_config.get('password'), 'uid': exchange_config.get('uid', ''), - 'enableRateLimit': True, + 'enableRateLimit': exchange_config.get('ccxt_rate_limit', True), }) except (KeyError, AttributeError): raise OperationalException(f'Exchange {name} is not supported') @@ -185,6 +185,79 @@ class Exchange(object): price = ceil(big_price) / pow(10, symbol_prec) return price + def submit_custom_order(self, symbol, type, side, amount, price=None, params={}): + pair = symbol + rate = price + try: + + return self._api.createOrder(symbol, type, side, amount, price, params) + + except ccxt.InsufficientFunds as e: + raise DependencyException( + f'Insufficient funds to create limit buy order on market {pair}.' + f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).' + f'Message: {e}') + + except ccxt.InvalidOrder as e: + raise DependencyException( + f'Could not create limit buy order on market {pair}.' + f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).' + f'Message: {e}') + + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not place buy order due to {e.__class__.__name__}. Message: {e}') + except ccxt.BaseError as e: + raise OperationalException(e) + + def customOrders(self, custom_orders): + ''' + To process custom orders. + These are a list of dicts of orders created in strategy. + + :param custom_orders: + :return: + ''' + sorted_custom_orders = sorted(custom_orders, key=lambda k: k['order_type']) + + for order in sorted_custom_orders: + order_type = order.get('order_type') if order.get('order_type') else "" + symbol = order.get('symbol') if order.get('symbol') else "" + type = order.get('type') if order.get('type') else "" + side = order.get('side') if order.get('side') else "" + amount = order.get('amount') if order.get('amount') else None + price = order.get('price') if order.get('price') else None + stop = order.get('stop') if order.get('stop') else "" + stop_price = order.get('stop_price') if order.get('stop_price') else None + params = order.get('params') if order.get('params') else {} + + # Set the precision for amount and price(rate) as accepted by the exchange + if amount: + float(amount) + amount = self.symbol_amount_prec(symbol, amount) + float(amount) + + if price: + float(price) + price = self.symbol_price_prec(symbol, price) + float(price) + + if params.get('stop_price'): + stop_price = params['stop_price'] + float(stop_price) + params['stop_price'] = self.symbol_price_prec(symbol, stop_price) + float(stop_price) + + # destroy the params dict if its empty, not to pass to CCXT + if not params: + params = "" + + # create_order(self, symbol, type, side, amount, price=None, params={}) + print(symbol, type, side, amount, price, params) + custom_order_id = self.submit_custom_order(symbol=symbol, type=type, side=side, + amount=amount, price=price, params=params) + print("custom order_id is", custom_order_id) + def buy(self, pair: str, rate: float, amount: float) -> Dict: if self._conf['dry_run']: order_id = f'dry_run_buy_{randint(0, 10**6)}' diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 46fbb3a38..a4a519cfa 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -330,14 +330,15 @@ class FreqtradeBot(object): # Pick pair based on buy signals for _pair in whitelist: - thistory = self.exchange.get_ticker_history(_pair, interval) - (buy, sell) = self.strategy.get_signal(_pair, interval, thistory) + (buy, sell, custom_orders) = self.strategy.get_signal(self.exchange, _pair, interval) if buy and not sell: - return self.execute_buy(_pair, stake_amount) + return self.execute_buy(_pair, stake_amount, custom_orders) + if custom_orders: + return self.execute_buy(_pair, stake_amount, custom_orders) return False - def execute_buy(self, pair: str, stake_amount: float) -> bool: + def execute_buy(self, pair: str, stake_amount: float, custom_orders: list) -> bool: """ Executes a limit buy for the given pair :param pair: pair for which we want to create a LIMIT_BUY @@ -352,46 +353,63 @@ class FreqtradeBot(object): buy_limit = self.get_target_bid(self.exchange.get_ticker(pair)) min_stake_amount = self._get_min_pair_stake_amount(pair_s, buy_limit) - if min_stake_amount is not None and min_stake_amount > stake_amount: - logger.warning( - f'Can\'t open a new trade for {pair_s}: stake amount' - f' is too small ({stake_amount} < {min_stake_amount})' + + # if custom trades are set over_ride stake_amount test + + if custom_orders: + # todo condider how or if to do this for limit, market, stop cutom_orders. + logger.info("bypassing stake amount test for custom_orders") + else: + if min_stake_amount is not None and min_stake_amount > stake_amount: + logger.warning( + f'Can\'t open a new trade for {pair_s}: stake amount' + f' is too small ({stake_amount} < {min_stake_amount})' + ) + return False + + # Call exchange to make order or customOrder + if custom_orders: + # Call custom_oders in exchange/__init__.py + logger.info("Calling customOrders in exchange") + order_ids = self.exchange.customOrders(custom_orders) + + else: + + amount = stake_amount / buy_limit + + order_id = self.exchange.buy(pair, buy_limit, amount)['id'] + + self.rpc.send_msg({ + 'type': RPCMessageType.BUY_NOTIFICATION, + 'exchange': self.exchange.name.capitalize(), + 'pair': pair_s, + 'market_url': pair_url, + 'limit': buy_limit, + 'stake_amount': stake_amount, + 'stake_currency': stake_currency, + 'fiat_currency': fiat_currency + }) + # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL + fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') + trade = Trade( + pair=pair, + stake_amount=stake_amount, + amount=amount, + fee_open=fee, + fee_close=fee, + open_rate=buy_limit, + open_rate_requested=buy_limit, + open_date=datetime.utcnow(), + exchange=self.exchange.id, + open_order_id=order_id, + strategy=self.strategy.get_strategy_name(), + ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']] ) - return False + Trade.session.add(trade) + Trade.session.flush() + return True - amount = stake_amount / buy_limit - - order_id = self.exchange.buy(pair, buy_limit, amount)['id'] - - self.rpc.send_msg({ - 'type': RPCMessageType.BUY_NOTIFICATION, - 'exchange': self.exchange.name.capitalize(), - 'pair': pair_s, - 'market_url': pair_url, - 'limit': buy_limit, - 'stake_amount': stake_amount, - 'stake_currency': stake_currency, - 'fiat_currency': fiat_currency - }) - # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL - fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') - trade = Trade( - pair=pair, - stake_amount=stake_amount, - amount=amount, - fee_open=fee, - fee_close=fee, - open_rate=buy_limit, - open_rate_requested=buy_limit, - open_date=datetime.utcnow(), - exchange=self.exchange.id, - open_order_id=order_id, - strategy=self.strategy.get_strategy_name(), - ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']] - ) - Trade.session.add(trade) - Trade.session.flush() - return True + return False def process_maybe_execute_buy(self) -> bool: """ @@ -497,9 +515,8 @@ class FreqtradeBot(object): (buy, sell) = (False, False) experimental = self.config.get('experimental', {}) if experimental.get('use_sell_signal') or experimental.get('ignore_roi_if_buy_signal'): - ticker = self.exchange.get_ticker_history(trade.pair, self.strategy.ticker_interval) - (buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.ticker_interval, - ticker) + (buy, sell, custom_orders) = self.strategy.get_signal(self.exchange, + trade.pair, self.strategy.ticker_interval) should_sell = self.strategy.should_sell(trade, current_rate, datetime.utcnow(), buy, sell) if should_sell.sell_flag: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index dfd624393..51849f7d1 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -14,6 +14,7 @@ from pandas import DataFrame from freqtrade import constants from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe +from freqtrade.exchange import Exchange from freqtrade.persistence import Trade logger = logging.getLogger(__name__) @@ -74,29 +75,29 @@ class IStrategy(ABC): self.config = config @abstractmethod - def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Populate indicators that will be used in the Buy and Sell strategy :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() - :param metadata: Additional information, like the currently traded pair + :param pair: Pair currently analyzed :return: a Dataframe with all mandatory indicators for the strategies """ @abstractmethod - def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def populate_buy_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame - :param metadata: Additional information, like the currently traded pair + :param pair: Pair currently analyzed :return: DataFrame with buy column """ @abstractmethod - def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame - :param metadata: Additional information, like the currently traded pair + :param pair: Pair currently analyzed :return: DataFrame with sell column """ @@ -106,31 +107,51 @@ class IStrategy(ABC): """ return self.__class__.__name__ - def analyze_ticker(self, ticker_history: List[Dict], metadata: dict) -> DataFrame: + def analyze_ticker(self, ticker_history: List[Dict], pair: str) -> DataFrame: """ Parses the given ticker history and returns a populated DataFrame add several TA indicators and buy signal to it :return DataFrame with ticker data and indicator data + :return custom_orders list of dicts containing any custom trades from strategy """ dataframe = parse_ticker_dataframe(ticker_history) - dataframe = self.advise_indicators(dataframe, metadata) - dataframe = self.advise_buy(dataframe, metadata) - dataframe = self.advise_sell(dataframe, metadata) - return dataframe + dataframe = self.advise_indicators(dataframe, pair) + dataframe = self.advise_buy(dataframe, pair) + dataframe = self.advise_sell(dataframe, pair) - def get_signal(self, pair: str, interval: str, ticker_hist: List[Dict]) -> Tuple[bool, bool]: + ## Strategy Plugin stop_stops_mgt' if in strategy. + # Analyzes stop-count of past X from Y trades, cancels BUY if True + if hasattr(self, 'stop_stops_plugin'): + dataframe = self.stop_stops_plugin(dataframe, pair) + + ## Strategy Plugin 'win_rate' if in strategy. + # Calls micro backslap on proposed buy=1 pair. If winrate is poor, cancels BUY + if hasattr(self, 'win_rate_plugin'): + dataframe = self.win_rate_plugin(dataframe, pair) + + ## Strategy Plugin 'money_mgt' if in strategy. + # Money management for any order in buy. Determines stake size, stop-loss, take-profit + # Returns dataframe, custom_orders (list of dicts) + if hasattr(self, 'money_mgt_plugin'): + dataframe, custom_orders = self.money_mgt_plugin(dataframe, pair) + + return dataframe, custom_orders + + def get_signal(self, exchange: Exchange, pair: str, interval: str) -> Tuple[bool, bool]: """ Calculates current signal based several technical analysis indicators :param pair: pair in format ANT/BTC :param interval: Interval to use (in min) - :return: (Buy, Sell) A bool-tuple indicating buy/sell signal + :return: (Buy, Sell, custom_orders) A bool-tuple indicating buy/sell signal, + and any custom_orders """ + ticker_hist = exchange.get_ticker_history(pair, interval) if not ticker_hist: logger.warning('Empty ticker history for pair %s', pair) return False, False try: - dataframe = self.analyze_ticker(ticker_hist, {'pair': pair}) + dataframe, custom_orders = self.analyze_ticker(ticker_hist, pair) except ValueError as error: logger.warning( 'Unable to analyze ticker for pair %s: %s', @@ -155,13 +176,13 @@ class IStrategy(ABC): # Check if dataframe is out of date signal_date = arrow.get(latest['date']) interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval] - if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + 5))): - logger.warning( - 'Outdated history for pair %s. Last tick is %s minutes old', - pair, - (arrow.utcnow() - signal_date).seconds // 60 - ) - return False, False + # if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + 5))): + # logger.warning( + # 'Outdated history for pair %s. Last tick is %s minutes old', + # pair, + # (arrow.utcnow() - signal_date).seconds // 60 + # ) + # return False, False (buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1 logger.debug( @@ -171,7 +192,7 @@ class IStrategy(ABC): str(buy), str(sell) ) - return buy, sell + return buy, sell, custom_orders def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, sell: bool) -> SellCheckTuple: @@ -212,7 +233,6 @@ class IStrategy(ABC): """ Based on current profit of the trade and configured (trailing) stoploss, decides to sell or not - :param current_profit: current profit in percent """ trailing_stop = self.config.get('trailing_stop', False) @@ -240,15 +260,12 @@ class IStrategy(ABC): # check if we have a special stop loss for positive condition # and if profit is positive stop_loss_value = self.stoploss - sl_offset = self.config.get('trailing_stop_positive_offset', 0.0) - - if 'trailing_stop_positive' in self.config and current_profit > sl_offset: + if 'trailing_stop_positive' in self.config and current_profit > 0: # Ignore mypy error check in configuration that this is a float stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore logger.debug(f"using positive stop loss mode: {stop_loss_value} " - f"with offset {sl_offset:.4g} " - f"since we have profit {current_profit:.4f}%") + f"since we have profit {current_profit}") trade.adjust_stop_loss(current_rate, stop_loss_value) @@ -275,15 +292,15 @@ class IStrategy(ABC): """ Creates a dataframe and populates indicators for given ticker data """ - return {pair: self.advise_indicators(parse_ticker_dataframe(pair_data), {'pair': pair}) + return {pair: self.advise_indicators(parse_ticker_dataframe(pair_data), pair) for pair, pair_data in tickerdata.items()} - def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Populate indicators that will be used in the Buy and Sell strategy This method should not be overridden. :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() - :param metadata: Additional information, like the currently traded pair + :param pair: The currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ if self._populate_fun_len == 2: @@ -291,14 +308,14 @@ class IStrategy(ABC): "the current function headers!", DeprecationWarning) return self.populate_indicators(dataframe) # type: ignore else: - return self.populate_indicators(dataframe, metadata) + return self.populate_indicators(dataframe, pair) - def advise_buy(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def advise_buy(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe This method should not be overridden. :param dataframe: DataFrame - :param pair: Additional information, like the currently traded pair + :param pair: The currently traded pair :return: DataFrame with buy column """ if self._buy_fun_len == 2: @@ -306,14 +323,14 @@ class IStrategy(ABC): "the current function headers!", DeprecationWarning) return self.populate_buy_trend(dataframe) # type: ignore else: - return self.populate_buy_trend(dataframe, metadata) + return self.populate_buy_trend(dataframe, pair) - def advise_sell(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def advise_sell(self, dataframe: DataFrame, pair: str) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe This method should not be overridden. :param dataframe: DataFrame - :param pair: Additional information, like the currently traded pair + :param pair: The currently traded pair :return: DataFrame with sell column """ if self._sell_fun_len == 2: @@ -321,4 +338,4 @@ class IStrategy(ABC): "the current function headers!", DeprecationWarning) return self.populate_sell_trend(dataframe) # type: ignore else: - return self.populate_sell_trend(dataframe, metadata) + return self.populate_sell_trend(dataframe, pair)