Skip to content

Commit 05f7c08

Browse files
authored
Merge pull request #2052 from Drakkar-Software/dev
master merge
2 parents 5eaaf84 + 2c9b373 commit 05f7c08

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+902
-228
lines changed

.github/workflows/docker.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ jobs:
8989
username: ${{ secrets.DOCKERHUB_USERNAME }}
9090
password: ${{ secrets.DOCKERHUB_TOKEN }}
9191

92+
- name: Login to Scaleway
93+
if: github.event_name == 'push'
94+
uses: docker/login-action@v1
95+
with:
96+
registry: ${{ secrets.SCALEWAY_DOCKER_REGISTRY }}
97+
username: nologin
98+
password: ${{ secrets.SCALEWAY_PRIVATE_KEY }}
99+
92100
- name: Build latest
93101
if: github.event_name != 'push'
94102
uses: docker/build-push-action@master
@@ -162,6 +170,20 @@ jobs:
162170
cache-from: type=local,src=/tmp/.buildx-cache
163171
cache-to: type=local,dest=/tmp/.buildx-cache
164172

173+
- name: Build and push on tag on scaleway
174+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
175+
uses: docker/build-push-action@master
176+
with:
177+
context: .
178+
file: ./Dockerfile
179+
builder: ${{ steps.buildx.outputs.name }}
180+
platforms: linux/amd64
181+
push: true
182+
tags: |
183+
${{ secrets.SCALEWAY_DOCKER_REGISTRY }}/${{ env.IMAGE }}:${{ env.STABLE }}
184+
cache-from: type=local,src=/tmp/.buildx-cache
185+
cache-to: type=local,dest=/tmp/.buildx-cache
186+
165187
- name: Image digest
166188
run: echo ${{ steps.docker_build_and_psuh.outputs.digest }}
167189

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
*It is strongly advised to perform an update of your tentacles after updating OctoBot. (start.py tentacles --install --all)*
88

9+
## [0.4.11] - 2022-10-12
10+
### Added
11+
- User inputs system
12+
- Phemex exchange
13+
- Run storage
14+
### Updated
15+
- Configuration for each tentacle
16+
- Community bot system instead of devices
17+
### Fixed
18+
- Exchange API issues (Bybit, Kucoin)
19+
920
## [0.4.10] - 2022-09-13
1021
### Updated
1122
- Beta tentacles

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@ VOLUME /octobot/tentacles
3939
VOLUME /octobot/user
4040
EXPOSE 5001
4141

