-
Notifications
You must be signed in to change notification settings - Fork 157
✨ docker-compose support & new installation method
#212
base: development
Are you sure you want to change the base?
Changes from 8 commits
be7b122
3bf6806
bd58082
cb0e9b1
e4a6e89
c90c71b
e8e8fbe
4e7ecbd
4ff410d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| [submodule "freqtrade"] | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm fan of the Freqtrade repo
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a great feature, but it must be used with caution (it shouldn't be abused). I think this is an excellent example of when to use it! I'm glad this was relevant :)
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I'll have to take a good in-depth read in the docs on how to manage submodules once we're about to merge this PR! |
||
| path = freqtrade | ||
| url = [email protected]:freqtrade/freqtrade.git | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,11 @@ | ||
| config: | ||
| username: MoniGoMani Community | ||
| exchange: binance | ||
| ft_binary: source ./.env/bin/activate; freqtrade | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm okay with deprecating
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the implementation of PR #224 we should easily be able to deprecate the |
||
| install_type: source | ||
| hyperopt: | ||
| epochs: 1000 | ||
| loss: MGM_WinRatioAndProfitRatioHyperOptLoss | ||
| stake_currency: USDT | ||
| spaces: buy sell | ||
| strategy: MoniGoManiHyperStrategy | ||
| install_type: source | ||
| mgm_config_names: | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why we would want to deprecate the This currently gives users the ability to easily switch between config files that co-exist in the I would even agree about moving all The new proposed "minimal" config:
install_type: source
mgm_config_names:
mgm-config: mgm-config.json
mgm-config-hyperopt: mgm-config-hyperopt.json
mgm-config-private: mgm-config-private.json(Then we can also scrap the double
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is going to be a bit controversial but I hope it's open for discussion. Allow me to elaborate my reasons to make that decision as well as to cover on this reply your other comment with my reasons to add the new command The main reason on to why I had to deprecate the My proposal:
My goal with this approach is to clean-up what I consider FQ files, from what I consider files for the MGM framework, then temporary files/output and the (possible several) user's strategy configuration files. This makes sense if you use a git repo, or a external folder somewhere in your FS, to store your strategy config files without polluting the MGM git repo. My goal for a clean process is: clone the official repo, install, configure framework with my config files and then run. Placing all my data inside the Following your example from your comment, the user will have a folder my_mgm_configurations anywhere and subfolders that contain all files that make a strategy (the config files!). And this is swapped by the use_cofiguration command. Does that make sense? To be honest, I see the .hurry more like a "environment configuration file" where you define how you've installed FQ or other meta stuff that is automatically access by MGM framework. But not things that are related to a certain configuration, that belongs to the config files IMO and that can be swapped easily using the use_configuration command.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do agree with And if it's about
However I'm a bit in disagreement with the scrapping the ability to configure custom config names (aka the Allow me to elaborate the concerns I have:
That's why I'd like to keep the
I really do appreciate thorough thinking through that you're doing for this issue here! |
||
| mgm-config: mgm-config.json | ||
| mgm-config-hyperopt: mgm-config-hyperopt.json | ||
| mgm-config-private: mgm-config-private.json | ||
| timerange: 20210501-20210616 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| --- | ||
| version: '3' | ||
| services: | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Am I right that we're currently only dockerizing I'm also wondering if Or will
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Currently only FQ is dockerized but it's possible to run both MGM and FQ inside their own containers (each on their own image). Docker-composer allows to easily manage a set of containers and run "two services" with dependencies and thanks to the Docker magic communicate with each other. It's possible but I've never done it myself.
To update to a newer MGM version, the MGM framework commands should simply checkout the MGM git commit (which will also update the FQ submodule) or checkout a new commit/tag (tag, please!) in the the FQ submodule. Then build. Both Dockerized or source code installation will be automatically updated.
This is a good question, but if they already have a working MGM the commands should just checkout to a newer commit version and then build. No need to run external scripts IMO.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm a fan of implementing 2 separate services in the However I do fear that it might complicate the implementation since we'll have to make sure that the
Hmmm I'm not such a fan of automatically updating Freqtrade when you update MGM 🧐 However this can also lead to confusion for the end user since it won't be clear that his Freqtrade also updated when he updated MGM. So I propose the following when updating MGM:
In my answers above I assumed that the docker containers will be able to run the As we've talked about before, currently the I'm not certain if this will or won't be needed though, so it'll have to be tested. |
||
| freqtrade: | ||
| image: fq:mgm-recommended | ||
| # image: freqtradeorg/freqtrade:stable | ||
| # image: freqtradeorg/freqtrade:develop | ||
| # Use plotting image | ||
| # image: freqtradeorg/freqtrade:develop_plot | ||
| # Build step - only needed when additional dependencies are needed | ||
| # build: | ||
| # context: . | ||
| # dockerfile: "./docker/Dockerfile.custom" | ||
| restart: unless-stopped | ||
| container_name: freqtrade | ||
| volumes: | ||
| - "./user_data:/freqtrade/user_data" | ||
| # Expose api on port 8080 (localhost only) | ||
| # Please read the https://www.freqtrade.io/en/stable/rest-api/ documentation | ||
| # before enabling this. | ||
| ports: | ||
| # - "127.0.0.1:8080:8080" | ||
| - "127.0.0.1:8080:8080" # Unsafe | ||
| # Default command used when running `docker compose up` | ||
| command: > | ||
| trade | ||
| --logfile /freqtrade/user_data/logs/freqtrade.log | ||
| --db-url sqlite:////freqtrade/user_data/tradesv3.sqlite | ||
| --config /freqtrade/user_data/config.json | ||
| --strategy SampleStrategy | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ import glob | |
| import json | ||
| import logging | ||
| import os | ||
| import shutil | ||
| import sys | ||
| from datetime import datetime | ||
| from string import Template | ||
|
|
@@ -69,6 +70,7 @@ class MGMHurry: | |
| self.freqtrade_cli = FreqtradeCli(self.basedir) | ||
| self.monigomani_cli = MoniGoManiCli(self.basedir) | ||
|
|
||
| # TODO redo. | ||
| def version(self): | ||
| if self.freqtrade_cli.install_type == 'source': | ||
| if self.freqtrade_cli.installation_exists(silent=True): | ||
|
|
@@ -82,7 +84,6 @@ class MGMHurry: | |
| self.monigomani_cli.run_command('git log -1; echo "";') | ||
| else: | ||
| self.logger.warning(Color.yellow('"No Freqtrade installation detected!')) | ||
|
|
||
| else: | ||
| self.logger.warning(Color.yellow('"version" command currently only supported on "source" installations.')) | ||
|
|
||
|
|
@@ -577,8 +578,9 @@ class MGMHurry: | |
|
|
||
| self.logger.info(f'👉 Downloading candle data ({tickers}) for timerange ({timerange})') | ||
|
|
||
| command = (f'{self.monigomani_config.config["ft_binary"]} download-data --timerange {timerange} ' | ||
| f'-t {tickers} {self.monigomani_config.command_configs()}') | ||
| command = f'{self.monigomani_config.get_freqtrade_cmd()} download-data' | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
command = (f'{self.monigomani_config.get_freqtrade_cmd()} download-data'
f' --timerange {timerange} -t {tickers} {self.monigomani_config.command_configs()}')
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I usually would have prefer this style: command = ' '.join([
self.monigomani_config.config["ft_binary"],
'download-data',
f'--timerange {timerange}',
f'-t {tickers}',
self.monigomani_config.command_configs()
])I believe this is a discussion over a personal style, however I will honor yours since you're the creator and maintainer.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's indeed a discussion of style, However I appreciate you sticking to my suggestion, for MGM I prefer this writing because it's already becoming a rather large project & this will keep the amount of lines significantly lower while code readability remains okay 🙂 |
||
| command += f' --timerange {timerange}' | ||
| command += f' -t {tickers} {self.monigomani_config.command_configs()}' | ||
|
|
||
| if self.monigomani_config.config['exchange'] == 'kraken': | ||
| command += ' --dl-trades' | ||
|
|
@@ -698,30 +700,24 @@ class MGMHurry: | |
| if spaces is None: | ||
| spaces = self.monigomani_config.config['hyperopt']['spaces'] | ||
|
|
||
| command = (f'$ft_binary hyperopt -s $ho_strategy {self.monigomani_config.command_configs()}' | ||
| f'--hyperopt-loss $ho_loss --spaces $ho_spaces -e $ho_epochs --timerange $timerange ') | ||
| command = f'{self.monigomani_config.get_freqtrade_cmd()} hyperopt' | ||
| command += f' {self.monigomani_config.command_configs()}' | ||
| command += f' --hyperopt-loss {strategy}' | ||
| command += f' --spaces {spaces}' | ||
| command += f' -e {epochs}' | ||
| command += f' --timerange {timerange}' | ||
|
|
||
| if enable_protections is True: | ||
| command = f'{command.strip()} --enable-protections ' | ||
| command = f' {command.strip()} --enable-protections' | ||
| if random_state is not None: | ||
| command = f'{command.strip()} --random-state $random_state ' | ||
| command = f' {command.strip()} --random-state {random_state}' | ||
| if jobs is not None: | ||
| command = f'{command.strip()} -j $jobs ' | ||
| command = f' {command.strip()} -j {jobs}' | ||
| if min_trades is not None: | ||
| command = f'{command.strip()} --min-trades $min_trades ' | ||
|
|
||
| command = Template(command).substitute( | ||
| ft_binary=self.monigomani_config.config['ft_binary'], | ||
| ho_strategy=strategy, | ||
| ho_loss=loss, | ||
| ho_spaces=spaces, | ||
| ho_epochs=epochs, | ||
| timerange=timerange, | ||
| random_state=random_state, | ||
| jobs=jobs, | ||
| min_trades=min_trades) | ||
| command = f' {command.strip()} --min-trades {min_trades}' | ||
|
|
||
| self.logger.debug(command) | ||
|
|
||
| if output_file_name is None: | ||
| output_file_name = f'{strategy}-{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}' | ||
| hyperopt_file_name = f'HyperOptResults-{output_file_name}' | ||
|
|
@@ -794,7 +790,7 @@ class MGMHurry: | |
| best = '--best' if only_best is True else '' | ||
| profit = '--profitable' if only_profitable is True else '' | ||
|
|
||
| command = (f'{self.monigomani_config.config["ft_binary"]} hyperopt-list ' | ||
| command = (f'{self.monigomani_config.get_freqtrade_cmd()} hyperopt-list ' | ||
| f'--hyperopt-filename "{choice}" {best} {profit}') | ||
| self.logger.debug(command) | ||
|
|
||
|
|
@@ -823,7 +819,7 @@ class MGMHurry: | |
|
|
||
| self.logger.info(f'👉 Showing {"" if apply is False else "and applying "}HyperOpt results for epoch #{epoch}') | ||
|
|
||
| command = (f'{self.monigomani_config.config["ft_binary"]} hyperopt-show -n {epoch} ' | ||
| command = (f'{self.monigomani_config.get_freqtrade_cmd()} hyperopt-show -n {epoch} ' | ||
| f'{self.monigomani_config.command_configs()}') | ||
|
|
||
| if fthypt_name is not None: | ||
|
|
@@ -857,7 +853,7 @@ class MGMHurry: | |
| self.logger.info(Color.title('👉 Start BackTesting. Lets see how it all turns out!')) | ||
|
|
||
| timerange = self.monigomani_config.get_preset_timerange(timerange) | ||
| command = (f'$ft_binary backtesting -s $ho_strategy {self.monigomani_config.command_configs()}' | ||
| command = (f'{self.monigomani_config.get_freqtrade_cmd()} backtesting -s $ho_strategy {self.monigomani_config.command_configs()}' | ||
| f'--timerange $timerange') | ||
|
|
||
| if timerange is None: | ||
|
|
@@ -950,7 +946,7 @@ class MGMHurry: | |
| output_file_path = f'{self.basedir}/user_data/plot/{plot_profit_file_name}.html' | ||
|
|
||
| self.monigomani_cli.run_command( | ||
| f'{self.monigomani_config.config["ft_binary"]} plot-profit {self.monigomani_config.command_configs()}' | ||
| f'{self.monigomani_config.get_freqtrade_cmd()} plot-profit {self.monigomani_config.command_configs()}' | ||
| f'--export-filename {backtest_results_path} --timerange {timerange} --timeframe {timeframe} && ' | ||
| f'mv {self.basedir}/user_data/plot/freqtrade-profit-plot.html {output_file_path}') | ||
|
|
||
|
|
@@ -1098,6 +1094,44 @@ class MGMHurry: | |
| message=f'🚀 Fresh **{strategy}** SpreadSheet (.csv) Results ⬇️', | ||
| results_paths=[output_file_path]) | ||
|
|
||
| def use_configuration(self, config_ho: str = None, config: str = None, config_private: str = None, override: bool = False) -> None: | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do like the idea for the new However I would do the implementation of it quite differently:
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please refer to the other (very) long reply: #212 (comment) I like your proposal of renaming the command, but the command makes sense if different the configuration files aren't directly in the user_data folder. Refer to the other comment for more info on this. |
||
| """ | ||
| Configure the bot with the given configuration. Use this to quickly move between different configurations | ||
| stored in your filesystem. | ||
|
|
||
| :param config_ho: (str) | ||
| :param config: (str, Optional) | ||
| :param config_private: (str, Optional) | ||
| :param override: (bool) | ||
| """ | ||
| self.logger.info(Color.title('👉 Configuring the bot with strategy:')) | ||
|
|
||
| if not override: | ||
| self.logger.error('Impossible to use provided configuration without overridden the present one.' | ||
| ' Include the `--override` flag if you wish to override the current configuration.') | ||
| return | ||
|
|
||
| if os.path.isfile(config_ho): | ||
| self.logger.info(f'Using config-hyperopt from: {config_ho}') | ||
|
|
||
| shutil.copy(config_ho, f'{self.basedir}/user_data/mgm-config-hyperopt.json') | ||
| elif config_ho is not None: | ||
| self.logger.error(f'Can\'t locate config-hyperopt file from: {config_ho}') | ||
|
|
||
| if os.path.isfile(config): | ||
| self.logger.info(f'Using config from: {config}') | ||
|
|
||
| shutil.copy(config, f'{self.basedir}/user_data/mgm-config.json') | ||
| elif config is not None: | ||
| self.logger.error(f'Can\'t locate config file from: {config}') | ||
|
|
||
| if os.path.isfile(config_private): | ||
| self.logger.info(f'Using config-private from: {config_private}') | ||
|
|
||
| shutil.copy(config_private, f'{self.basedir}/user_data/mgm-config-private.json') | ||
| elif config_private is not None: | ||
| self.logger.error(f'Can\'t locate config-private file from: {config_private}') | ||
|
|
||
| def start_trader(self, dry_run: bool = True): | ||
| """ | ||
| Start the trader. Your ultimate goal! | ||
|
|
@@ -1107,8 +1141,9 @@ class MGMHurry: | |
| self.logger.info(Color.title(f'👉 Starting the trader, ' | ||
| f'{"in Dry-Run Mode!" if dry_run else "in Live-Run Mode, fingers crossed! 🤞"}')) | ||
|
|
||
| command = (f'{self.monigomani_config.config["ft_binary"]} trade {self.monigomani_config.command_configs()}' | ||
| f'--strategy {self.monigomani_config.config["hyperopt"]["strategy"]}') | ||
| command = f'{self.monigomani_config.get_freqtrade_cmd()} trade' | ||
| command += f' {self.monigomani_config.command_configs()}' | ||
| command += f' --strategy {self.monigomani_config.config["hyperopt"]["strategy"]}' | ||
|
|
||
| if dry_run is True: | ||
| command += ' --dry-run' | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| -r requirements-mgm.txt | ||
| pytest==6.2.5 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ | |
| # \_| |_| \___| \__, | \__||_| \__,_| \__,_| \___| \____/|_||_| | ||
| # | | | ||
| # |_| | ||
| import shutil | ||
|
|
||
| import distro | ||
| import glob | ||
|
|
@@ -129,27 +130,36 @@ def installation_exists(self, silent: bool = False) -> bool: | |
| return False | ||
|
|
||
| # Well if install_type is docker, we return True because we don't verify if docker is installed | ||
| if self.install_type == 'docker': | ||
| if self.install_type == 'docker-compose': | ||
| if silent is False: | ||
| self.cli_logger.debug('FreqtradeCli - installation_exists() succeeded because ' | ||
| 'install_type is set to docker.') | ||
| return True | ||
|
|
||
| if self.freqtrade_binary is None: | ||
| if silent is False: | ||
| self.cli_logger.warning(Color.yellow('FreqtradeCli - installation_exists() failed. ' | ||
| 'No freqtrade_binary.')) | ||
| return False | ||
|
|
||
| if self.install_type == 'source': | ||
| if silent is False: | ||
| self.cli_logger.debug('FreqtradeCli - installation_exists() install_type is "source".') | ||
| if os.path.exists(f'{self.basedir}/.env/bin/freqtrade'): | ||
|
|
||
| if os.path.isfile('freqtrade/.env/bin/freqtrade'): | ||
| return True | ||
|
|
||
| if silent is False: | ||
| self.cli_logger.warning(Color.yellow(f'FreqtradeCli - installation_exists() failed. Freqtrade binary ' | ||
| f'not found in {self.basedir}/.env/bin/freqtrade.')) | ||
| f'not found.')) | ||
|
|
||
| if self.install_type == 'custom' and self.freqtrade_binary: | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we only log here, we can replace these 3
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Care to explain how does the silent mode works? Thanks!
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The optional By default these functions would log output to the user which is not always what we want 🙂 |
||
| if os.path.isfile(self.freqtrade_binary): | ||
| if silent is False: | ||
| self.cli_logger.debug('FreqtradeCli - installation_exists() succeeded because ' | ||
| 'install_type is set to custom.') | ||
| else: | ||
| if silent is False: | ||
| self.cli_logger.warning(Color.yellow(f'FreqtradeCli - installation_exists() failed. Freqtrade binary ' | ||
| f'not found.')) | ||
|
|
||
| if silent is False: | ||
| self.cli_logger.warning(Color.yellow('FreqtradeCli - installation_exists() failed. ' | ||
| 'No freqtrade_binary.')) | ||
|
|
||
| return False | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -206,6 +206,18 @@ def read_hurry_config(self) -> dict: | |
|
|
||
| return hurry_config | ||
|
|
||
| def get_freqtrade_cmd(self): | ||
| if self.config['install_type'] == 'docker-compose': | ||
| cmd = 'docker-compose run --rm freqtrade' | ||
|
|
||
| return cmd | ||
| elif self.config['install_type'] == 'source': | ||
| cmd = f'{self.basedir}/freqtrade/.env/bin/freqtrade' | ||
|
|
||
| return cmd | ||
| elif self.config['install_type'] == 'custom': | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious which other
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah... the use isn't really clear 😃 . However, I wanted to use it as a replacement for the .hurry install_fq. Think on a situation where the environment is customized by the user. E.g. FQ is installed in a different location in the system and it doesn't follow the recommended/supported/default MGM framework installation. If removed, with the current changes the user wouldn't be able to customize FQ command with its own.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think in the end the process of installation will be very similar for the end user as it currently is:
Because the web installer allows for installation in a custom folder & with a custom installation folder name I think we should be able to drop the I'm still fine with dropping the |
||
| raise "TODO install_type 'custom' unsupported" | ||
|
|
||
| def get_config_filepath(self, cfg_key: str) -> str: | ||
| """ | ||
| Transforms given cfg_key into the corresponding absolute config filepath. | ||
|
|
@@ -476,13 +488,12 @@ def save_weak_strong_signal_overrides(self) -> bool: | |
|
|
||
| def command_configs(self) -> str: | ||
| """ | ||
| Returns a string with the 'mgm-config' & 'mgm-config-private' names loaded from '.hurry' | ||
| Returns a string with the 'mgm-config' & 'mgm-config-private' file paths | ||
| ready to implement in a freqtrade command. | ||
| :return str: String with 'mgm-config' & 'mgm-config-private' for a freqtrade command | ||
| """ | ||
| mgm_json_name = self.config['mgm_config_names']['mgm-config'] | ||
| mgm_private_json_name = self.config['mgm_config_names']['mgm-config-private'] | ||
| return f'-c ./user_data/{mgm_json_name} -c ./user_data/{mgm_private_json_name} ' | ||
|
|
||
| return '-c ./user_data/mgm-config.json -c ./user_data/mgm-config-private.json' | ||
|
|
||
| def get_preset_timerange(self, timerange: str) -> str: | ||
| """ | ||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.