Merge pull request #8184 from LangLazy/feature

Feature market direction
This commit is contained in:
Matthias 2023-02-28 17:22:31 +01:00 committed by GitHub
commit 244fd0e731
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 109 additions and 9 deletions

View File

@ -152,7 +152,7 @@ You can create your own keyboard in `config.json`:
!!! Note "Supported Commands" !!! Note "Supported Commands"
Only the following commands are allowed. Command arguments are not supported! Only the following commands are allowed. Command arguments are not supported!
`/start`, `/stop`, `/status`, `/status table`, `/trades`, `/profit`, `/performance`, `/daily`, `/stats`, `/count`, `/locks`, `/balance`, `/stopentry`, `/reload_config`, `/show_config`, `/logs`, `/whitelist`, `/blacklist`, `/edge`, `/help`, `/version` `/start`, `/stop`, `/status`, `/status table`, `/trades`, `/profit`, `/performance`, `/daily`, `/stats`, `/count`, `/locks`, `/balance`, `/stopentry`, `/reload_config`, `/show_config`, `/logs`, `/whitelist`, `/blacklist`, `/edge`, `/help`, `/version`, `/marketdir`
## Telegram commands ## Telegram commands
@ -179,6 +179,7 @@ official commands. You can ask at any moment for help with `/help`.
| `/count` | Displays number of trades used and available | `/count` | Displays number of trades used and available
| `/locks` | Show currently locked pairs. | `/locks` | Show currently locked pairs.
| `/unlock <pair or lock_id>` | Remove the lock for this pair (or for this lock id). | `/unlock <pair or lock_id>` | Remove the lock for this pair (or for this lock id).
| `/marketdir [long | short | even | none]` | Updates the user managed variable that represents the current market direction. If no direction is provided, the currently set direction will be displayed.
| **Modify Trade states** | | **Modify Trade states** |
| `/forceexit <trade_id> | /fx <tradeid>` | Instantly exits the given trade (Ignoring `minimum_roi`). | `/forceexit <trade_id> | /fx <tradeid>` | Instantly exits the given trade (Ignoring `minimum_roi`).
| `/forceexit all | /fx all` | Instantly exits all open trades (Ignoring `minimum_roi`). | `/forceexit all | /fx all` | Instantly exits all open trades (Ignoring `minimum_roi`).
@ -416,3 +417,23 @@ ARDR/ETH 0.366667 0.143059 -0.01
### /version ### /version
> **Version:** `0.14.3` > **Version:** `0.14.3`
### /marketdir
If a market direction is provided the command updates the user managed variable that represents the current market direction.
This variable is not set to any valid market direction on bot startup and must be set by the user. The example below is for `/marketdir long`:
```
Successfully updated marketdirection from none to long.
```
If no market direction is provided the command outputs the currently set market directions. The example below is for `/marketdir`:
```
Currently set marketdirection: even
```
You can use the market direction in your strategy via `self.market_direction`.
!!! Warning "Bot restarts"
Please note that the market direction is not persisted, and will be reset after a bot restart/reload.

View File

@ -5,6 +5,7 @@ from freqtrade.enums.exitchecktuple import ExitCheckTuple
from freqtrade.enums.exittype import ExitType from freqtrade.enums.exittype import ExitType
from freqtrade.enums.hyperoptstate import HyperoptState from freqtrade.enums.hyperoptstate import HyperoptState
from freqtrade.enums.marginmode import MarginMode from freqtrade.enums.marginmode import MarginMode
from freqtrade.enums.marketstatetype import MarketDirection
from freqtrade.enums.ordertypevalue import OrderTypeValues from freqtrade.enums.ordertypevalue import OrderTypeValues
from freqtrade.enums.pricetype import PriceType from freqtrade.enums.pricetype import PriceType
from freqtrade.enums.rpcmessagetype import NO_ECHO_MESSAGES, RPCMessageType, RPCRequestType from freqtrade.enums.rpcmessagetype import NO_ECHO_MESSAGES, RPCMessageType, RPCRequestType

View File

@ -0,0 +1,15 @@
from enum import Enum
class MarketDirection(Enum):
"""
Enum for various market directions.
"""
LONG = "long"
SHORT = "short"
EVEN = "even"
NONE = "none"
def __str__(self):
# convert to string
return self.value

View File

