Skip to content

Commit

Permalink
Merge pull request #223 from Azulinho/next_release
Browse files Browse the repository at this point in the history
drop SORT_BY for single checks, add VALID_TOKENS, revert find_best_results_from_backtesting_log()
  • Loading branch information
Azulinho authored Sep 9, 2023
2 parents 9063dbf + bc8e042 commit 47326f2
Show file tree
Hide file tree
Showing 11 changed files with 587 additions and 176 deletions.
3 changes: 3 additions & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
if [ -e .envrc.local ]; then
source .envrc.local
fi
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ strategies/Local*
control/*
.tests*.txt
lastfewdays.log.gz
.envrc.local
*.inotifywait
5 changes: 3 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,13 @@ RUN eatmydata /cryptobot/.venv/bin/pip install -r requirements.txt && \
rm -rf /tmp/*

COPY lib/ lib/
COPY utils/prove-backtesting.py utils/prove-backtesting.py
COPY utils/prove-backtesting.sh utils/prove-backtesting.sh
COPY utils/__init__.py utils/__init__.py
COPY utils/pull_klines.py utils/pull_klines.py
COPY utils/config-endpoint-service.py utils/config-endpoint-service.py
COPY utils/config-endpoint-service.sh utils/config-endpoint-service.sh
COPY klines_caching_service.py klines_caching_service.py
COPY price_log_service.py price_log_service.py
COPY app.py .
COPY utils/prove-backtesting.sh utils/prove-backtesting.sh
COPY utils/prove-backtesting.py utils/prove-backtesting.py

66 changes: 47 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ A python based trading bot for Binance, which relies heavily on backtesting.
* [PRICE_LOG_SERVICE_URL](#price_log_service_url)
* [KLINES_CACHING_SERVICE_URL](#klines_caching_service_url)
* [CONCURRENCY](#concurrency)
* [SORT_BY](#sort_by)
* [max_profit_on_clean_wins](#max_profit_on_clean_wins)
* [number_of_clean_wins](#number_of_clean_wins)
* [greed](#greed)
* [MIN_PROFIT](#min_profit)
* [MIN_WINS](#min_wins)
* [MAX_LOSSES](#max_losses)
* [MAX_STALES](#max_stales)
* [MAX_HOLDS](#max_holds)
* [VALID_TOKENS](#valid_tokens)
11. [Bot command center](#bot-command-center)
12. [Development/New features](#development/new-features)

Expand Down Expand Up @@ -495,7 +497,7 @@ Use it as:
returned at least 10% in profit.

```yaml
MIN: 10
MIN_PROFIT: 10
```
3. Update the dates to test against, for example:
Expand Down Expand Up @@ -1024,37 +1026,63 @@ The number of parallel backtesting processes to run.
CONCURRENCY: 4
```
### SORT_BY
### MIN_PROFIT
How to order results from the different test runs in automated-backtesting in
order to choose the best config for each coin.
Defines the minimum profit we expect from a single coin run in prove-backtesting
before we select this run in our forward backtesting session.
#### max_profit_on_clean_wins
```yaml
MIN_PROFIT: 10.0
```
### MIN_WINS
Defines the minimum number of wins we expect from a single coin run in prove-backtesting
before we select this run in our forward backtesting session.
```yaml
MIN_WINS: 3
```
Use the config that provided the best profit for this coin and did not result
in any STOP_LOSSES or STALES or HOLDS at the end of the runs.
### MAX_LOSSES
Defines the maximum number of losses we tolerate from a single coin run in prove-backtesting
before we select this run in our forward backtesting session.
```yaml
SORT_BY: "max_profit_on_clean_wins"
MAX_LOSSES: 2
```
#### number_of_clean_wins
### MAX_STALES
Use the config that returned the highest number of wins for this coin and did
not result in any STOP_LOSSES or STALES or HOLDS at the end of the runs
Defines the maximum number of stales we tolerate from a single coin run in prove-backtesting
before we select this run in our forward backtesting session.
```yaml
SORT_BY: "number_of_clean_wins"
MAX_STALES: 2
```
#### greed
### MAX_HOLDS
Use the config that returned the highest profit for this coin.
Defines the maximum number of holds we tolerate from a single coin run in prove-backtesting
before we select this run in our forward backtesting session.
Use 0 or 1
```yaml
SORT_BY: "greed
MAX_HOLDS: 1
```
### VALID_TOKENS
Defines a list of tokens that the bot will use in a prove-backtesting session,
any tokens not defined will be ignored.
Defaults to [], when set this [] all tokens are included.
```yaml
VALID_TOKENS: ["BTC", "ETH"]
```
## Bot command center
The bot is running a *pdb* endpoint on container port 5555.
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ urllib3-mock
virtualenv
types-requests
flaky
openai
2 changes: 1 addition & 1 deletion run
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ function github_actions_ci_pr_docker_tests() {
TAG=pr CONFIG_FILE=prove-backtesting.yaml

wc -l results/prove-backtesting.prove-backtesting.yaml.txt \
| grep '44'
| grep '49'

for ta in 01 02 03 04 05 06 07 08 09
do
Expand Down
8 changes: 6 additions & 2 deletions tests/prove-backtesting.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@ KLINES_CACHING_SERVICE_URL: "http://klines:8999"
PRICE_LOG_SERVICE_URL: "http://price-log-service:8998"
CONCURRENCY: 1

MIN: 1
FILTER_BY: "ETH"
SORT_BY: "greed"
MIN_PROFIT: 1
MIN_WINS: 0
MAX_STALES: 9999
MAX_HOLDS: 9999
MAX_LOSSES: 9999
FROM_DATE: 20211202
END_DATE: 20211206
ROLL_BACKWARDS: 1
ROLL_FORWARD: 1
STOP_BOT_ON_LOSS: false
STOP_BOT_ON_STALE: false
VALID_TOKENS: ["ETH"]

STRATEGY: BuyDropSellRecoveryStrategy

Expand Down
168 changes: 156 additions & 12 deletions tests/test_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,16 @@
# pylint: disable=missing-function-docstring
# pylint: disable=redefined-outer-name
# pylint: disable=import-outside-toplevel
# pylint: disable=no-self-use
from datetime import datetime
from unittest import mock
import json
from flaky import flaky

import pytest
import app
import lib
import lib.bot
import lib.coin
import pytest
import requests
import json


@pytest.fixture()
Expand Down Expand Up @@ -518,7 +516,7 @@ def test_calculate_volume_size(self, bot, coin):
bot, "get_step_size", return_value=(True, "0.00001000")
) as _:
ok, volume = bot.calculate_volume_size(coin)
assert ok == True
assert ok is True
assert volume == 0.5

def test_get_binance_prices(self, bot):
Expand Down Expand Up @@ -767,12 +765,12 @@ def test_run_stategy_gives_False_if_coin_not_in_tickers(self, bot, coin):
def test_run_stategy_gives_False_if_coin_is_delisted(self, bot, coin):
coin.delisted = True
bot.coins["BTCUSDT"] = coin
bot.run_strategy(coin) is False
assert bot.run_strategy(coin) is False

def test_run_stategy_gives_False_if_coin_is_naughty(self, bot, coin):
coin.naughty = True
bot.coins["BTCUSDT"] = coin
bot.run_strategy(coin) is False
assert bot.run_strategy(coin) is False

def test_run_stategy_calls_sale_if_wallet_not_empty(self, bot, coin):
# if there are coins in WALLET
Expand Down Expand Up @@ -926,6 +924,34 @@ def test_place_sell_order(self, bot, coin):

assert bot.place_sell_order(coin) is False

def test_get_ticker_with_default(self):
tickers = {
"ETH": {"BUY_AT_PERCENTAGE": "0.05", "SELL_AT_PERCENTAGE": "0.1"},
"BTC": {"BUY_AT_PERCENTAGE": "0.02", "SELL_AT_PERCENTAGE": "0.05"},
}

assert (
lib.bot.get_ticker_with_default(
tickers, "ETH", "BUY_AT_PERCENTAGE"
)
== "0.05"
)
assert (
lib.bot.get_ticker_with_default(
tickers, "ETH", "SELL_AT_PERCENTAGE"
)
== "0.1"
)

def test_get_ticker_with_default_not_in_ticket(self):
tickers = {}
assert (
lib.bot.get_ticker_with_default(
tickers, "ETH", "STOP_LOSS_AT_PERCENTAGE"
)
== "-99999999"
)


class TestBotCheckForSaleConditions:
def test_returns_early_on_empty_wallet(self, bot, coin):
Expand Down Expand Up @@ -1104,6 +1130,81 @@ def test_buy_coin_using_limit_order_in_testnet(self, bot, coin):
# TODO: assert that clear_all_coins_stats


class TestPlaceBuyOrder:
def test_place_buy_order_limit(
self,
bot,
coin,
):
bot.order_type = "LIMIT"
bot.extract_order_data = mock.MagicMock()
bot.client.get_order_book = mock.MagicMock()
bot.client.create_order = mock.MagicMock()
bot.client.get_order = mock.MagicMock()
lib.bot.udatetime.now = mock.MagicMock()

lib.bot.udatetime.now.return_value = datetime(2021, 1, 1, 0, 0, 0)
bot.client.get_order_book.return_value = {"asks": [(50000, 1)]}
bot.client.get_order.return_value = {
"orderId": 123,
"status": "FILLED",
"price": 50000,
"executedQty": 1,
}

bot.extract_order_data.return_value = (
True,
{"avgPrice": 50000, "volume": 1},
)

result = bot.place_buy_order(coin, 1.0)

bot.client.create_order.assert_called_with(
symbol="BTCUSDT",
side="BUY",
type="LIMIT",
quantity=1.0,
timeInForce="FOK",
price=50000,
)
assert result is True

def test_place_buy_order_market(
self,
coin,
bot,
):
bot.order_type = "MARKET"
bot.extract_order_data = mock.MagicMock()
bot.client.get_order_book = mock.MagicMock()
bot.client.create_order = mock.MagicMock()
bot.client.get_order = mock.MagicMock()
lib.bot.udatetime.now = mock.MagicMock()

lib.bot.udatetime.now.return_value = datetime(2021, 1, 1, 0, 0, 0)
bot.client.get_order_book.return_value = {"asks": [(50000, 1)]}
bot.client.get_order.return_value = {
"orderId": 123,
"status": "FILLED",
"price": 50000,
"executedQty": 1,
}

bot.extract_order_data.return_value = (
True,
{"avgPrice": 50000, "volume": 1},
)
result = bot.place_buy_order(coin, 1.0)

bot.client.create_order.assert_called_with(
symbol="BTCUSDT",
side="BUY",
type="MARKET",
quantity=1.0,
)
assert result is True


class TestCoinStatus:
def test_stop_loss(self, bot, coin):
bot.wallet = ["BTCUSDT"]
Expand Down Expand Up @@ -1573,9 +1674,52 @@ def test_coin_bought_when_price_above_averages_threshold(self, bot, coin):
assert result is True


class TestBacktesting:
def backtesting(self):
pass
class TestBotState: # pylint: disable=too-few-public-methods
def test_save_coins(self, bot, coin):
mock_open = mock.mock_open()

def backtest_logfile(self):
pass
lib.bot.fsync = mock.MagicMock()
lib.bot.unlink = mock.MagicMock()
lib.bot.rename = mock.MagicMock()

bot.coins["BTCUSDT"] = coin

with mock.patch("builtins.open", mock_open):
bot.save_coins()

expected_calls = [
mock.call().write(mock.ANY),
]
mock_open.assert_has_calls(expected_calls, any_order=True)


class TestForDelistedCoin:
def test_coin_is_delisted(self, bot, coin):
bot.load_klines_for_coin = mock.MagicMock(return_value=[])
bot.coins["BTCUSDT"] = coin
assert bot.check_for_delisted_coin("BTCUSDT")

def test_coin_is_not_delisted(self, bot, coin):
bot.load_klines_for_coin = mock.MagicMock(return_value=[1])
bot.coins["BTCUSDT"] = coin
assert bot.check_for_delisted_coin("BTCUSDT") is False


class TestProcessLine: # pylint: disable=too-few-public-methods
def test_process_line(self, bot, coin):
symbol = "BTCUSDT"
date = datetime(2021, 1, 1, 0, 0, 0).timestamp()
market_price = 40000.0

bot.coins = {}
bot.coins["BTCUSDT"] = coin

lib.bot.udatetime.now = mock.MagicMock(
return_value=datetime(2021, 1, 1, 0, 0, 0)
)
bot.process_line(symbol, date, market_price)

assert symbol in bot.coins
assert coin.symbol == symbol
assert coin.date == datetime(2021, 1, 1, 0, 0, 0).timestamp()
assert coin.price == market_price
Loading

0 comments on commit 47326f2

Please sign in to comment.