Merge pull request #5299 from kevinjulian/feat/kevinjulian/add-buy-signal-name
Add buy signal name
This commit is contained in:
@@ -30,7 +30,7 @@ BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
|
||||
'fee_open', 'fee_close', 'trade_duration',
|
||||
'profit_ratio', 'profit_abs', 'sell_reason',
|
||||
'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs',
|
||||
'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', ]
|
||||
'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag']
|
||||
|
||||
|
||||
def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str:
|
||||
|
@@ -3,5 +3,5 @@ from freqtrade.enums.backteststate import BacktestState
|
||||
from freqtrade.enums.rpcmessagetype import RPCMessageType
|
||||
from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode
|
||||
from freqtrade.enums.selltype import SellType
|
||||
from freqtrade.enums.signaltype import SignalType
|
||||
from freqtrade.enums.signaltype import SignalTagType, SignalType
|
||||
from freqtrade.enums.state import State
|
||||
|
@@ -7,3 +7,10 @@ class SignalType(Enum):
|
||||
"""
|
||||
BUY = "buy"
|
||||
SELL = "sell"
|
||||
|
||||
|
||||
class SignalTagType(Enum):
|
||||
"""
|
||||
Enum for signal columns
|
||||
"""
|
||||
BUY_TAG = "buy_tag"
|
||||
|
@@ -420,20 +420,24 @@ class FreqtradeBot(LoggingMixin):
|
||||
return False
|
||||
|
||||
# running get_signal on historical data fetched
|
||||
(buy, sell) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df)
|
||||
(buy, sell, buy_tag) = self.strategy.get_signal(
|
||||
pair,
|
||||
self.strategy.timeframe,
|
||||
analyzed_df
|
||||
)
|
||||
|
||||
if buy and not sell:
|
||||
stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge)
|
||||
|
||||
bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {})
|
||||
if ((bid_check_dom.get('enabled', False)) and
|
||||
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
|
||||
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
|
||||
if self._check_depth_of_market_buy(pair, bid_check_dom):
|
||||
return self.execute_buy(pair, stake_amount)
|
||||
return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
|
||||
else:
|
||||
return False
|
||||
|
||||
return self.execute_buy(pair, stake_amount)
|
||||
return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -462,7 +466,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
return False
|
||||
|
||||
def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None,
|
||||
forcebuy: bool = False) -> bool:
|
||||
forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool:
|
||||
"""
|
||||
Executes a limit buy for the given pair
|
||||
:param pair: pair for which we want to create a LIMIT_BUY
|
||||
@@ -565,6 +569,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
exchange=self.exchange.id,
|
||||
open_order_id=order_id,
|
||||
strategy=self.strategy.get_strategy_name(),
|
||||
buy_tag=buy_tag,
|
||||
timeframe=timeframe_to_minutes(self.config['timeframe'])
|
||||
)
|
||||
trade.orders.append(order_obj)
|
||||
@@ -590,6 +595,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
msg = {
|
||||
'trade_id': trade.id,
|
||||
'type': RPCMessageType.BUY,
|
||||
'buy_tag': trade.buy_tag,
|
||||
'exchange': self.exchange.name.capitalize(),
|
||||
'pair': trade.pair,
|
||||
'limit': trade.open_rate,
|
||||
@@ -614,6 +620,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
msg = {
|
||||
'trade_id': trade.id,
|
||||
'type': RPCMessageType.BUY_CANCEL,
|
||||
'buy_tag': trade.buy_tag,
|
||||
'exchange': self.exchange.name.capitalize(),
|
||||
'pair': trade.pair,
|
||||
'limit': trade.open_rate,
|
||||
@@ -634,6 +641,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
msg = {
|
||||
'trade_id': trade.id,
|
||||
'type': RPCMessageType.BUY_FILL,
|
||||
'buy_tag': trade.buy_tag,
|
||||
'exchange': self.exchange.name.capitalize(),
|
||||
'pair': trade.pair,
|
||||
'open_rate': trade.open_rate,
|
||||
@@ -692,7 +700,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
|
||||
self.strategy.timeframe)
|
||||
|
||||
(buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.timeframe, analyzed_df)
|
||||
(buy, sell, _) = self.strategy.get_signal(
|
||||
trade.pair,
|
||||
self.strategy.timeframe,
|
||||
analyzed_df
|
||||
)
|
||||
|
||||
logger.debug('checking sell')
|
||||
sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell")
|
||||
|
@@ -43,6 +43,7 @@ CLOSE_IDX = 3
|
||||
SELL_IDX = 4
|
||||
LOW_IDX = 5
|
||||
HIGH_IDX = 6
|
||||
BUY_TAG_IDX = 7
|
||||
|
||||
|
||||
class Backtesting:
|
||||
@@ -217,9 +218,13 @@ class Backtesting:
|
||||
for pair, pair_data in processed.items():
|
||||
self.check_abort()
|
||||
self.progress.increment()
|
||||
has_buy_tag = 'buy_tag' in pair_data
|
||||
headers = headers + ['buy_tag'] if has_buy_tag else headers
|
||||
if not pair_data.empty:
|
||||
pair_data.loc[:, 'buy'] = 0 # cleanup if buy_signal is exist
|
||||
pair_data.loc[:, 'sell'] = 0 # cleanup if sell_signal is exist
|
||||
if has_buy_tag:
|
||||
pair_data.loc[:, 'buy_tag'] = None # cleanup if buy_tag is exist
|
||||
|
||||
df_analyzed = self.strategy.advise_sell(
|
||||
self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy()
|
||||
@@ -230,6 +235,8 @@ class Backtesting:
|
||||
# from the previous candle
|
||||
df_analyzed.loc[:, 'buy'] = df_analyzed.loc[:, 'buy'].shift(1)
|
||||
df_analyzed.loc[:, 'sell'] = df_analyzed.loc[:, 'sell'].shift(1)
|
||||
if has_buy_tag:
|
||||
df_analyzed.loc[:, 'buy_tag'] = df_analyzed.loc[:, 'buy_tag'].shift(1)
|
||||
|
||||
df_analyzed.drop(df_analyzed.head(1).index, inplace=True)
|
||||
|
||||
@@ -264,7 +271,7 @@ class Backtesting:
|
||||
# Worst case: price reaches stop_positive_offset and dives down.
|
||||
stop_rate = (sell_row[OPEN_IDX] *
|
||||
(1 + abs(self.strategy.trailing_stop_positive_offset) -
|
||||
abs(self.strategy.trailing_stop_positive)))
|
||||
abs(self.strategy.trailing_stop_positive)))
|
||||
else:
|
||||
# Worst case: price ticks tiny bit above open and dives down.
|
||||
stop_rate = sell_row[OPEN_IDX] * (1 - abs(trade.stop_loss_pct))
|
||||
@@ -360,6 +367,7 @@ class Backtesting:
|
||||
|
||||
if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount):
|
||||
# Enter trade
|
||||
has_buy_tag = len(row) >= BUY_TAG_IDX + 1
|
||||
trade = LocalTrade(
|
||||
pair=pair,
|
||||
open_rate=row[OPEN_IDX],
|
||||
@@ -369,6 +377,7 @@ class Backtesting:
|
||||
fee_open=self.fee,
|
||||
fee_close=self.fee,
|
||||
is_open=True,
|
||||
buy_tag=row[BUY_TAG_IDX] if has_buy_tag else None,
|
||||
exchange='backtesting',
|
||||
)
|
||||
return trade
|
||||
|
@@ -47,6 +47,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
|
||||
min_rate = get_column_def(cols, 'min_rate', 'null')
|
||||
sell_reason = get_column_def(cols, 'sell_reason', 'null')
|
||||
strategy = get_column_def(cols, 'strategy', 'null')
|
||||
buy_tag = get_column_def(cols, 'buy_tag', 'null')
|
||||
# If ticker-interval existed use that, else null.
|
||||
if has_column(cols, 'ticker_interval'):
|
||||
timeframe = get_column_def(cols, 'timeframe', 'ticker_interval')
|
||||
@@ -80,7 +81,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
|
||||
stake_amount, amount, amount_requested, open_date, close_date, open_order_id,
|
||||
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
|
||||
stoploss_order_id, stoploss_last_update,
|
||||
max_rate, min_rate, sell_reason, sell_order_status, strategy,
|
||||
max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_tag,
|
||||
timeframe, open_trade_value, close_profit_abs
|
||||
)
|
||||
select id, lower(exchange),
|
||||
@@ -103,7 +104,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
|
||||
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
|
||||
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
|
||||
{sell_order_status} sell_order_status,
|
||||
{strategy} strategy, {timeframe} timeframe,
|
||||
{strategy} strategy, {buy_tag} buy_tag, {timeframe} timeframe,
|
||||
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs
|
||||
from {table_back_name}
|
||||
"""))
|
||||
@@ -160,7 +161,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
|
||||
table_back_name = get_backup_name(tabs, 'trades_bak')
|
||||
|
||||
# Check for latest column
|
||||
if not has_column(cols, 'open_trade_value'):
|
||||
if not has_column(cols, 'buy_tag'):
|
||||
logger.info(f'Running database migration for trades - backup: {table_back_name}')
|
||||
migrate_trades_table(decl_base, inspector, engine, table_back_name, cols)
|
||||
# Reread columns - the above recreated the table!
|
||||
|
@@ -257,6 +257,7 @@ class LocalTrade():
|
||||
sell_reason: str = ''
|
||||
sell_order_status: str = ''
|
||||
strategy: str = ''
|
||||
buy_tag: Optional[str] = None
|
||||
timeframe: Optional[int] = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@@ -288,6 +289,7 @@ class LocalTrade():
|
||||
'amount_requested': round(self.amount_requested, 8) if self.amount_requested else None,
|
||||
'stake_amount': round(self.stake_amount, 8),
|
||||
'strategy': self.strategy,
|
||||
'buy_tag': self.buy_tag,
|
||||
'timeframe': self.timeframe,
|
||||
|
||||
'fee_open': self.fee_open,
|
||||
@@ -636,7 +638,7 @@ class LocalTrade():
|
||||
|
||||
# skip case if trailing-stop changed the stoploss already.
|
||||
if (trade.stop_loss == trade.initial_stop_loss
|
||||
and trade.initial_stop_loss_pct != desired_stoploss):
|
||||
and trade.initial_stop_loss_pct != desired_stoploss):
|
||||
# Stoploss value got changed
|
||||
|
||||
logger.info(f"Stoploss for {trade} needs adjustment...")
|
||||
@@ -703,6 +705,7 @@ class Trade(_DECL_BASE, LocalTrade):
|
||||
sell_reason = Column(String(100), nullable=True)
|
||||
sell_order_status = Column(String(100), nullable=True)
|
||||
strategy = Column(String(100), nullable=True)
|
||||
buy_tag = Column(String(100), nullable=True)
|
||||
timeframe = Column(Integer, nullable=True)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
@@ -151,6 +151,7 @@ class TradeSchema(BaseModel):
|
||||
amount_requested: float
|
||||
stake_amount: float
|
||||
strategy: str
|
||||
buy_tag: Optional[str]
|
||||
timeframe: int
|
||||
fee_open: Optional[float]
|
||||
fee_open_cost: Optional[float]
|
||||
|
@@ -208,15 +208,25 @@ class Telegram(RPCHandler):
|
||||
else:
|
||||
msg['stake_amount_fiat'] = 0
|
||||
|
||||
message = (f"\N{LARGE BLUE CIRCLE} *{msg['exchange']}:* Buying {msg['pair']}"
|
||||
f" (#{msg['trade_id']})\n"
|
||||
f"*Amount:* `{msg['amount']:.8f}`\n"
|
||||
f"*Open Rate:* `{msg['limit']:.8f}`\n"
|
||||
f"*Current Rate:* `{msg['current_rate']:.8f}`\n"
|
||||
f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}")
|
||||
|
||||
content = []
|
||||
content.append(
|
||||
f"\N{LARGE BLUE CIRCLE} *{msg['exchange']}:* Buying {msg['pair']}"
|
||||
f" (#{msg['trade_id']})\n"
|
||||
)
|
||||
if msg.get('buy_tag', None):
|
||||
content.append(f"*Buy Tag:* `{msg['buy_tag']}`\n")
|
||||
content.append(f"*Amount:* `{msg['amount']:.8f}`\n")
|
||||
content.append(f"*Open Rate:* `{msg['limit']:.8f}`\n")
|
||||
content.append(f"*Current Rate:* `{msg['current_rate']:.8f}`\n")
|
||||
content.append(
|
||||
f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}"
|
||||
)
|
||||
if msg.get('fiat_currency', None):
|
||||
message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
|
||||
content.append(
|
||||
f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
|
||||
)
|
||||
|
||||
message = ''.join(content)
|
||||
message += ")`"
|
||||
return message
|
||||
|
||||
@@ -354,6 +364,7 @@ class Telegram(RPCHandler):
|
||||
"*Trade ID:* `{trade_id}` `(since {open_date_hum})`",
|
||||
"*Current Pair:* {pair}",
|
||||
"*Amount:* `{amount} ({stake_amount} {base_currency})`",
|
||||
"*Buy Tag:* `{buy_tag}`" if r['buy_tag'] else "",
|
||||
"*Open Rate:* `{open_rate:.8f}`",
|
||||
"*Close Rate:* `{close_rate}`" if r['close_rate'] else "",
|
||||
"*Current Rate:* `{current_rate:.8f}`",
|
||||
|
@@ -13,7 +13,7 @@ from pandas import DataFrame
|
||||
|
||||
from freqtrade.constants import ListPairsWithTimeframes
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.enums import SellType, SignalType
|
||||
from freqtrade.enums import SellType, SignalTagType, SignalType
|
||||
from freqtrade.exceptions import OperationalException, StrategyError
|
||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
||||
from freqtrade.exchange.exchange import timeframe_to_next_date
|
||||
@@ -422,6 +422,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
logger.debug("Skipping TA Analysis for already analyzed candle")
|
||||
dataframe['buy'] = 0
|
||||
dataframe['sell'] = 0
|
||||
dataframe['buy_tag'] = None
|
||||
|
||||
# Other Defs in strategy that want to be called every loop here
|
||||
# twitter_sell = self.watch_twitter_feed(dataframe, metadata)
|
||||
@@ -496,7 +497,12 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
else:
|
||||
raise StrategyError(message)
|
||||
|
||||
def get_signal(self, pair: str, timeframe: str, dataframe: DataFrame) -> Tuple[bool, bool]:
|
||||
def get_signal(
|
||||
self,
|
||||
pair: str,
|
||||
timeframe: str,
|
||||
dataframe: DataFrame
|
||||
) -> Tuple[bool, bool, Optional[str]]:
|
||||
"""
|
||||
Calculates current signal based based on the buy / sell columns of the dataframe.
|
||||
Used by Bot to get the signal to buy or sell
|
||||
@@ -507,7 +513,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
"""
|
||||
if not isinstance(dataframe, DataFrame) or dataframe.empty:
|
||||
logger.warning(f'Empty candle (OHLCV) data for pair {pair}')
|
||||
return False, False
|
||||
return False, False, None
|
||||
|
||||
latest_date = dataframe['date'].max()
|
||||
latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1]
|
||||
@@ -522,9 +528,12 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
'Outdated history for pair %s. Last tick is %s minutes old',
|
||||
pair, int((arrow.utcnow() - latest_date).total_seconds() // 60)
|
||||
)
|
||||
return False, False
|
||||
return False, False, None
|
||||
|
||||
buy = latest[SignalType.BUY.value] == 1
|
||||
sell = latest[SignalType.SELL.value] == 1
|
||||
buy_tag = latest.get(SignalTagType.BUY_TAG.value, None)
|
||||
|
||||
(buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1
|
||||
logger.debug('trigger: %s (pair=%s) buy=%s sell=%s',
|
||||
latest['date'], pair, str(buy), str(sell))
|
||||
timeframe_seconds = timeframe_to_seconds(timeframe)
|
||||
@@ -532,8 +541,8 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
current_time=datetime.now(timezone.utc),
|
||||
timeframe_seconds=timeframe_seconds,
|
||||
buy=buy):
|
||||
return False, sell
|
||||
return buy, sell
|
||||
return False, sell, buy_tag
|
||||
return buy, sell, buy_tag
|
||||
|
||||
def ignore_expired_candle(self, latest_date: datetime, current_time: datetime,
|
||||
timeframe_seconds: int, buy: bool):
|
||||
|
Reference in New Issue
Block a user