@ -19,8 +19,8 @@ from freqtrade.configuration.timerange import TimeRange
from freqtrade.constants import CANCEL_REASON, DATETIME_PRINT_FORMAT, Config from freqtrade.constants import CANCEL_REASON, DATETIME_PRINT_FORMAT, Config
from freqtrade.data.history import load_data from freqtrade.data.history import load_data
from freqtrade.data.metrics import calculate_max_drawdown from freqtrade.data.metrics import calculate_max_drawdown
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, State, from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, MarketDirection, SignalDirection,
TradingMode) State, TradingMode)
from freqtrade.exceptions import ExchangeError, PricingError from freqtrade.exceptions import ExchangeError, PricingError
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs
from freqtrade.loggers import bufferHandler from freqtrade.loggers import bufferHandler
@ -1205,3 +1205,9 @@ class RPC:
'last_process_loc': last_p.astimezone(tzlocal()).strftime(DATETIME_PRINT_FORMAT), 'last_process_loc': last_p.astimezone(tzlocal()).strftime(DATETIME_PRINT_FORMAT),
'last_process_ts': int(last_p.timestamp()), 'last_process_ts': int(last_p.timestamp()),
} }
def _update_market_direction(self, direction: MarketDirection) -> None:
self._freqtrade.strategy.market_direction = direction
def _get_market_direction(self) -> MarketDirection:
return self._freqtrade.strategy.market_direction

View File