42-
HEALTHCHECK --interval=1m --timeout=30s --retries=3 CMD curl --fail http://localhost:5001 || exit 1
42+
HEALTHCHECK --interval=1m --timeout=30s --retries=3 CMD curl -sS http://127.0.0.1:5001 || exit 1
4343
ENTRYPOINT ["./docker-entrypoint.sh"]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# OctoBot [0.4.10](https://octobot.click/gh-changelog)
1+
# OctoBot [0.4.11](https://octobot.click/gh-changelog)
22
[![PyPI](https://img.shields.io/pypi/v/OctoBot.svg)](https://octobot.click/gh-pypi)
33
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/e07fb190156d4efb8e7d07aaa5eff2e1)](https://app.codacy.com/gh/Drakkar-Software/OctoBot?utm_source=github.com&utm_medium=referral&utm_content=Drakkar-Software/OctoBot&utm_campaign=Badge_Grade_Dashboard)[![Downloads](https://pepy.tech/badge/octobot/month)](https://pepy.tech/project/octobot)
44
[![Dockerhub](https://img.shields.io/docker/pulls/drakkarsoftware/octobot.svg)](https://octobot.click/gh-dockerhub)

octobot/api/backtesting.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,22 @@ def create_independent_backtesting(config,
2626
start_timestamp=None,
2727
end_timestamp=None,
2828
enable_logs=True,
29-
stop_when_finished=False) -> backtesting.IndependentBacktesting:
30-
return backtesting.IndependentBacktesting(config, tentacles_setup_config, data_files,
31-
data_file_path,
32-
run_on_common_part_only=run_on_common_part_only,
33-
join_backtesting_timeout=join_backtesting_timeout,
34-
start_timestamp=start_timestamp,
35-
end_timestamp=end_timestamp,
36-
enable_logs=enable_logs,
37-
stop_when_finished=stop_when_finished)
29+
stop_when_finished=False,
30+
enforce_total_databases_max_size_after_run=True,
31+
enable_storage=True) \
32+
-> backtesting.IndependentBacktesting:
33+
return backtesting.IndependentBacktesting(
34+
config, tentacles_setup_config, data_files,
35+
data_file_path,
36+
run_on_common_part_only=run_on_common_part_only,
37+
join_backtesting_timeout=join_backtesting_timeout,
38+
start_timestamp=start_timestamp,
39+
end_timestamp=end_timestamp,
40+
enable_logs=enable_logs,
41+
stop_when_finished=stop_when_finished,
42+
enforce_total_databases_max_size_after_run=enforce_total_databases_max_size_after_run,
43+
enable_storage=enable_storage,
44+
)
3845

3946

4047
async def initialize_and_run_independent_backtesting(independent_backtesting, log_errors=True) -> None:

octobot/backtesting/independent_backtesting.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ cdef class IndependentBacktesting:
4343
cdef public object post_backtesting_task
4444
cdef public object previous_log_level
4545
cdef public object stopped_event
46+
cdef public bint enforce_total_databases_max_size_after_run
4647

4748
cdef public octobot_backtesting.OctoBotBacktesting octobot_backtesting
4849

octobot/backtesting/independent_backtesting.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import octobot_trading.api as trading_api
4040
import octobot_trading.enums as trading_enums
4141

42+
import octobot.storage as storage
43+
4244

4345
class IndependentBacktesting:
4446
def __init__(self, config,
@@ -50,7 +52,9 @@ def __init__(self, config,
5052
start_timestamp=None,
5153
end_timestamp=None,
5254
enable_logs=True,
53-
stop_when_finished=False):
55+
stop_when_finished=False,
56+
enforce_total_databases_max_size_after_run=True,
57+
enable_storage=True):
5458
self.octobot_origin_config = config
5559
self.tentacles_setup_config = tentacles_setup_config
5660
self.backtesting_config = {}
@@ -72,14 +76,16 @@ def __init__(self, config,
7276
self.enable_logs = enable_logs
7377
self.stop_when_finished = stop_when_finished
7478
self.previous_log_level = commons_logging.get_global_logger_level()
79+
self.enforce_total_databases_max_size_after_run = enforce_total_databases_max_size_after_run
7580
self.octobot_backtesting = backtesting.OctoBotBacktesting(self.backtesting_config,
7681
self.tentacles_setup_config,
7782
self.symbols_to_create_exchange_classes,
7883
self.backtesting_files,
7984
run_on_common_part_only,
8085
start_timestamp=start_timestamp,
8186
end_timestamp=end_timestamp,
82-
enable_logs=self.enable_logs)
87+
enable_logs=self.enable_logs,
88+
enable_storage=enable_storage)
8389

8490
async def initialize_and_run(self, log_errors=True):
8591
try:
@@ -162,11 +168,16 @@ async def _post_backtesting_end_callback(self):
162168
else:
163169
# stop backtesting importers to release database files
164170
await self.octobot_backtesting.stop_importers()
171+
if self.enforce_total_databases_max_size_after_run:
172+
try:
173+
await storage.enforce_total_databases_max_size()
174+
except Exception as e:
175+
self.logger.exception(e, True, f"Error when enforcing max run databases size: {e}")
165176

166177
@staticmethod
167178
def _get_market_delta(symbol, exchange_manager, min_timeframe):
168179
market_data = trading_api.get_symbol_historical_candles(
169-
trading_api.get_symbol_data(exchange_manager, symbol.legacy_symbol()), min_timeframe)
180+
trading_api.get_symbol_data(exchange_manager, str(symbol)), min_timeframe)
170181
market_begin = market_data[enums.PriceIndexes.IND_PRICE_CLOSE.value][0]
171182
market_end = market_data[enums.PriceIndexes.IND_PRICE_CLOSE.value][-1]
172183

@@ -355,8 +366,9 @@ async def _generate_backtesting_id_if_missing(self):
355366
optimization_campaign.OptimizationCampaign.get_campaign_name(self.tentacles_setup_config)
356367
)
357368
run_dbs_identifier.backtesting_id = await run_dbs_identifier.generate_new_backtesting_id()
358-
# initialize to lock the backtesting id
359-
await run_dbs_identifier.initialize()
369+
if self.octobot_backtesting.enable_storage:
370+
# initialize to lock the backtesting id
371+
await run_dbs_identifier.initialize()
360372
self.backtesting_config[common_constants.CONFIG_BACKTESTING_ID] = run_dbs_identifier.backtesting_id
361373

