Merge branch 'develop' into tsl_on_exchange

This commit is contained in:
misagh 2019-01-08 15:33:32 +01:00
commit f4ceeca438
13 changed files with 174 additions and 104 deletions

View File

@ -1,4 +1,4 @@
FROM python:3.7.0-slim-stretch FROM python:3.7.2-slim-stretch
RUN apt-get update \ RUN apt-get update \
&& apt-get -y install curl build-essential \ && apt-get -y install curl build-essential \

View File

@ -19,10 +19,10 @@ The table below will list all configuration parameters.
| `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode. | `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode.
| `process_only_new_candles` | false | No | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. Can be set either in Configuration or in the strategy. | `process_only_new_candles` | false | No | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. Can be set either in Configuration or in the strategy.
| `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file. | `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file.
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file. | `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md).
| `trailing_stop` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). | `trailing_stop` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md).
| `trailing_stop_positve` | 0 | No | Changes stop-loss once profit has been reached. | `trailing_stop_positve` | 0 | No | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md).
| `trailing_stop_positve_offset` | 0 | No | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. | `trailing_stop_positve_offset` | 0 | No | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md).
| `unfilledtimeout.buy` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. | `unfilledtimeout.buy` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled.
| `unfilledtimeout.sell` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. | `unfilledtimeout.sell` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled.
| `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below. | `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below.
@ -223,7 +223,7 @@ creating trades.
```json ```json
"dry_run": true, "dry_run": true,
"db_url": "sqlite///tradesv3.dryrun.sqlite", "db_url": "sqlite:///tradesv3.dryrun.sqlite",
``` ```
3. Remove your Exchange API key (change them by fake api credentials) 3. Remove your Exchange API key (change them by fake api credentials)

View File

@ -6,6 +6,9 @@ At this stage the bot contains the following stoploss support modes:
2. trailing stop loss, defined in the configuration 2. trailing stop loss, defined in the configuration
3. trailing stop loss, custom positive loss, defined in configuration 3. trailing stop loss, custom positive loss, defined in configuration
!!! Note
All stoploss properties can be configured in eihter Strategy or configuration. Configuration values override strategy values.
## Static Stop Loss ## Static Stop Loss
This is very simple, basically you define a stop loss of x in your strategy file or alternative in the configuration, which This is very simple, basically you define a stop loss of x in your strategy file or alternative in the configuration, which

View File

@ -124,9 +124,6 @@ class Configuration(object):
if self.args.db_url and self.args.db_url != constants.DEFAULT_DB_PROD_URL: if self.args.db_url and self.args.db_url != constants.DEFAULT_DB_PROD_URL:
config.update({'db_url': self.args.db_url}) config.update({'db_url': self.args.db_url})
logger.info('Parameter --db-url detected ...') logger.info('Parameter --db-url detected ...')
else:
# Set default here
config.update({'db_url': constants.DEFAULT_DB_PROD_URL})
if config.get('dry_run', False): if config.get('dry_run', False):
logger.info('Dry run is enabled') logger.info('Dry run is enabled')

View File

@ -59,7 +59,7 @@ class Edge():
# checking max_open_trades. it should be -1 as with Edge # checking max_open_trades. it should be -1 as with Edge
# the number of trades is determined by position size # the number of trades is determined by position size
if self.config['max_open_trades'] != -1: if self.config['max_open_trades'] != float('inf'):
logger.critical('max_open_trades should be -1 in config !') logger.critical('max_open_trades should be -1 in config !')
if self.config['stake_amount'] != constants.UNLIMITED_STAKE_AMOUNT: if self.config['stake_amount'] != constants.UNLIMITED_STAKE_AMOUNT:

View File

@ -38,60 +38,43 @@ class StrategyResolver(IResolver):
self.strategy: IStrategy = self._load_strategy(strategy_name, self.strategy: IStrategy = self._load_strategy(strategy_name,
config=config, config=config,
extra_dir=config.get('strategy_path')) extra_dir=config.get('strategy_path'))
# Set attributes # Set attributes
# Check if we need to override configuration # Check if we need to override configuration
if 'minimal_roi' in config: attributes = ["minimal_roi",
self.strategy.minimal_roi = config['minimal_roi'] "ticker_interval",
logger.info("Override strategy 'minimal_roi' with value in config file: %s.", "stoploss",
config['minimal_roi']) "trailing_stop",
else: "trailing_stop_positive",
config['minimal_roi'] = self.strategy.minimal_roi "trailing_stop_positive_offset",
"process_only_new_candles",
"order_types",
"order_time_in_force"
]
for attribute in attributes:
self._override_attribute_helper(config, attribute)
if 'stoploss' in config: # Loop this list again to have output combined
self.strategy.stoploss = config['stoploss'] for attribute in attributes:
logger.info( if attribute in config:
"Override strategy 'stoploss' with value in config file: %s.", config['stoploss'] logger.info("Strategy using %s: %s", attribute, config[attribute])
)
else:
config['stoploss'] = self.strategy.stoploss
if 'ticker_interval' in config: # Sort and apply type conversions
self.strategy.ticker_interval = config['ticker_interval'] self.strategy.minimal_roi = OrderedDict(sorted(
logger.info( {int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(),
"Override strategy 'ticker_interval' with value in config file: %s.", key=lambda t: t[0]))
config['ticker_interval'] self.strategy.stoploss = float(self.strategy.stoploss)
)
else:
config['ticker_interval'] = self.strategy.ticker_interval
if 'process_only_new_candles' in config: self._strategy_sanity_validations()
self.strategy.process_only_new_candles = config['process_only_new_candles']
logger.info(
"Override process_only_new_candles 'process_only_new_candles' "
"with value in config file: %s.", config['process_only_new_candles']
)
else:
config['process_only_new_candles'] = self.strategy.process_only_new_candles
if 'order_types' in config: def _override_attribute_helper(self, config, attribute: str):
self.strategy.order_types = config['order_types'] if attribute in config:
logger.info( setattr(self.strategy, attribute, config[attribute])
"Override strategy 'order_types' with value in config file: %s.", logger.info("Override strategy '%s' with value in config file: %s.",
config['order_types'] attribute, config[attribute])
) elif hasattr(self.strategy, attribute):
else: config[attribute] = getattr(self.strategy, attribute)
config['order_types'] = self.strategy.order_types
if 'order_time_in_force' in config:
self.strategy.order_time_in_force = config['order_time_in_force']
logger.info(
"Override strategy 'order_time_in_force' with value in config file: %s.",
config['order_time_in_force']
)
else:
config['order_time_in_force'] = self.strategy.order_time_in_force
def _strategy_sanity_validations(self):
if not all(k in self.strategy.order_types for k in constants.REQUIRED_ORDERTYPES): if not all(k in self.strategy.order_types for k in constants.REQUIRED_ORDERTYPES):
raise ImportError(f"Impossible to load Strategy '{self.strategy.__class__.__name__}'. " raise ImportError(f"Impossible to load Strategy '{self.strategy.__class__.__name__}'. "
f"Order-types mapping is incomplete.") f"Order-types mapping is incomplete.")
@ -100,12 +83,6 @@ class StrategyResolver(IResolver):
raise ImportError(f"Impossible to load Strategy '{self.strategy.__class__.__name__}'. " raise ImportError(f"Impossible to load Strategy '{self.strategy.__class__.__name__}'. "
f"Order-time-in-force mapping is incomplete.") f"Order-time-in-force mapping is incomplete.")
# Sort and apply type conversions
self.strategy.minimal_roi = OrderedDict(sorted(
{int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(),
key=lambda t: t[0]))
self.strategy.stoploss = float(self.strategy.stoploss)
def _load_strategy( def _load_strategy(
self, strategy_name: str, config: dict, extra_dir: Optional[str] = None) -> IStrategy: self, strategy_name: str, config: dict, extra_dir: Optional[str] = None) -> IStrategy:
""" """

View File

@ -67,6 +67,11 @@ class IStrategy(ABC):
# associated stoploss # associated stoploss
stoploss: float stoploss: float
# trailing stoploss
trailing_stop: bool = False
trailing_stop_positive: float
trailing_stop_positive_offset: float
# associated ticker interval # associated ticker interval
ticker_interval: str ticker_interval: str

View File

@ -150,6 +150,45 @@ def test_strategy_override_stoploss(caplog):
) in caplog.record_tuples ) in caplog.record_tuples
def test_strategy_override_trailing_stop(caplog):
caplog.set_level(logging.INFO)
config = {
'strategy': 'DefaultStrategy',
'trailing_stop': True
}
resolver = StrategyResolver(config)
assert resolver.strategy.trailing_stop
assert isinstance(resolver.strategy.trailing_stop, bool)
assert ('freqtrade.resolvers.strategy_resolver',
logging.INFO,
"Override strategy 'trailing_stop' with value in config file: True."
) in caplog.record_tuples
def test_strategy_override_trailing_stop_positive(caplog):
caplog.set_level(logging.INFO)
config = {
'strategy': 'DefaultStrategy',
'trailing_stop_positive': -0.1,
'trailing_stop_positive_offset': -0.2
}
resolver = StrategyResolver(config)
assert resolver.strategy.trailing_stop_positive == -0.1
assert ('freqtrade.resolvers.strategy_resolver',
logging.INFO,
"Override strategy 'trailing_stop_positive' with value in config file: -0.1."
) in caplog.record_tuples
assert resolver.strategy.trailing_stop_positive_offset == -0.2
assert ('freqtrade.resolvers.strategy_resolver',
logging.INFO,
"Override strategy 'trailing_stop_positive' with value in config file: -0.1."
) in caplog.record_tuples
def test_strategy_override_ticker_interval(caplog): def test_strategy_override_ticker_interval(caplog):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
@ -178,8 +217,7 @@ def test_strategy_override_process_only_new_candles(caplog):
assert resolver.strategy.process_only_new_candles assert resolver.strategy.process_only_new_candles
assert ('freqtrade.resolvers.strategy_resolver', assert ('freqtrade.resolvers.strategy_resolver',
logging.INFO, logging.INFO,
"Override process_only_new_candles 'process_only_new_candles' " "Override strategy 'process_only_new_candles' with value in config file: True."
"with value in config file: True."
) in caplog.record_tuples ) in caplog.record_tuples

View File

@ -73,7 +73,6 @@ def test_load_config_max_open_trades_minus_one(default_conf, mocker, caplog) ->
args = Arguments([], '').get_parsed_arg() args = Arguments([], '').get_parsed_arg()
configuration = Configuration(args) configuration = Configuration(args)
validated_conf = configuration.load_config() validated_conf = configuration.load_config()
print(validated_conf)
assert validated_conf['max_open_trades'] > 999999999 assert validated_conf['max_open_trades'] > 999999999
assert validated_conf['max_open_trades'] == float('inf') assert validated_conf['max_open_trades'] == float('inf')
@ -125,6 +124,43 @@ def test_load_config_with_params(default_conf, mocker) -> None:
assert validated_conf.get('strategy_path') == '/some/path' assert validated_conf.get('strategy_path') == '/some/path'
assert validated_conf.get('db_url') == 'sqlite:///someurl' assert validated_conf.get('db_url') == 'sqlite:///someurl'
# Test conf provided db_url prod
conf = default_conf.copy()
conf["dry_run"] = False
conf["db_url"] = "sqlite:///path/to/db.sqlite"
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(conf)
))
arglist = [
'--strategy', 'TestStrategy',
'--strategy-path', '/some/path'
]
args = Arguments(arglist, '').get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf.get('db_url') == "sqlite:///path/to/db.sqlite"
# Test conf provided db_url dry_run
conf = default_conf.copy()
conf["dry_run"] = True
conf["db_url"] = "sqlite:///path/to/db.sqlite"
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(conf)
))
arglist = [
'--strategy', 'TestStrategy',
'--strategy-path', '/some/path'
]
args = Arguments(arglist, '').get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf.get('db_url') == "sqlite:///path/to/db.sqlite"
# Test args provided db_url prod
conf = default_conf.copy() conf = default_conf.copy()
conf["dry_run"] = False conf["dry_run"] = False
del conf["db_url"] del conf["db_url"]
@ -142,7 +178,7 @@ def test_load_config_with_params(default_conf, mocker) -> None:
validated_conf = configuration.load_config() validated_conf = configuration.load_config()
assert validated_conf.get('db_url') == DEFAULT_DB_PROD_URL assert validated_conf.get('db_url') == DEFAULT_DB_PROD_URL
# Test dry=run with ProdURL # Test args provided db_url dry_run
conf = default_conf.copy() conf = default_conf.copy()
conf["dry_run"] = True conf["dry_run"] = True
conf["db_url"] = DEFAULT_DB_PROD_URL conf["db_url"] = DEFAULT_DB_PROD_URL

View File

@ -4,18 +4,18 @@ nav:
- Installation: installation.md - Installation: installation.md
- Configuration: configuration.md - Configuration: configuration.md
- Start the bot: bot-usage.md - Start the bot: bot-usage.md
- Optimization: bot-optimization.md - Stoploss: stoploss.md
- Custom Strategy: bot-optimization.md
- Telegram: telegram-usage.md
- Web Hook: webhook-config.md
- Backtesting: backtesting.md - Backtesting: backtesting.md
- Hyperopt: hyperopt.md - Hyperopt: hyperopt.md
- Stoploss: stoploss.md
- Telegram: telegram-usage.md
- Plotting: plotting.md
- Edge positioning: edge.md - Edge positioning: edge.md
- Web Hook: webhook-config.md - Plotting: plotting.md
- FAQ: faq.md - FAQ: faq.md
- Contributors guide: developer.md - SQL Cheatsheet: sql_cheatsheet.md
- SQL Cheatsheeet: sql_cheatsheet.md
- Sanbox testing: sandbox-testing.md - Sanbox testing: sandbox-testing.md
- Contributors guide: developer.md
theme: theme:
name: material name: material
logo: 'images/logo.png' logo: 'images/logo.png'

View File

@ -1,4 +1,4 @@
ccxt==1.18.101 ccxt==1.18.102
SQLAlchemy==1.2.15 SQLAlchemy==1.2.15
python-telegram-bot==11.1.0 python-telegram-bot==11.1.0
arrow==0.12.1 arrow==0.12.1

View File

@ -17,23 +17,27 @@ function check_installed_python() {
return return
fi fi
if [ -z ${PYTHON} ]; then
echo "No usable python found. Please make sure to have python3.6 or python3.7 installed"
exit 1
fi
} }
function updateenv() { function updateenv() {
echo "-------------------------" echo "-------------------------"
echo "Update your virtual env" echo "Updating your virtual env"
echo "-------------------------" echo "-------------------------"
source .env/bin/activate source .env/bin/activate
echo "pip3 install in-progress. Please wait..." echo "pip3 install in-progress. Please wait..."
pip3 install --quiet --upgrade pip # Install numpy first to have py_find_1st install clean
pip3 install --quiet -r requirements.txt --upgrade pip3 install --upgrade pip numpy
pip3 install --quiet -r requirements.txt pip3 install --upgrade -r requirements.txt
read -p "Do you want to install dependencies for dev [Y/N]? " read -p "Do you want to install dependencies for dev [y/N]? "
if [[ $REPLY =~ ^[Yy]$ ]] if [[ $REPLY =~ ^[Yy]$ ]]
then then
pip3 install --quiet -r requirements-dev.txt --upgrade pip3 install --upgrade -r requirements-dev.txt
pip3 install --quiet -r requirements-dev.txt
else else
echo "Dev dependencies ignored." echo "Dev dependencies ignored."
fi fi
@ -45,6 +49,11 @@ function updateenv () {
# Install tab lib # Install tab lib
function install_talib() { function install_talib() {
if [ -f /usr/local/lib/libta_lib.a ]; then
echo "ta-lib already installed, skipping"
return
fi
cd build_helpers cd build_helpers
tar zxvf ta-lib-0.4.0-src.tar.gz tar zxvf ta-lib-0.4.0-src.tar.gz
cd ta-lib cd ta-lib
@ -61,7 +70,7 @@ function install_macos () {
if [ ! -x "$(command -v brew)" ] if [ ! -x "$(command -v brew)" ]
then then
echo "-------------------------" echo "-------------------------"
echo "Install Brew" echo "Installing Brew"
echo "-------------------------" echo "-------------------------"
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
fi fi
@ -87,7 +96,7 @@ function update () {
# Reset Develop or Master branch # Reset Develop or Master branch
function reset() { function reset() {
echo "----------------------------" echo "----------------------------"
echo "Reset branch and virtual env" echo "Reseting branch and virtual env"
echo "----------------------------" echo "----------------------------"
if [ "1" == $(git branch -vv |grep -cE "\* develop|\* master") ] if [ "1" == $(git branch -vv |grep -cE "\* develop|\* master") ]
then then
@ -133,7 +142,7 @@ function config_generator () {
echo "Starting to generate config.json" echo "Starting to generate config.json"
echo echo
echo "General configuration" echo "Generating General configuration"
echo "-------------------------" echo "-------------------------"
default_max_trades=3 default_max_trades=3
read -p "Max open trades: (Default: $default_max_trades) " max_trades read -p "Max open trades: (Default: $default_max_trades) " max_trades
@ -152,13 +161,13 @@ function config_generator () {
fiat_currency=${fiat_currency:-$default_fiat_currency} fiat_currency=${fiat_currency:-$default_fiat_currency}
echo echo
echo "Exchange config generator" echo "Generating exchange config "
echo "------------------------" echo "------------------------"
read -p "Exchange API key: " api_key read -p "Exchange API key: " api_key
read -p "Exchange API Secret: " api_secret read -p "Exchange API Secret: " api_secret
echo echo
echo "Telegram config generator" echo "Generating Telegram config"
echo "-------------------------" echo "-------------------------"
read -p "Telegram Token: " token read -p "Telegram Token: " token
read -p "Telegram Chat_id: " chat_id read -p "Telegram Chat_id: " chat_id
@ -178,11 +187,11 @@ function config_generator () {
function config() { function config() {
echo "-------------------------" echo "-------------------------"
echo "Config file generator" echo "Generating config file"
echo "-------------------------" echo "-------------------------"
if [ -f config.json ] if [ -f config.json ]
then then
read -p "A config file already exist, do you want to override it [Y/N]? " read -p "A config file already exist, do you want to override it [y/N]? "
if [[ $REPLY =~ ^[Yy]$ ]] if [[ $REPLY =~ ^[Yy]$ ]]
then then
config_generator config_generator
@ -203,7 +212,7 @@ function config () {
function install() { function install() {
echo "-------------------------" echo "-------------------------"
echo "Install mandatory dependencies" echo "Installing mandatory dependencies"
echo "-------------------------" echo "-------------------------"
if [ "$(uname -s)" == "Darwin" ] if [ "$(uname -s)" == "Darwin" ]
@ -224,7 +233,7 @@ function install () {
reset reset
config config
echo "-------------------------" echo "-------------------------"
echo "Run the bot" echo "Run the bot !"
echo "-------------------------" echo "-------------------------"
echo "You can now use the bot by executing 'source .env/bin/activate; python freqtrade/main.py'." echo "You can now use the bot by executing 'source .env/bin/activate; python freqtrade/main.py'."
} }
@ -232,7 +241,7 @@ function install () {
function plot() { function plot() {
echo " echo "
----------------------------------------- -----------------------------------------
Install dependencies for Plotting scripts Installing dependencies for Plotting scripts
----------------------------------------- -----------------------------------------
" "
pip install plotly --upgrade pip install plotly --upgrade

View File

@ -42,6 +42,11 @@ class TestStrategy(IStrategy):
# This attribute will be overridden if the config file contains "stoploss" # This attribute will be overridden if the config file contains "stoploss"
stoploss = -0.10 stoploss = -0.10
# trailing stoploss
trailing_stop = False
trailing_stop_positive = 0.01
trailing_stop_positive_offset = None # Disabled / not configured
# Optimal ticker interval for the strategy # Optimal ticker interval for the strategy
ticker_interval = '5m' ticker_interval = '5m'