@ -25,7 +25,7 @@ from telegram.utils.helpers import escape_markdown
from freqtrade.__init__ import __version__ from freqtrade.__init__ import __version__
from freqtrade.constants import DUST_PER_COIN, Config from freqtrade.constants import DUST_PER_COIN, Config
from freqtrade.enums import RPCMessageType, SignalDirection, TradingMode from freqtrade.enums import MarketDirection, RPCMessageType, SignalDirection, TradingMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import chunks, plural, round_coin_value from freqtrade.misc import chunks, plural, round_coin_value
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
@ -129,7 +129,8 @@ class Telegram(RPCHandler):
r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$',
r'/forcebuy$', r'/forcelong$', r'/forceshort$', r'/forcebuy$', r'/forcelong$', r'/forceshort$',
r'/forcesell$', r'/forceexit$', r'/forcesell$', r'/forceexit$',
r'/edge$', r'/health$', r'/help$', r'/version$' r'/edge$', r'/health$', r'/help$', r'/version$', r'/marketdir (long|short|even|none)$',
r'/marketdir$'
] ]
# Create keys for generation # Create keys for generation
valid_keys_print = [k.replace('$', '') for k in valid_keys] valid_keys_print = [k.replace('$', '') for k in valid_keys]
@ -197,6 +198,7 @@ class Telegram(RPCHandler):
CommandHandler('health', self._health), CommandHandler('health', self._health),
CommandHandler('help', self._help), CommandHandler('help', self._help),
CommandHandler('version', self._version), CommandHandler('version', self._version),
CommandHandler('marketdir', self._changemarketdir)
] ]
callbacks = [ callbacks = [
CallbackQueryHandler(self._status_table, pattern='update_status_table'), CallbackQueryHandler(self._status_table, pattern='update_status_table'),
@ -1502,6 +1504,9 @@ class Telegram(RPCHandler):
"*/count:* `Show number of active trades compared to allowed number of trades`\n" "*/count:* `Show number of active trades compared to allowed number of trades`\n"
"*/edge:* `Shows validated pairs by Edge if it is enabled` \n" "*/edge:* `Shows validated pairs by Edge if it is enabled` \n"
"*/health* `Show latest process timestamp - defaults to 1970-01-01 00:00:00` \n" "*/health* `Show latest process timestamp - defaults to 1970-01-01 00:00:00` \n"
"*/marketdir [long | short | even | none]:* `Updates the user managed variable "
"that represents the current market direction. If no direction is provided `"
"`the currently set market direction will be output.` \n"
"_Statistics_\n" "_Statistics_\n"
"------------\n" "------------\n"
@ -1685,3 +1690,39 @@ class Telegram(RPCHandler):
'TelegramError: %s! Giving up on that message.', 'TelegramError: %s! Giving up on that message.',
telegram_err.message telegram_err.message
) )
@authorized_only
def _changemarketdir(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /marketdir.
Updates the bot's market_direction
:param bot: telegram bot
:param update: message update
:return: None
"""
if context.args and len(context.args) == 1:
new_market_dir_arg = context.args[0]
old_market_dir = self._rpc._get_market_direction()
new_market_dir = None
if new_market_dir_arg == "long":
new_market_dir = MarketDirection.LONG
elif new_market_dir_arg == "short":
new_market_dir = MarketDirection.SHORT
elif new_market_dir_arg == "even":
new_market_dir = MarketDirection.EVEN
elif new_market_dir_arg == "none":
new_market_dir = MarketDirection.NONE
if new_market_dir is not None:
self._rpc._update_market_direction(new_market_dir)
self._send_msg("Successfully updated market direction"
f" from *{old_market_dir}* to *{new_market_dir}*.")
else:
raise RPCException("Invalid market direction provided. \n"
"Valid market directions: *long, short, even, none*")
elif context.args is not None and len(context.args) == 0:
old_market_dir = self._rpc._get_market_direction()
self._send_msg(f"Currently set market direction: *{old_market_dir}*")
else:
raise RPCException("Invalid usage of command /marketdir. \n"
"Usage: */marketdir [short | long | even | none]*")

View File

@ -12,8 +12,8 @@ from pandas import DataFrame
from freqtrade.constants import Config, IntOrInf, ListPairsWithTimeframes from freqtrade.constants import Config, IntOrInf, ListPairsWithTimeframes
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RunMode, SignalDirection, from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, MarketDirection, RunMode,
SignalTagType, SignalType, TradingMode) SignalDirection, SignalTagType, SignalType, TradingMode)
from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exceptions import OperationalException, StrategyError
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds
from freqtrade.misc import remove_entry_exit_signals from freqtrade.misc import remove_entry_exit_signals
@ -122,6 +122,9 @@ class IStrategy(ABC, HyperStrategyMixin):
# Definition of plot_config. See plotting documentation for more details. # Definition of plot_config. See plotting documentation for more details.
plot_config: Dict = {} plot_config: Dict = {}
# A self set parameter that represents the market direction. filled from configuration
market_direction: MarketDirection = MarketDirection.NONE
def __init__(self, config: Config) -> None: def __init__(self, config: Config) -> None:
self.config = config self.config = config
# Dict to determine if analysis is necessary # Dict to determine if analysis is necessary

View File

@ -20,7 +20,8 @@ from telegram.error import BadRequest, NetworkError, TelegramError
from freqtrade import __version__ from freqtrade import __version__
from freqtrade.constants import CANCEL_REASON from freqtrade.constants import CANCEL_REASON
from freqtrade.edge import PairInfo from freqtrade.edge import PairInfo
from freqtrade.enums import ExitType, RPCMessageType, RunMode, SignalDirection, State from freqtrade.enums import (ExitType, MarketDirection, RPCMessageType, RunMode, SignalDirection,
State)
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.loggers import setup_logging from freqtrade.loggers import setup_logging
@ -106,7 +107,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
"['reload_config', 'reload_conf'], ['show_config', 'show_conf'], " "['reload_config', 'reload_conf'], ['show_config', 'show_conf'], "
"['stopbuy', 'stopentry'], ['whitelist'], ['blacklist'], " "['stopbuy', 'stopentry'], ['whitelist'], ['blacklist'], "
"['blacklist_delete', 'bl_delete'], " "['blacklist_delete', 'bl_delete'], "
"['logs'], ['edge'], ['health'], ['help'], ['version']" "['logs'], ['edge'], ['health'], ['help'], ['version'], ['marketdir']"
"]") "]")
assert log_has(message_str, caplog) assert log_has(message_str, caplog)
@ -2395,3 +2396,15 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
assert log_has("using custom keyboard from config.json: " assert log_has("using custom keyboard from config.json: "
"[['/daily', '/stats', '/balance', '/profit', '/profit 5'], ['/count', " "[['/daily', '/stats', '/balance', '/profit', '/profit 5'], ['/count', "
"'/start', '/reload_config', '/help']]", caplog) "'/start', '/reload_config', '/help']]", caplog)
def test_change_market_direction(default_conf, mocker, update) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.NONE
context = MagicMock()
context.args = ["long"]
telegram._changemarketdir(update, context)
assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.LONG
context = MagicMock()
context.args = ["invalid"]
assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.LONG