diff --git a/cache/README.md b/cache/README.md new file mode 100644 index 000000000..e1c9bbaa7 --- /dev/null +++ b/cache/README.md @@ -0,0 +1,5 @@ +# Cache folder + +Store downloaded trading data files here. + + diff --git a/configurations/README.md b/configurations/README.md new file mode 100644 index 000000000..f1b0842f7 --- /dev/null +++ b/configurations/README.md @@ -0,0 +1,15 @@ +# Executor configurations + +This folder contains configuration scripts for different trading strategy executions + +- Strategy Python file +- Used Python hot wallet +- Discord webhooks + +These scripts are used to create `.env` file that you can use with `docker-compose` or directly `source` in shell. + +The configuration is created by splicing together + +- Shared secret variables (e.g. JSON-RPC endpoints) +- Strategy specific secret variables (e.g. hot wallet private key) +- Public variables (e.g. strategy icon, name and description, gas pricing parameters) diff --git a/bootstraps/pancake_8h_momentum.sh b/configurations/pancake_8h_momentum.sh similarity index 100% rename from bootstraps/pancake_8h_momentum.sh rename to configurations/pancake_8h_momentum.sh diff --git a/bootstraps/quickswap-momentum.sh b/configurations/quickswap-momentum.sh similarity index 63% rename from bootstraps/quickswap-momentum.sh rename to configurations/quickswap-momentum.sh index 3f52cb96e..7add3ae24 100755 --- a/bootstraps/quickswap-momentum.sh +++ b/configurations/quickswap-momentum.sh @@ -1,19 +1,17 @@ #!/bin/bash # -# QuickSwap momentum trading launch script. +# QuickSwap momentum strategy configuration script. # -# This script combines a strategy specific trade executor environt variables -# with secret environment variables before running the trade-executor command line entry point. +# This file will set up environment variables to execute the strategy, +# either for normal UNIX application run or Docker-compose run. # -# Check that `~/secrets.env` and `~/$EXECUTOR_ID.secrets.env` are set up. +# Usage: # -# Then run the checks: +# configurations/quickswap-momentum.sh # -# bootstraps/polygon_momentum.sh check-universe --max-data-delay-minutes=1440 +# This will generate: # -# Then start: -# -# bootstraps/polygon_momentum.sh start +# ~/quickswap-momentum.env # # The strategy is mapped to a webhook in its secrets file: # @@ -36,7 +34,7 @@ check_secret_envs() # This id is used in various paths and such. # It is taken from the shell script name. export EXECUTOR_ID=`basename "$0" .sh` -echo "Starting trade executor $EXECUTOR_ID" +echo "Creating strategy execution configuration for $EXECUTOR_ID" # Strategy specific secrets. # This file will give us private key needed to access the hot wallet @@ -47,34 +45,40 @@ if [ ! -f "$STRATEGY_SECRETS_FILE" ] ; then exit 1 fi - # Read generic secrets, then strategy specific secrets source ~/secrets.env source $STRATEGY_SECRETS_FILE # These variables must come from the secrets file -check_secret_envs PRIVATE_KEY JSON_RPC_BINANCE TRADING_STRATEGY_API_KEY DISCORD_WEBHOOK_URL HTTP_ENABLED +check_secret_envs PRIVATE_KEY JSON_RPC_BINANCE JSON_RPC_POLYGON TRADING_STRATEGY_API_KEY DISCORD_WEBHOOK_URL HTTP_ENABLED # Metadata -export NAME="Polygon momentum" +export NAME="Quickswap momentum" +export DOMAIN_NAME="robocop.tradingstrategy.ai" export SHORT_DESCRIPTION="A data collection test that trades on Polygon MATIC/USDC pairs." export LONG_DESCRIPTION="This strategy is a test strategy that does not aim to generated profit. The strategy trades on MATIC/USDC pairs on QuickSwap. The strategy rebalances every 16 hours to generate maximum amount of data. Based on 4h candles, the strategy calculates the momentum and picks highest gainers. The portfolio can hold 6 positions at once." -export ICON_URL="src="https://i0.wp.com/bloody-disgusting.com/wp-content/uploads/2022/01/watch-robocop-the-series.png?w=1270" +export ICON_URL="https://i0.wp.com/bloody-disgusting.com/wp-content/uploads/2022/01/watch-robocop-the-series.png?w=1270" -export STRATEGY_FILE="${PWD}/strategies/${EXECUTOR_ID}.py" +export STRATEGY_FILE="strategies/${EXECUTOR_ID}.py" export JSON_RPC=$JSON_RPC_BINANCE export GAS_PRICE_METHOD="legacy" export STATE_FILE="$EXECUTOR_ID.json" export EXECUTION_TYPE="uniswap_v2_hot_wallet" export APPROVAL_TYPE="unchecked" -export CACHE_PATH="${PWD}/.cache/${EXECUTOR_ID}" +export CACHE_PATH="cache/${EXECUTOR_ID}" export TICK_OFFSET_MINUTES="10" export TICK_SIZE="16h" +export HTTP_PORT=3456 + # 12 hours export MAX_DATA_DELAY_MINUTES=720 -echo "HTTP enabled is $HTTP_ENABLED" +# Passed as Docker volume mount point +export STATE_FILE=state/$EXECUTOR_ID.state.json + +OUTPUT=~/$EXECUTOR_ID.env + +python scripts/prepare-env.py > $OUTPUT -# https://stackoverflow.com/a/1537695/315168 -poetry run trade-executor "$@" +echo "Created $OUTPUT" diff --git a/docker-compose.yml b/docker-compose.yml index 5ffb41791..f09e01132 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,13 +3,19 @@ # version: "3.9" services: - polygon-momentum: + quickswap-momentum: + container_name: quickswap-momentum build: . ports: - - "10001:3456" + # Mapped in reverse proxy Caddyfile + - "127.0.0.1:10001:3456" volumes: - - .:/state - environment: - FLASK_ENV: development - redis: - image: "redis:alpine" + # Save the strategy execution state in the local filesystem + - ./state:/usr/src/trade-executor/state + # Cache the dataset downloads in the local filesystem + - ./cache:/usr/src/trade-executor/cache + env_file: + # Generated by configurations/quickswap-momentum.sh + - ~/quickswap-momentum.env + # What we pass to tradexecutor.cli.main application + command: start diff --git a/docs/docker.md b/docs/docker.md index b07a311fb..30ba060d8 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -7,6 +7,10 @@ - Port 3456 is exposed for the executor webhook integration - All executor parameters must be passed as environment variables - The application files are copied to `/usr/src/trade-executor` +- Work dir is `/usr/src/trade-executor` +- You need to configure a domain name for each strategy executions +- Local `/state` and `/cache` are mapped to `/usr/src/trade-executor` - note that these folders are **shared across instances** + and trade executor application code must deal with having specific state files for each strategy # Building @@ -16,9 +20,51 @@ docker build -t trading-strategy/trade-executor . # Running +You can start `trade-executor` binary as: + ```shell docker run -ti trading-strategy/trade-executor --help ``` -# Launching exeuctors +# Configuring and launching strategy executors + +## Creating environment file for Docker + +First you need to prepare an environment variable file that is going to be +passed to `docker-compose`. This file contains secrets and +is spliced together for multiple configuration files + +- Generic secrets: `~/secrets.env` +- Strategy specific secrets: `~/quickswap-momentum.secrets.env` +- Generic options + +This option splicing is done by a configuration helper script like `configurations/quickswap-momentum.sh` + +```shell +# Creates ~/quickswap-momentum.env +bash configuration/quickswap-momentum.sh +``` + +## Launching a strategty using docker-compose + +The project comes with a `docker-compose.yml` with configurations for example strategies. +Now when we have created `~/quickswap-momentum.env` we can launch the executor. + +```shell +docker-compose build quickswap-momentum +docker-compose up --no-deps -d quickswap-momentum +``` + +This executor + +- Maps a host port for the webhook access - each strategy execution gets its own port +- This port is mapped to the Internet through a Caddy reverse proxy + +# Troubleshooting the container + +You can do bash + +```shell +docker run -it --entrypoint /bin/bash trading-strategy/trade-executor +``` \ No newline at end of file diff --git a/scripts/prepare-env.py b/scripts/prepare-env.py new file mode 100644 index 000000000..c3b2adae4 --- /dev/null +++ b/scripts/prepare-env.py @@ -0,0 +1,34 @@ +"""Prepare environment variable list. + +Because the configuration options list is so long, it is hard to manage by hand. + +- Reads the current environment variable list +- Writes out cleaned up .env file to stdout + +Very useful with Docker. +""" + +import os + +from tradeexecutor.cli.env import get_available_env_vars + +vars = get_available_env_vars() + +# print("Strategy execution settings are:", ", ".join(vars)) + +for desc in vars: + value = os.environ.get(desc.name) + print(f"# {desc.help}") + print(f"# Type: {desc.type}") + if value is not None: + print(f"{desc.name}={value}") + else: + print(f"{desc.name}=") + print() + + + + + + + diff --git a/state/README.md b/state/README.md new file mode 100644 index 000000000..865ad1da4 --- /dev/null +++ b/state/README.md @@ -0,0 +1,5 @@ +# State files + +Running strategy executors will write their state files here. + + diff --git a/tradeexecutor/cli/env.py b/tradeexecutor/cli/env.py new file mode 100644 index 000000000..ebdcc6144 --- /dev/null +++ b/tradeexecutor/cli/env.py @@ -0,0 +1,41 @@ +"""Environment variable managment.""" +from dataclasses import dataclass +from typing import List + +from click import Context +from typer.main import get_command + +from tradeexecutor.cli.main import app + + +@dataclass +class EnvVarDescription: + name: str + help: str + type: str + + +def get_available_env_vars() -> List[EnvVarDescription]: + """Get list of environment variable configuration options for trade-executor. + + :return: + List of environment variable names + """ + command = get_command(app) + start = command.commands["start"] + ctx = Context(start) + params = start.get_params(ctx) + result = [] + for p in params: + envvar = p.envvar + if envvar: + # Option --help does not have envvar, etc. + result.append( + EnvVarDescription( + envvar, + p.help, + p.type, + ) + ) + + return result diff --git a/tradeexecutor/cli/main.py b/tradeexecutor/cli/main.py index d030e8aa5..ea86e69de 100644 --- a/tradeexecutor/cli/main.py +++ b/tradeexecutor/cli/main.py @@ -3,10 +3,12 @@ import logging from pathlib import Path from queue import Queue -from typing import Optional +from typing import Optional, List import pkg_resources import typer +from click import Context +from typer.main import get_command from tradeexecutor.ethereum.uniswap_v2_execution import UniswapV2ExecutionModel from tradeexecutor.monkeypatch.dataclasses_json import patch_dataclasses_json @@ -348,4 +350,3 @@ def check_wallet( logger.info("%s: %s %s", details.name, details.convert_to_decimals(balance), details.symbol) -