362374
def _find_reference_market_and_update_contract_type(self):
@@ -412,7 +424,7 @@ def _add_config_default_backtesting_values(self):
412424
def _add_crypto_currencies_config(self):
413425
for symbols in self.symbols_to_create_exchange_classes.values():
414426
for symbol in symbols:
415-
symbol_id = symbol.legacy_symbol()
427+
symbol_id = str(symbol)
416428
if symbol_id not in self.backtesting_config[common_constants.CONFIG_CRYPTO_CURRENCIES]:
417429
self.backtesting_config[common_constants.CONFIG_CRYPTO_CURRENCIES][symbol_id] = {
418430
common_constants.CONFIG_CRYPTO_PAIRS: []

octobot/backtesting/octobot_backtesting.pxd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ cdef class OctoBotBacktesting:
3232
cdef public list backtesting_files
3333
cdef public object backtesting
3434
cdef public bint run_on_common_part_only
35+
cdef public object start_time
3536
cdef public object start_timestamp
3637
cdef public object end_timestamp
3738
cdef public bint enable_logs
3839
cdef public dict exchange_type_by_exchange
3940
cdef public object futures_contract_type
41+
cdef public bint enable_storage
4042

4143
cpdef object memory_leak_checkup(self, list to_check_elements)
4244
cpdef object check_remaining_objects(self)

octobot/backtesting/octobot_backtesting.py

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@
1313
#
1414
# You should have received a copy of the GNU General Public
1515
# License along with OctoBot. If not, see <https://www.gnu.org/licenses/>.
16+
import json
1617
import uuid
1718
import gc
1819
import sys
1920
import asyncio
21+
import time
2022

21-
import octobot_commons.logging as logging
23+
import octobot_commons.logging as commons_logging
24+
import octobot_commons.configuration as commons_configuration
25+
import octobot_commons.databases as commons_databases
26+
import octobot_commons.constants as commons_constants
2227

2328
import octobot_backtesting.api as backtesting_api
2429
import octobot_backtesting.importers as importers
@@ -32,10 +37,9 @@
3237
import octobot_trading.api as trading_api
3338
import octobot_trading.enums as trading_enums
3439

35-
import octobot_commons.databases as databases
36-
import octobot_commons.constants as commons_constants
37-
3840
import octobot.logger as logger
41+
import octobot.storage as storage
42+
import octobot.databases_util as databases_util
3943

4044

4145
class OctoBotBacktesting:
@@ -47,8 +51,9 @@ def __init__(self, backtesting_config,
4751
run_on_common_part_only,
4852
start_timestamp=None,
4953
end_timestamp=None,
50-
enable_logs=True):
51-
self.logger = logging.get_logger(self.__class__.__name__)
54+
enable_logs=True,
55+
enable_storage=True):
56+
self.logger = commons_logging.get_logger(self.__class__.__name__)
5257
self.backtesting_config = backtesting_config
5358
self.tentacles_setup_config = tentacles_setup_config
5459
self.bot_id = str(uuid.uuid4())
@@ -60,14 +65,26 @@ def __init__(self, backtesting_config,
6065
self.backtesting_files = backtesting_files
6166
self.backtesting = None
6267
self.run_on_common_part_only = run_on_common_part_only
68+
self.start_time = None
6369
self.start_timestamp = start_timestamp
6470
self.end_timestamp = end_timestamp
6571
self.enable_logs = enable_logs
6672
self.exchange_type_by_exchange = {}
6773
self.futures_contract_type = trading_enums.FutureContractType.LINEAR_PERPETUAL
74+
self.enable_storage = enable_storage
6875

6976
async def initialize_and_run(self):
7077
self.logger.info(f"Starting on {self.backtesting_files} with {self.symbols_to_create_exchange_classes}")
78+
self.start_time = time.time()
79+
await commons_databases.init_bot_storage(
80+
self.bot_id,
81+
databases_util.get_run_databases_identifier(
82+
self.backtesting_config,
83+
self.tentacles_setup_config,
84+
enable_storage=self.enable_storage,
85+
),
86+
False
87+
)
7188
await self._init_evaluators()
7289
await self._init_service_feeds()
7390
await self._init_exchanges()
@@ -91,16 +108,27 @@ async def stop(self, memory_check=False, should_raise=False):
91108
if self.backtesting is None:
92109
self.logger.warning("No backtesting to stop, there was probably an issue when starting the backtesting")
93110
else:
111+
exchange_managers = trading_api.get_exchange_managers_from_exchange_ids(self.exchange_manager_ids)
112+
if exchange_managers and self.enable_storage:
113+
try:
114+
for exchange_manager in exchange_managers:
115+
await trading_api.store_history_in_run_storage(exchange_manager)
116+
except Exception as e:
117+
self.logger.exception(e, True, f"Error when saving exchange historical data: {e}")
118+
try:
119+
await self._store_metadata(exchange_managers)
120+
self.logger.info(f"Stored backtesting run metadata")
121+
except Exception as e:
122+
self.logger.exception(e, True, f"Error when saving run metadata: {e}")
94123
await backtesting_api.stop_backtesting(self.backtesting)
95124
try:
96-
for exchange_manager in trading_api.get_exchange_managers_from_exchange_ids(self.exchange_manager_ids):
97-
exchange_managers.append(exchange_manager)
125+
for exchange_manager in exchange_managers:
98126
await trading_api.stop_exchange(exchange_manager)
99127
except KeyError:
100128
# exchange managers are not added in global exchange list when an exception occurred
101129
pass
102130
# close run databases
103-
await trading_api.close_bot_storage(self.bot_id)
131+
await commons_databases.close_bot_storage(self.bot_id)
104132
# stop evaluators
105133
for evaluators in self.evaluators:
106134
# evaluators by type
@@ -109,7 +137,7 @@ async def stop(self, memory_check=False, should_raise=False):
109137
if evaluator is not None:
110138
await evaluator_api.stop_evaluator(evaluator)
111139
# close all caches (if caches have to be used later on, they can always be re-opened)
112-
await databases.CacheManager().close_cache(commons_constants.UNPROVIDED_CACHE_IDENTIFIER)
140+
await commons_databases.CacheManager().close_cache(commons_constants.UNPROVIDED_CACHE_IDENTIFIER)
113141
try:
114142
await evaluator_api.stop_all_evaluator_channels(self.matrix_id)
115143
except KeyError:
@@ -170,6 +198,10 @@ def check_remaining_objects(self):
170198
}
171199
for obj in gc.get_objects():
172200
if isinstance(obj, to_watch_objects):
201+
if isinstance(obj, exchanges.ExchangeManager) and not obj.is_initialized:
202+
# Ignore exchange managers that have not been initialized
203+
# and are irrelevant. Pytest fixtures can also retain references failing tests
204+
continue
173205
objects_references[type(obj)][1].append(obj)
174206
objects_references[type(obj)] = (objects_references[type(obj)][0] + 1,
175207
objects_references[type(obj)][1])
@@ -186,6 +218,24 @@ def check_remaining_objects(self):
186218
f"[Dev oriented error: no effect on backtesting result, please report if you see it]: {errors}"
187219
)
188220

221+
async def _store_metadata(self, exchange_managers):
222+
run_db = commons_databases.RunDatabasesProvider.instance().get_run_db(self.bot_id)
223+
await run_db.flush()
224+
user_inputs = await commons_configuration.get_user_inputs(run_db)
225+
await storage.store_run_metadata(
226+
self.bot_id,
227+
exchange_managers,
228+
self.start_time,
229+
user_inputs=user_inputs,
230+
)
231+
metadata = await storage.store_backtesting_run_metadata(
232+
exchange_managers,
233+
self.start_time,
234+
user_inputs,
235+
commons_databases.RunDatabasesProvider.instance().get_run_databases_identifier(self.bot_id),
236+
)
237+
self.logger.info(f"Backtesting metadata:\n{json.dumps(metadata, indent=4)}")
238+
189239
async def _init_evaluators(self):
190240
self.matrix_id = await evaluator_api.initialize_evaluators(self.backtesting_config, self.tentacles_setup_config)
191241
await evaluator_api.create_evaluator_channels(self.matrix_id, is_backtesting=True)
@@ -223,7 +273,6 @@ async def _init_exchanges(self):
223273
data_files=self.backtesting_files)
224274
# modify_backtesting_channels before creating exchanges as they require the current backtesting time to
225275
# initialize
226-
trading_api.init_bot_storage(self.bot_id, self.backtesting_config, self.tentacles_setup_config)
227276
await backtesting_api.adapt_backtesting_channels(self.backtesting,
228277
self.backtesting_config,
229278
importers.ExchangeDataImporter,
@@ -241,7 +290,8 @@ async def _init_exchanges(self):
241290
.is_simulated() \
242291
.is_rest_only() \
243292
.is_backtesting(self.backtesting) \
244-
.is_future(is_future, self.futures_contract_type)
293+
.is_future(is_future, self.futures_contract_type) \
294+
.enable_storage(self.enable_storage)
245295
try:
246296
await exchange_builder.build()
247297
finally:
@@ -260,5 +310,8 @@ async def start_exchange_loggers(self):
260310
def _get_remaining_object_error(obj, expected, actual):
261311
error = f"too many remaining {obj.__name__} instances: expected: {expected} actual {actual[0]}"
262312
for i in range(len(actual[1])):
263-
error += f"{sys.getrefcount(actual[1][i])} references on {actual[1][i]}"
313+
debug_info = ""
314+
if isinstance(actual[1][i], exchanges.ExchangeManager):
315+
debug_info = f" ({actual[1][i].debug_info})"
316+
error += f"{sys.getrefcount(actual[1][i])} references on {actual[1][i]} {debug_info}"
264317
return error

octobot/cli.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@
1313
#
1414
# You should have received a copy of the GNU General Public
1515
# License along with OctoBot. If not, see <https://www.gnu.org/licenses/>.
16-
# prevents distutils_patch.py:26: UserWarning: Distutils was imported before Setuptools. This usage is discouraged
17-
# and may exhibit undesirable behaviors or errors. Please use Setuptools' objects directly or at least import Setuptools
18-
# first
19-
from distutils.version import LooseVersion
16+
17+
import packaging.version as packaging_version
2018

2119
import argparse
2220
import os
@@ -401,7 +399,7 @@ def main(args=None):
401399
try:
402400
from octobot_tentacles_manager import VERSION
403401

404-
if LooseVersion(VERSION) < MIN_TENTACLE_MANAGER_VERSION:
402+
if packaging_version.Version(VERSION) < packaging_version.Version(MIN_TENTACLE_MANAGER_VERSION):
405403
print("OctoBot requires OctoBot-Tentacles-Manager in a minimum version of " + MIN_TENTACLE_MANAGER_VERSION +
406404
" you can install and update OctoBot-Tentacles-Manager using the following command: "
407405
"python3 -m pip install -U OctoBot-Tentacles-Manager", file=sys.stderr)

0 commit comments

Comments
 (0)