├── .coveragerc ├── .github ├── dependabot.yml └── workflows │ └── main.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── dev_requirements.txt ├── octobot_commons ├── __init__.py ├── aiohttp_util.py ├── async_job.py ├── asyncio_tools.py ├── authentication.py ├── channels_name.py ├── configuration │ ├── __init__.py │ ├── config_file_manager.py │ ├── config_operations.py │ ├── configuration.py │ ├── fields_utils.py │ ├── historical_configuration.py │ ├── user_input_configuration.py │ └── user_inputs.py ├── constants.py ├── data_util.py ├── databases │ ├── __init__.py │ ├── bases │ │ ├── __init__.py │ │ ├── base_database.py │ │ └── document_database.py │ ├── cache_client.py │ ├── cache_manager.py │ ├── database_caches │ │ ├── __init__.py │ │ ├── chronological_read_database_cache.py │ │ └── generic_database_cache.py │ ├── databases_util │ │ ├── __init__.py │ │ └── cache_wrapper.py │ ├── document_database_adaptors │ │ ├── __init__.py │ │ ├── abstract_document_database_adaptor.py │ │ └── tinydb_adaptor.py │ ├── global_storage │ │ ├── __init__.py │ │ └── global_shared_memory_storage.py │ ├── implementations │ │ ├── __init__.py │ │ ├── _exchange_database.py │ │ ├── cache_database.py │ │ ├── cache_timestamp_database.py │ │ ├── db_reader.py │ │ ├── db_writer.py │ │ ├── db_writer_reader.py │ │ └── meta_database.py │ ├── relational_databases │ │ ├── __init__.py │ │ └── sqlite │ │ │ ├── __init__.py │ │ │ ├── cursor_pool.py │ │ │ ├── cursor_wrapper.py │ │ │ └── sqlite_database.py │ └── run_databases │ │ ├── __init__.py │ │ ├── abstract_run_databases_pruner.py │ │ ├── file_system_run_databases_pruner.py │ │ ├── run_databases_identifier.py │ │ ├── run_databases_provider.py │ │ ├── run_databases_pruning_factory.py │ │ ├── storage.py │ │ └── utils.py ├── dataclasses │ ├── __init__.py │ ├── flexible_dataclass.py │ ├── minimizable_dataclass.py │ └── updatable_dataclass.py ├── dict_util.py ├── display │ ├── __init__.py │ ├── display_factory.py │ ├── display_translator.py │ └── plot_settings.py ├── enums.py ├── errors.py ├── evaluators_util.py ├── external_resources_manager.py ├── html_util.py ├── json_util.py ├── list_util.py ├── logging │ ├── __init__.py │ └── logging_util.py ├── logical_operators.py ├── multiprocessing_util.py ├── number_util.py ├── optimization_campaign.py ├── os_clock_sync.py ├── os_util.py ├── pretty_printer.py ├── profiles │ ├── __init__.py │ ├── exchange_auth_data.py │ ├── profile.py │ ├── profile_data.py │ ├── profile_data_import.py │ ├── profile_sharing.py │ ├── profile_sync.py │ ├── tentacles_profile_data_adapter.py │ └── tentacles_profile_data_translator.py ├── signals │ ├── __init__.py │ ├── signal.py │ ├── signal_builder_wrapper.py │ ├── signal_bundle.py │ ├── signal_bundle_builder.py │ ├── signal_factory.py │ ├── signal_publisher.py │ └── signals_emitter.py ├── singleton │ ├── __init__.py │ └── singleton_class.py ├── support.py ├── symbols │ ├── __init__.py │ ├── symbol.py │ └── symbol_util.py ├── system_resources_watcher.py ├── tentacles_management │ ├── __init__.py │ ├── abstract_tentacle.py │ └── class_inspector.py ├── tests │ ├── __init__.py │ └── test_config.py ├── thread_util.py ├── time_frame_manager.py ├── timestamp_util.py └── tree │ ├── __init__.py │ ├── base_tree.py │ ├── event_provider.py │ └── event_tree.py ├── requirements.txt ├── setup.py ├── standard.rc └── tests ├── __init__.py ├── configuration ├── __init__.py ├── test_configuration.py └── test_fields_util.py ├── databases ├── __init__.py ├── global_storage │ ├── __init__.py │ └── test_global_shared_memory_storage.py ├── relational_databases │ ├── __init__.py │ └── sqlite │ │ ├── __init__.py │ │ └── test_sqlite_database.py └── run_databases │ ├── __init__.py │ └── test_run_databases_provider.py ├── dataclasses ├── test_flexible_dataclass.py └── test_updatable_dataclass.py ├── logging ├── __init__.py └── test_logging_util.py ├── profiles ├── __init__.py ├── test_profile.py ├── test_profile_data.py └── test_profile_sharing.py ├── signals ├── __init__.py ├── test_signal.py ├── test_signal_builder_wrapper.py ├── test_signal_bundle.py ├── test_signal_bundle_builder.py ├── test_signal_factory.py └── test_signal_publisher.py ├── static ├── ExchangeHistoryDataCollector_1589740606.4862757.data ├── config.json ├── default_config.json ├── default_profile.png ├── invalid_profile │ └── profile.json ├── profile.json ├── profile_schema.json └── second_ExchangeHistoryDataCollector_1589740606.4862757.data ├── symbols ├── __init__.py ├── test_symbol.py └── test_symbol_util.py ├── tentacles_management ├── __init__.py ├── test_abstract_tentacle.py └── test_class_inspector.py ├── test_aiohttp_util.py ├── test_async_job.py ├── test_asyncio_tools.py ├── test_data_util.py ├── test_dict_util.py ├── test_evaluator_util.py ├── test_html_util.py ├── test_list_util.py ├── test_number_util.py ├── test_os_util.py ├── test_pretty_printer.py ├── test_singleton.py ├── test_time_frame_manager.py ├── test_timestamp_util.py ├── thread_util.py └── tree ├── __init__.py ├── test_base_tree.py └── test_event_tree.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | octobot_commons/channels_name.py 4 | octobot_commons/constants.py 5 | octobot_commons/errors.py 6 | octobot_commons/enums.py 7 | venv/* 8 | tests/* 9 | setup.py 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | open-pull-requests-limit: 10 8 | allow: 9 | - dependency-name: "jsonschema" 10 | - dependency-name: "cython" 11 | - dependency-name: "numpy" 12 | reviewers: 13 | - Herklos 14 | - GuillaumeDSM 15 | assignees: 16 | - Herklos 17 | - GuillaumeDSM 18 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Github-Action-CI 2 | on: 3 | push: 4 | branches: 5 | - 'master' 6 | tags: 7 | - '*' 8 | pull_request: 9 | 10 | jobs: 11 | lint: 12 | uses: Drakkar-Software/.github/.github/workflows/python3_lint_workflow.yml@master 13 | with: 14 | project_main_package: octobot_commons 15 | use_black: true 16 | 17 | tests: 18 | needs: lint 19 | uses: Drakkar-Software/.github/.github/workflows/python3_tests_workflow.yml@master 20 | secrets: 21 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 22 | 23 | publish: 24 | needs: tests 25 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 26 | uses: Drakkar-Software/.github/.github/workflows/python3_sdist_workflow.yml@master 27 | secrets: 28 | PYPI_OFFICIAL_UPLOAD_URL: ${{ secrets.PYPI_OFFICIAL_UPLOAD_URL }} 29 | PYPI_USERNAME: __token__ 30 | PYPI_PASSWORD: ${{ secrets.PYPI_TOKEN }} 31 | 32 | notify: 33 | if: ${{ failure() }} 34 | needs: 35 | - lint 36 | - tests 37 | - publish 38 | uses: Drakkar-Software/.github/.github/workflows/failure_notify_workflow.yml@master 39 | secrets: 40 | DISCORD_GITHUB_WEBHOOK: ${{ secrets.DISCORD_GITHUB_WEBHOOK }} 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | *.c 107 | 108 | .idea 109 | user 110 | cython_debug 111 | wheelhouse 112 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include octobot_commons *.pxd 2 | 3 | include README.md 4 | include LICENSE 5 | include CHANGELOG.md 6 | include requirements.txt 7 | 8 | global-exclude *.c 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PYTHON=python 2 | PYTHON_VENV=venv/bin/python 3 | COMPILER=gcc 4 | LINKER=gcc 5 | 6 | CLEAN_EXT=*.o *.c *.so 7 | EXTENSION_FOLDER=octobot_commons 8 | CFLAGS=-O9 9 | 10 | PYTHON=$(PYTHON_VENV) 11 | 12 | # export PYTHONPATH=$PYTHONPATH:. 13 | 14 | help: 15 | @echo "OctoBot-Commons Cython Makefile. Available tasks:" 16 | @echo "build -> build the Cython extension module." 17 | @echo "clean -> clean the Cython extension module." 18 | @echo "debug -> debug the Cython extension module." 19 | @echo "run -> run the Cython extension module." 20 | 21 | all: build 22 | 23 | .PHONY: build 24 | build: clean 25 | $(PYTHON) setup.py build_ext --inplace 26 | 27 | .PHONY: clean 28 | clean: 29 | rm -rf build 30 | rm -rf auditwheel 31 | rm -rf dist 32 | rm -rf .eggs 33 | rm -rf *.egg-info 34 | for i in $(CLEAN_EXT); do find $(EXTENSION_FOLDER) -name "$$i" -delete; done 35 | 36 | .PHONY: debug 37 | debug: 38 | gdb -ex r --args $(PYTHON) demo.py 39 | 40 | .PHONY: run 41 | run: build 42 | $(PYTHON) demo.py 43 | 44 | # Suffix rules 45 | .PRECIOUS: %.c 46 | %: %.o 47 | $(LINKER) -o $@ -L$(LIBRARY_DIR) -l$(PYTHON_LIB) $(SYSLIBS) $< 48 | 49 | %.o: %.c 50 | $(COMPILER) $(CFLAGS) -I$(INCLUDE_DIR) -c $< -o $@ 51 | 52 | %.c: %.py 53 | cython -a --embed $< 54 | 55 | %.c: %.pyx 56 | cython -a --embed $< 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OctoBot-Commons [1.9.77](https://github.com/Drakkar-Software/OctoBot-Commons/blob/master/CHANGELOG.md) 2 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/b31f3ab3511744a5a5ca6b9bb48e77bb)](https://app.codacy.com/gh/Drakkar-Software/OctoBot-Commons?utm_source=github.com&utm_medium=referral&utm_content=Drakkar-Software/OctoBot-Commons&utm_campaign=Badge_Grade_Dashboard) 3 | [![PyPI](https://img.shields.io/pypi/v/OctoBot-Commons.svg)](https://pypi.python.org/pypi/OctoBot-Commons/) 4 | [![Coverage Status](https://coveralls.io/repos/github/Drakkar-Software/OctoBot-Commons/badge.svg?branch=master)](https://coveralls.io/github/Drakkar-Software/OctoBot-Commons?branch=master) 5 | [![Github-Action-CI](https://github.com/Drakkar-Software/OctoBot-Commons/workflows/Github-Action-CI/badge.svg)](https://github.com/Drakkar-Software/OctoBot-Commons/actions) 6 | [![Build Status](https://cloud.drone.io/api/badges/Drakkar-Software/OctoBot-Commons/status.svg)](https://cloud.drone.io/Drakkar-Software/OctoBot-Commons) 7 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 8 | 9 | [OctoBot](https://github.com/Drakkar-Software/OctoBot) project common modules. 10 | -------------------------------------------------------------------------------- /dev_requirements.txt: -------------------------------------------------------------------------------- 1 | pytest>=5.4.0 2 | pytest-pep8 3 | pytest-cov 4 | pytest-asyncio 5 | pytest-xdist 6 | 7 | mock>=4.0.1 8 | 9 | coverage 10 | coveralls 11 | 12 | twine 13 | pip 14 | setuptools 15 | wheel 16 | 17 | pur 18 | 19 | pylint 20 | black==23.3.0 21 | -------------------------------------------------------------------------------- /octobot_commons/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=W0511 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | 18 | PROJECT_NAME = "OctoBot-Commons" 19 | VERSION = "1.9.77" # major.minor.revision 20 | 21 | MARKET_SEPARATOR = "/" 22 | SETTLEMENT_ASSET_SEPARATOR = ":" 23 | DICT_BULLET_TOKEN_STR = "\n " 24 | 25 | OCTOBOT_KEY = b"uVEw_JJe7uiXepaU_DR4T-ThkjZlDn8Pzl8hYPIv7w0=" # TODO temp 26 | -------------------------------------------------------------------------------- /octobot_commons/channels_name.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import enum 17 | 18 | 19 | class OctoBotChannelsName(enum.Enum): 20 | """ 21 | OctoBot-Evaluators channel names 22 | """ 23 | 24 | OCTOBOT_CHANNEL = "OctoBot" 25 | 26 | 27 | class OctoBotUserChannelsName(enum.Enum): 28 | """ 29 | OctoBot-Backtesting channel names 30 | """ 31 | 32 | USER_COMMANDS_CHANNEL = "UserCommands" 33 | 34 | 35 | class OctoBotEvaluatorsChannelsName(enum.Enum): 36 | """ 37 | OctoBot-Evaluators channel names 38 | """ 39 | 40 | MATRIX_CHANNEL = "Matrix" 41 | EVALUATORS_CHANNEL = "Evaluators" 42 | 43 | 44 | class OctoBotBacktestingChannelsName(enum.Enum): 45 | """ 46 | OctoBot-Backtesting channel names 47 | """ 48 | 49 | TIME_CHANNEL = "Time" 50 | 51 | 52 | class OctoBotCommunityChannelsName(enum.Enum): 53 | """ 54 | OctoBot community channel names 55 | """ 56 | 57 | REMOTE_TRADING_SIGNALS_CHANNEL = "RemoteTradingSignals" 58 | 59 | 60 | class OctoBotTradingChannelsName(enum.Enum): 61 | """ 62 | OctoBot-Trading channel names 63 | """ 64 | 65 | OHLCV_CHANNEL = "OHLCV" 66 | TICKER_CHANNEL = "Ticker" 67 | MINI_TICKER_CHANNEL = "MiniTicker" 68 | RECENT_TRADES_CHANNEL = "RecentTrade" 69 | ORDER_BOOK_CHANNEL = "OrderBook" 70 | ORDER_BOOK_TICKER_CHANNEL = "OrderBookTicker" 71 | KLINE_CHANNEL = "Kline" 72 | TRADES_CHANNEL = "Trades" 73 | LIQUIDATIONS_CHANNEL = "Liquidations" 74 | ORDERS_CHANNEL = "Orders" 75 | BALANCE_CHANNEL = "Balance" 76 | BALANCE_PROFITABILITY_CHANNEL = "BalanceProfitability" 77 | POSITIONS_CHANNEL = "Positions" 78 | MODE_CHANNEL = "Mode" 79 | MARK_PRICE_CHANNEL = "MarkPrice" 80 | FUNDING_CHANNEL = "Funding" 81 | -------------------------------------------------------------------------------- /octobot_commons/configuration/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | from octobot_commons.configuration import configuration 17 | from octobot_commons.configuration import config_file_manager 18 | from octobot_commons.configuration import config_operations 19 | from octobot_commons.configuration import fields_utils 20 | from octobot_commons.configuration import user_inputs 21 | from octobot_commons.configuration import user_input_configuration 22 | from octobot_commons.configuration import historical_configuration 23 | 24 | 25 | from octobot_commons.configuration.configuration import ( 26 | Configuration, 27 | ) 28 | from octobot_commons.configuration.config_file_manager import ( 29 | get_user_config, 30 | load, 31 | dump, 32 | encrypt_values_if_necessary, 33 | handle_encrypted_value, 34 | ) 35 | from octobot_commons.configuration.config_operations import ( 36 | filter_to_update_data, 37 | parse_and_update, 38 | merge_dictionaries_by_appending_keys, 39 | clear_dictionaries_by_keys, 40 | ) 41 | from octobot_commons.configuration.fields_utils import ( 42 | has_invalid_default_config_value, 43 | encrypt, 44 | decrypt, 45 | decrypt_element_if_possible, 46 | get_password_hash, 47 | ) 48 | from octobot_commons.configuration.user_inputs import ( 49 | UserInput, 50 | UserInputFactory, 51 | sanitize_user_input_name, 52 | save_user_input, 53 | get_user_input_tentacle_type, 54 | get_user_inputs, 55 | clear_user_inputs, 56 | ) 57 | from octobot_commons.configuration.user_input_configuration import ( 58 | load_user_inputs_from_class, 59 | get_raw_config_and_user_inputs_from_class, 60 | get_raw_config_and_user_inputs, 61 | load_and_save_user_inputs, 62 | ) 63 | from octobot_commons.configuration.historical_configuration import ( 64 | add_historical_tentacle_config, 65 | get_historical_tentacle_config, 66 | get_oldest_historical_tentacle_config_time, 67 | ) 68 | 69 | 70 | __all__ = [ 71 | "Configuration", 72 | "get_user_config", 73 | "load", 74 | "dump", 75 | "encrypt_values_if_necessary", 76 | "handle_encrypted_value", 77 | "filter_to_update_data", 78 | "parse_and_update", 79 | "merge_dictionaries_by_appending_keys", 80 | "clear_dictionaries_by_keys", 81 | "has_invalid_default_config_value", 82 | "encrypt", 83 | "decrypt", 84 | "decrypt_element_if_possible", 85 | "get_password_hash", 86 | "UserInput", 87 | "UserInputFactory", 88 | "sanitize_user_input_name", 89 | "save_user_input", 90 | "get_user_input_tentacle_type", 91 | "get_user_inputs", 92 | "clear_user_inputs", 93 | "load_user_inputs_from_class", 94 | "get_raw_config_and_user_inputs_from_class", 95 | "get_raw_config_and_user_inputs", 96 | "load_and_save_user_inputs", 97 | "add_historical_tentacle_config", 98 | "get_historical_tentacle_config", 99 | "get_oldest_historical_tentacle_config_time", 100 | ] 101 | -------------------------------------------------------------------------------- /octobot_commons/configuration/fields_utils.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=W1203 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | import logging 18 | import hashlib 19 | import cryptography.fernet as fernet 20 | 21 | import octobot_commons 22 | import octobot_commons.constants as commons_constants 23 | 24 | 25 | def has_invalid_default_config_value(*config_values): 26 | """ 27 | Check if config has invalid values 28 | :param config_values: the config values to check 29 | :return: the check result 30 | """ 31 | return any( 32 | value in commons_constants.DEFAULT_CONFIG_VALUES for value in config_values 33 | ) 34 | 35 | 36 | def encrypt(data): 37 | """ 38 | Basic encryption 39 | :param data: the data to encrypt 40 | :return: the encrypted data 41 | """ 42 | try: 43 | return fernet.Fernet(octobot_commons.OCTOBOT_KEY).encrypt(data.encode()) 44 | except Exception as global_exception: 45 | logging.getLogger().error(f"Failed to encrypt : {data}") 46 | raise global_exception 47 | 48 | 49 | def decrypt(data, silent_on_invalid_token=False): 50 | """ 51 | Basic decryption method 52 | :param data: the data to decrypt 53 | :param silent_on_invalid_token: if an error should be raised if a token is invalid 54 | :return: the decrypted data 55 | """ 56 | try: 57 | return ( 58 | fernet.Fernet(octobot_commons.OCTOBOT_KEY).decrypt(data.encode()).decode() 59 | ) 60 | except fernet.InvalidToken as invalid_token_error: 61 | if not silent_on_invalid_token: 62 | logging.getLogger().error( 63 | f"Failed to decrypt : {data} ({invalid_token_error})" 64 | ) 65 | raise invalid_token_error 66 | except Exception as global_exception: 67 | logging.getLogger().error(f"Failed to decrypt : {data} ({global_exception})") 68 | raise global_exception 69 | 70 | 71 | def decrypt_element_if_possible(value_key, config_element, default="") -> str: 72 | """ 73 | Return decrypted values, handles placeholder values 74 | :param value_key: the value key 75 | :param config_element: the config element 76 | :param default: the default value if no decrypt possible 77 | :return: True if the value can be decrypted 78 | """ 79 | element = config_element.get(value_key, "") 80 | if element and not has_invalid_default_config_value(element): 81 | return decrypt(element) 82 | return default 83 | 84 | 85 | def get_password_hash(password): 86 | """ 87 | Returns the password's hex digest 88 | :param password: the password to hash 89 | :return: the hash digest 90 | """ 91 | return hashlib.sha256(password.encode()).hexdigest() 92 | -------------------------------------------------------------------------------- /octobot_commons/configuration/historical_configuration.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.constants as constants 17 | 18 | 19 | def add_historical_tentacle_config( 20 | master_config: dict, config_start_time: float, historical_config: dict 21 | ): 22 | """ 23 | Adds the given historical_config to the master_config historical configurations 24 | """ 25 | if constants.CONFIG_HISTORICAL_CONFIGURATION not in master_config: 26 | master_config[constants.CONFIG_HISTORICAL_CONFIGURATION] = [] 27 | # use list to ensure it still can be serialized 28 | master_config[constants.CONFIG_HISTORICAL_CONFIGURATION].append( 29 | [config_start_time, historical_config] 30 | ) 31 | # always keep the most recent first to be able to find the most up to date first when iterating 32 | master_config[constants.CONFIG_HISTORICAL_CONFIGURATION].sort( 33 | key=lambda x: x[0], reverse=True 34 | ) 35 | 36 | 37 | def get_historical_tentacle_config(master_config: dict, current_time: float) -> dict: 38 | """ 39 | :return: the historical configuration associated to the given time 40 | """ 41 | try: 42 | for config_start_time_and_config in master_config[ 43 | constants.CONFIG_HISTORICAL_CONFIGURATION 44 | ]: 45 | if config_start_time_and_config[0] <= current_time: 46 | return config_start_time_and_config[1] 47 | # no suitable config found: fallback to the oldest config 48 | return master_config[constants.CONFIG_HISTORICAL_CONFIGURATION][-1][1] 49 | except KeyError: 50 | raise KeyError( 51 | f"{constants.CONFIG_HISTORICAL_CONFIGURATION} not found in master_config." 52 | ) 53 | 54 | 55 | def get_oldest_historical_tentacle_config_time(master_config: dict) -> float: 56 | """ 57 | :return: the oldest historical configuration timestamp 58 | """ 59 | try: 60 | return min( 61 | historical_config[0] 62 | for historical_config in master_config.get( 63 | constants.CONFIG_HISTORICAL_CONFIGURATION, [] 64 | ) 65 | ) 66 | except ValueError: 67 | raise ValueError("No historical configuration found") 68 | -------------------------------------------------------------------------------- /octobot_commons/data_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | import numpy as np 18 | 19 | 20 | def normalize_data(data): 21 | """ 22 | Normalize the specified data 23 | :param data: the data to normalize 24 | :return: normalized data 25 | """ 26 | if data.size > 1: 27 | return (data - np.mean(data)) / (data.max() - data.min()) 28 | return data 29 | 30 | 31 | def drop_nan(data): 32 | """ 33 | Drop nan of a numpy array 34 | :param data: the numpy array 35 | :return: the numpy array without nan value 36 | """ 37 | return data[~np.isnan(data)] 38 | 39 | 40 | def mean(number_list): 41 | """ 42 | Return the list average 43 | :param number_list: the list to use 44 | :return: the list average 45 | """ 46 | return sum(number_list) / len(number_list) if number_list else 0 47 | 48 | 49 | def shift_value_array(array, shift_count=-1, fill_value=np.nan, dtype=np.float64): 50 | """ 51 | Shift a numpy array 52 | :param array: the numpy array 53 | :param shift_count: the shift direction (- / +) and counter 54 | :param fill_value: the new value of the shifted indexes 55 | :param dtype: the type of the numpy array (and also the :fill_value:) 56 | :return: the shifted array 57 | """ 58 | new_array = np.empty_like(array, dtype=dtype) 59 | if shift_count > 0: 60 | new_array[:shift_count] = fill_value 61 | new_array[shift_count:] = array[:-shift_count] 62 | elif shift_count < 0: 63 | new_array[shift_count:] = fill_value 64 | new_array[:shift_count] = array[-shift_count:] 65 | else: 66 | new_array[:] = array 67 | return new_array 68 | -------------------------------------------------------------------------------- /octobot_commons/databases/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=R0801 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | 18 | 19 | from octobot_commons.databases import global_storage 20 | from octobot_commons.databases import database_caches 21 | from octobot_commons.databases import document_database_adaptors 22 | from octobot_commons.databases import bases 23 | from octobot_commons.databases import implementations 24 | from octobot_commons.databases import relational_databases 25 | 26 | from octobot_commons.databases import cache_manager 27 | from octobot_commons.databases import databases_util 28 | from octobot_commons.databases import cache_client 29 | from octobot_commons.databases import run_databases 30 | 31 | from octobot_commons.databases.global_storage import ( 32 | GlobalSharedMemoryStorage, 33 | ) 34 | 35 | from octobot_commons.databases.database_caches import ( 36 | GenericDatabaseCache, 37 | ChronologicalReadDatabaseCache, 38 | ) 39 | 40 | from octobot_commons.databases.document_database_adaptors import ( 41 | AbstractDocumentDatabaseAdaptor, 42 | TinyDBAdaptor, 43 | ) 44 | 45 | from octobot_commons.databases.bases import ( 46 | DocumentDatabase, 47 | BaseDatabase, 48 | ) 49 | 50 | from octobot_commons.databases.implementations import ( 51 | DBReader, 52 | DBWriter, 53 | DBWriterReader, 54 | MetaDatabase, 55 | CacheDatabase, 56 | CacheTimestampDatabase, 57 | ) 58 | 59 | from octobot_commons.databases.relational_databases import ( 60 | SQLiteDatabase, 61 | new_sqlite_database, 62 | ) 63 | 64 | from octobot_commons.databases.run_databases import ( 65 | RunDatabasesIdentifier, 66 | RunDatabasesProvider, 67 | init_bot_storage, 68 | close_bot_storage, 69 | AbstractRunDatabasesPruner, 70 | FileSystemRunDatabasesPruner, 71 | run_databases_pruner_factory, 72 | ) 73 | 74 | from octobot_commons.databases.cache_manager import ( 75 | CacheManager, 76 | ) 77 | 78 | from octobot_commons.databases.databases_util import ( 79 | CacheWrapper, 80 | ) 81 | 82 | from octobot_commons.databases.cache_client import ( 83 | CacheClient, 84 | ) 85 | 86 | 87 | __all__ = [ 88 | "GlobalSharedMemoryStorage", 89 | "GenericDatabaseCache", 90 | "ChronologicalReadDatabaseCache", 91 | "AbstractDocumentDatabaseAdaptor", 92 | "TinyDBAdaptor", 93 | "DocumentDatabase", 94 | "BaseDatabase", 95 | "MetaDatabase", 96 | "DBReader", 97 | "DBWriter", 98 | "DBWriterReader", 99 | "CacheDatabase", 100 | "CacheTimestampDatabase", 101 | "SQLiteDatabase", 102 | "new_sqlite_database", 103 | "RunDatabasesIdentifier", 104 | "RunDatabasesProvider", 105 | "init_bot_storage", 106 | "close_bot_storage", 107 | "AbstractRunDatabasesPruner", 108 | "FileSystemRunDatabasesPruner", 109 | "run_databases_pruner_factory", 110 | "CacheManager", 111 | "CacheWrapper", 112 | "CacheClient", 113 | ] 114 | -------------------------------------------------------------------------------- /octobot_commons/databases/bases/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | 18 | from octobot_commons.databases.bases import document_database 19 | from octobot_commons.databases.bases import base_database 20 | 21 | from octobot_commons.databases.bases.document_database import ( 22 | DocumentDatabase, 23 | ) 24 | from octobot_commons.databases.bases.base_database import ( 25 | BaseDatabase, 26 | ) 27 | 28 | 29 | __all__ = [ 30 | "DocumentDatabase", 31 | "BaseDatabase", 32 | ] 33 | -------------------------------------------------------------------------------- /octobot_commons/databases/database_caches/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | 18 | from octobot_commons.databases.database_caches import generic_database_cache 19 | from octobot_commons.databases.database_caches import chronological_read_database_cache 20 | 21 | from octobot_commons.databases.database_caches.generic_database_cache import ( 22 | GenericDatabaseCache, 23 | ) 24 | from octobot_commons.databases.database_caches.chronological_read_database_cache import ( 25 | ChronologicalReadDatabaseCache, 26 | ) 27 | 28 | 29 | __all__ = [ 30 | "GenericDatabaseCache", 31 | "ChronologicalReadDatabaseCache", 32 | ] 33 | -------------------------------------------------------------------------------- /octobot_commons/databases/databases_util/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | 18 | from octobot_commons.databases.databases_util import cache_wrapper 19 | from octobot_commons.databases.databases_util.cache_wrapper import ( 20 | CacheWrapper, 21 | ) 22 | -------------------------------------------------------------------------------- /octobot_commons/databases/databases_util/cache_wrapper.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=R0902 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | 18 | 19 | class CacheWrapper: 20 | def __init__( 21 | self, file_path, cache_type, database_adaptor, tentacles_requirements, **kwargs 22 | ): 23 | self.file_path = file_path 24 | self.cache_type = cache_type 25 | self.database_adaptor = database_adaptor 26 | self.db_kwargs = kwargs 27 | self._cache_database = None 28 | self._db_path = None 29 | self.previous_db_metadata = None 30 | self.tentacles_requirements = tentacles_requirements.summary() 31 | 32 | def get_database(self) -> tuple: 33 | """ 34 | Returns the database, creates it if messing 35 | """ 36 | if self._cache_database is None: 37 | self._cache_database = self.cache_type( 38 | self.file_path, database_adaptor=self.database_adaptor, **self.db_kwargs 39 | ) 40 | self._db_path = self._cache_database.get_db_path() 41 | return self._cache_database, True 42 | return self._cache_database, False 43 | 44 | def is_open(self): 45 | """ 46 | :return: True if a database is open 47 | """ 48 | return self._cache_database is not None 49 | 50 | async def close(self): 51 | """ 52 | Closes the current database. Stores its metadata into self.previous_db_metadata 53 | """ 54 | if self.is_open(): 55 | self.previous_db_metadata = self._cache_database.get_non_default_metadata() 56 | await self._cache_database.close() 57 | self._cache_database = None 58 | return True 59 | return False 60 | 61 | async def clear(self): 62 | """ 63 | Clears the database, deleting its data 64 | """ 65 | if self._cache_database is not None: 66 | await self._cache_database.clear() 67 | 68 | def get_path(self): 69 | """ 70 | :return: the database path 71 | """ 72 | return self._db_path 73 | -------------------------------------------------------------------------------- /octobot_commons/databases/document_database_adaptors/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | 18 | from octobot_commons.databases.document_database_adaptors import ( 19 | abstract_document_database_adaptor, 20 | ) 21 | from octobot_commons.databases.document_database_adaptors import tinydb_adaptor 22 | 23 | 24 | from octobot_commons.databases.document_database_adaptors.abstract_document_database_adaptor import ( 25 | AbstractDocumentDatabaseAdaptor, 26 | ) 27 | from octobot_commons.databases.document_database_adaptors.tinydb_adaptor import ( 28 | TinyDBAdaptor, 29 | ) 30 | 31 | 32 | __all__ = [ 33 | "AbstractDocumentDatabaseAdaptor", 34 | "TinyDBAdaptor", 35 | ] 36 | -------------------------------------------------------------------------------- /octobot_commons/databases/global_storage/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | 18 | from octobot_commons.databases.global_storage import global_shared_memory_storage 19 | 20 | from octobot_commons.databases.global_storage.global_shared_memory_storage import ( 21 | GlobalSharedMemoryStorage, 22 | ) 23 | 24 | 25 | __all__ = [ 26 | "GlobalSharedMemoryStorage", 27 | ] 28 | -------------------------------------------------------------------------------- /octobot_commons/databases/global_storage/global_shared_memory_storage.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import sys 17 | 18 | import octobot_commons.singleton as singleton 19 | 20 | 21 | class GlobalSharedMemoryStorage(dict, singleton.Singleton): 22 | """ 23 | A global singleton dict available to the whole python virtual machine. 24 | Warnings: 25 | only stored in RAM, not persisted on disc 26 | not thread safe 27 | """ 28 | 29 | def remove_oldest_elements(self, elements_count_to_remove: int): 30 | """ 31 | Remove (pop) the elements_count_to_remove oldest elements 32 | :param elements_count_to_remove: number of elements to remove 33 | """ 34 | for key in list(self.keys())[:elements_count_to_remove]: 35 | self.pop(key) 36 | 37 | def get_bytes_size(self): 38 | """ 39 | Return the size in bytes of the memory storage 40 | """ 41 | return sys.getsizeof(self) 42 | -------------------------------------------------------------------------------- /octobot_commons/databases/implementations/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | 18 | from octobot_commons.databases.implementations import db_reader 19 | from octobot_commons.databases.implementations import db_writer 20 | from octobot_commons.databases.implementations import db_writer_reader 21 | from octobot_commons.databases.implementations import meta_database 22 | from octobot_commons.databases.implementations import cache_database 23 | from octobot_commons.databases.implementations import cache_timestamp_database 24 | 25 | 26 | from octobot_commons.databases.implementations.db_reader import ( 27 | DBReader, 28 | ) 29 | from octobot_commons.databases.implementations.db_writer import ( 30 | DBWriter, 31 | ) 32 | from octobot_commons.databases.implementations.db_writer_reader import ( 33 | DBWriterReader, 34 | ) 35 | from octobot_commons.databases.implementations.meta_database import ( 36 | MetaDatabase, 37 | ) 38 | from octobot_commons.databases.implementations.cache_database import ( 39 | CacheDatabase, 40 | ) 41 | from octobot_commons.databases.implementations.cache_timestamp_database import ( 42 | CacheTimestampDatabase, 43 | ) 44 | 45 | 46 | __all__ = [ 47 | "DBReader", 48 | "DBWriter", 49 | "DBWriterReader", 50 | "MetaDatabase", 51 | "CacheDatabase", 52 | "CacheTimestampDatabase", 53 | ] 54 | -------------------------------------------------------------------------------- /octobot_commons/databases/implementations/cache_database.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=C0116,R0801 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | import copy 18 | import sortedcontainers 19 | 20 | import octobot_commons.enums as enums 21 | import octobot_commons.databases.implementations.db_writer as writer 22 | import octobot_commons.databases.document_database_adaptors as adaptors 23 | 24 | 25 | class CacheDatabase(writer.DBWriter): 26 | CACHE_TABLE = enums.CacheDatabaseTables.CACHE.value 27 | CACHE_METADATA_TABLE = enums.CacheDatabaseTables.METADATA.value 28 | UUID_KEY = "uuid" 29 | 30 | def __init__( 31 | self, 32 | file_path: str, 33 | database_adaptor=adaptors.TinyDBAdaptor, 34 | cache_size=None, 35 | **kwargs 36 | ): 37 | super().__init__( 38 | file_path, 39 | database_adaptor=database_adaptor, 40 | cache_size=cache_size, 41 | **kwargs 42 | ) 43 | self._are_metadata_written = False 44 | self._local_cache = None 45 | self.metadata = { 46 | enums.CacheDatabaseColumns.TYPE.value: self.__class__.__name__, 47 | } 48 | 49 | def get_non_default_metadata(self): 50 | metadata = copy.copy(self.metadata) 51 | metadata.pop(enums.CacheDatabaseColumns.TYPE.value) 52 | return metadata 53 | 54 | def add_metadata(self, additional_metadata: dict): 55 | self.metadata.update(additional_metadata) 56 | 57 | async def _ensure_metadata(self): 58 | if not self._are_metadata_written: 59 | await self._database.upsert( 60 | self.CACHE_METADATA_TABLE, self.metadata, None, uuid=1 61 | ) 62 | self._are_metadata_written = True 63 | 64 | async def _ensure_local_cache(self, identifier_key, update=False): 65 | if update or self._local_cache is None: 66 | self._local_cache = sortedcontainers.SortedDict() 67 | for cache in await self.get_cache(): 68 | cache[self.UUID_KEY] = self._database.get_uuid(cache) 69 | self._local_cache[cache[identifier_key]] = cache 70 | 71 | async def get_metadata(self): 72 | return await self._database.select(self.CACHE_METADATA_TABLE, None, uuid=1) 73 | 74 | async def get_cache(self): 75 | return await self._database.select(self.CACHE_TABLE, None) 76 | 77 | async def clear(self): 78 | await self._database.delete(self.CACHE_TABLE, None) 79 | await self._database.delete(self.CACHE_METADATA_TABLE, None) 80 | await super().clear() 81 | self._local_cache = sortedcontainers.SortedDict() 82 | self._are_metadata_written = False 83 | # always rewrite metadata as they are necessary to handle cache later 84 | await self._ensure_metadata() 85 | await self.flush() 86 | 87 | async def _get_from_local_cache(self, identifier_key, identifier_value, sub_key): 88 | await self._ensure_local_cache(identifier_key) 89 | return self._local_cache[identifier_value][sub_key] 90 | 91 | async def _needs_update( 92 | self, identifier_key, identifier_value, sub_key, value 93 | ) -> bool: 94 | try: 95 | return ( 96 | await self._get_from_local_cache( 97 | identifier_key, identifier_value, sub_key 98 | ) 99 | != value 100 | ) 101 | except KeyError: 102 | return True 103 | -------------------------------------------------------------------------------- /octobot_commons/databases/implementations/db_reader.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.databases.bases.base_database as base_database 17 | 18 | 19 | class DBReader(base_database.BaseDatabase): 20 | async def select(self, table_name: str, query: str) -> list: 21 | """ 22 | :param table_name: table to select data from 23 | :param query: select query 24 | :return: list of selected results 25 | """ 26 | return await self._database.select(table_name, query) 27 | 28 | async def tables(self) -> list: 29 | """ 30 | :return: list of tables contained in the database 31 | """ 32 | return await self._database.tables() 33 | 34 | async def all(self, table_name: str) -> list: 35 | """ 36 | :param table_name: table to select data from 37 | :return: all data of the selected table 38 | """ 39 | return await self._database.select(table_name, None) 40 | -------------------------------------------------------------------------------- /octobot_commons/databases/implementations/db_writer_reader.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.databases.implementations.db_writer as writer 17 | import octobot_commons.databases.implementations.db_reader as reader 18 | 19 | 20 | class DBWriterReader(writer.DBWriter, reader.DBReader): 21 | pass 22 | -------------------------------------------------------------------------------- /octobot_commons/databases/relational_databases/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=R0801 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | 18 | 19 | from octobot_commons.databases.relational_databases import sqlite 20 | from octobot_commons.databases.relational_databases.sqlite import ( 21 | SQLiteDatabase, 22 | new_sqlite_database, 23 | ) 24 | 25 | 26 | __all__ = [ 27 | "SQLiteDatabase", 28 | "new_sqlite_database", 29 | ] 30 | -------------------------------------------------------------------------------- /octobot_commons/databases/relational_databases/sqlite/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | 18 | from octobot_commons.databases.relational_databases.sqlite import sqlite_database 19 | from octobot_commons.databases.relational_databases.sqlite.sqlite_database import ( 20 | SQLiteDatabase, 21 | new_sqlite_database, 22 | ) 23 | 24 | 25 | __all__ = [ 26 | "SQLiteDatabase", 27 | "new_sqlite_database", 28 | ] 29 | -------------------------------------------------------------------------------- /octobot_commons/databases/relational_databases/sqlite/cursor_pool.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Backtesting 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import asyncio 17 | import contextlib 18 | 19 | import octobot_commons.databases.relational_databases.sqlite.cursor_wrapper as cursor_wrapper 20 | 21 | 22 | class CursorPool: 23 | def __init__(self, db_connection): 24 | self._db_connection = db_connection 25 | self._cursors = [] 26 | 27 | @contextlib.asynccontextmanager 28 | async def idle_cursor(self) -> cursor_wrapper.CursorWrapper: 29 | """ 30 | Yields an idle cursor, creates a new one if necessary 31 | """ 32 | cursor = None 33 | try: 34 | cursor = await self._get_or_create_idle_cursor() 35 | cursor.idle = False 36 | yield cursor 37 | finally: 38 | if cursor is not None: 39 | cursor.idle = True 40 | 41 | async def close(self): 42 | """ 43 | Close every cursor 44 | """ 45 | await asyncio.gather(*(cursor.close() for cursor in self._cursors)) 46 | 47 | async def _get_or_create_idle_cursor(self) -> cursor_wrapper.CursorWrapper: 48 | for cursor in self._cursors: 49 | if cursor.idle: 50 | return cursor 51 | cursor = cursor_wrapper.CursorWrapper(await self._db_connection.cursor()) 52 | self._cursors.append(cursor) 53 | return cursor 54 | -------------------------------------------------------------------------------- /octobot_commons/databases/relational_databases/sqlite/cursor_wrapper.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Backtesting 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | 18 | class CursorWrapper: 19 | def __init__(self, cursor): 20 | self.cursor = cursor 21 | self.idle = True 22 | 23 | async def close(self): 24 | """ 25 | Close the underlying cursor 26 | """ 27 | await self.cursor.close() 28 | -------------------------------------------------------------------------------- /octobot_commons/databases/run_databases/__init__.py: -------------------------------------------------------------------------------- 1 | # License as published by the Free Software Foundation; either 2 | # version 3.0 of the License, or (at your option) any later version. 3 | # 4 | # This library is distributed in the hope that it will be useful, 5 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 6 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 7 | # Lesser General Public License for more details. 8 | # 9 | # You should have received a copy of the GNU Lesser General Public 10 | # License along with this library. 11 | 12 | 13 | from octobot_commons.databases.run_databases import run_databases_identifier 14 | from octobot_commons.databases.run_databases import run_databases_provider 15 | from octobot_commons.databases.run_databases import storage 16 | from octobot_commons.databases.run_databases import abstract_run_databases_pruner 17 | from octobot_commons.databases.run_databases import file_system_run_databases_pruner 18 | 19 | from octobot_commons.databases.run_databases.run_databases_identifier import ( 20 | RunDatabasesIdentifier, 21 | ) 22 | from octobot_commons.databases.run_databases.run_databases_provider import ( 23 | RunDatabasesProvider, 24 | ) 25 | from octobot_commons.databases.run_databases.storage import ( 26 | init_bot_storage, 27 | close_bot_storage, 28 | ) 29 | from octobot_commons.databases.run_databases.abstract_run_databases_pruner import ( 30 | AbstractRunDatabasesPruner, 31 | ) 32 | from octobot_commons.databases.run_databases.file_system_run_databases_pruner import ( 33 | FileSystemRunDatabasesPruner, 34 | ) 35 | from octobot_commons.databases.run_databases.run_databases_pruning_factory import ( 36 | run_databases_pruner_factory, 37 | ) 38 | 39 | 40 | __all__ = [ 41 | "RunDatabasesIdentifier", 42 | "RunDatabasesProvider", 43 | "init_bot_storage", 44 | "close_bot_storage", 45 | "AbstractRunDatabasesPruner", 46 | "FileSystemRunDatabasesPruner", 47 | "run_databases_pruner_factory", 48 | ] 49 | -------------------------------------------------------------------------------- /octobot_commons/databases/run_databases/file_system_run_databases_pruner.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=W0703 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | import os 18 | import shutil 19 | 20 | import octobot_commons.databases.run_databases.abstract_run_databases_pruner as abstract_run_databases_pruner 21 | 22 | 23 | class FileSystemRunDatabasesPruner( 24 | abstract_run_databases_pruner.AbstractRunDatabasesPruner 25 | ): 26 | async def _explore_databases(self): 27 | self.all_db_data = [ 28 | abstract_run_databases_pruner.DBData( 29 | directory, 30 | [FileSystemDBPartData(f) for f in self._get_all_files(directory)], 31 | ) 32 | for directory in self._get_file_system_runs(self.databases_root_identifier) 33 | ] 34 | 35 | async def _prune_database(self, db_data): 36 | try: 37 | shutil.rmtree(db_data.identifier) 38 | return True 39 | except Exception as err: 40 | self.logger.exception(err, True, f"Error when deleting run database: {err}") 41 | return False 42 | 43 | async def _get_global_runs_identifiers(self, removed_databases): 44 | return { 45 | os.path.dirname(removed_database.identifier) 46 | for removed_database in removed_databases 47 | } 48 | 49 | def _get_file_system_runs(self, root): 50 | try: 51 | # use os.scandir as it is much faster than os.walk 52 | for entry in os.scandir(root): 53 | if self._is_run_top_level_folder(entry): 54 | yield entry 55 | elif entry.is_dir(): 56 | yield from self._get_file_system_runs(entry) 57 | except FileNotFoundError: 58 | # nothing to explore 59 | pass 60 | 61 | def _get_all_files(self, root): 62 | for entry in os.scandir(root): 63 | if entry.is_file(): 64 | yield entry 65 | elif entry.is_dir(): 66 | yield from self._get_all_files(entry) 67 | 68 | def _is_run_top_level_folder(self, dir_entry): 69 | return os.path.isfile(os.path.join(dir_entry, self._run_db)) and any( 70 | identifier in dir_entry.path 71 | for identifier in self.backtesting_run_path_identifier 72 | ) 73 | 74 | 75 | class FileSystemDBPartData(abstract_run_databases_pruner.AbstractDBPartData): 76 | def __init__(self, identifier): 77 | super().__init__(identifier) 78 | self.size = os.path.getsize(self.identifier) 79 | self.last_modified_time = os.path.getmtime(self.identifier) 80 | -------------------------------------------------------------------------------- /octobot_commons/databases/run_databases/run_databases_pruning_factory.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=W0703 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | import octobot_commons.databases.run_databases.file_system_run_databases_pruner as file_system_run_databases_pruner 18 | 19 | 20 | def run_databases_pruner_factory(run_databases_identifier, max_db_size): 21 | """ 22 | :return: A RunDatabasesPruner instance 23 | """ 24 | if run_databases_identifier.database_adaptor.is_file_system_based(): 25 | return file_system_run_databases_pruner.FileSystemRunDatabasesPruner( 26 | run_databases_identifier, 27 | max_db_size, 28 | ) 29 | raise NotImplementedError("Only file system based database pruner is implemented") 30 | -------------------------------------------------------------------------------- /octobot_commons/databases/run_databases/storage.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import json 17 | 18 | import octobot_commons.databases.run_databases.run_databases_provider as run_databases_provider 19 | import octobot_commons.configuration as configuration 20 | import octobot_commons.enums as enums 21 | import octobot_commons.logging as logging 22 | 23 | 24 | async def init_bot_storage(bot_id, run_database_identifier, clear_user_inputs): 25 | """ 26 | Initializes the associated bot_id databases. Deletes any existing user input if clear_user_inputs is True 27 | """ 28 | if not run_databases_provider.RunDatabasesProvider.instance().has_bot_id(bot_id): 29 | # only one run database per bot id 30 | await run_databases_provider.RunDatabasesProvider.instance().add_bot_id( 31 | bot_id, run_database_identifier 32 | ) 33 | # always ensure database is valid 34 | run_db = run_databases_provider.RunDatabasesProvider.instance().get_run_db( 35 | bot_id 36 | ) 37 | if run_database_identifier.enable_storage: 38 | await _repair_database_if_necessary(run_db) 39 | if clear_user_inputs: 40 | await configuration.clear_user_inputs(run_db) 41 | 42 | 43 | async def close_bot_storage(bot_id): 44 | """ 45 | :return: Close the bot_id associated run databases 46 | """ 47 | if run_databases_provider.RunDatabasesProvider.instance().has_bot_id(bot_id): 48 | await run_databases_provider.RunDatabasesProvider.instance().close(bot_id) 49 | 50 | 51 | async def _repair_database_if_necessary(database): 52 | try: 53 | # will raise if the db has an issue 54 | await database.all(enums.DBTables.METADATA.value) 55 | except json.JSONDecodeError: 56 | logging.get_logger(__name__).warning( 57 | f"Invalid database at {database}, resetting content." 58 | ) 59 | # error in database, reset it 60 | await database.hard_reset() 61 | -------------------------------------------------------------------------------- /octobot_commons/databases/run_databases/utils.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import os 17 | 18 | # pathlib.Path should not be used to keep consistency with str paths 19 | import pathlib 20 | 21 | import octobot_commons.enums as enums 22 | import octobot_commons.logging as logging 23 | import octobot_commons.constants as constants 24 | import octobot_commons.databases.run_databases.run_databases_identifier as run_databases_identifier 25 | 26 | 27 | def get_backtesting_related_run_path_identifiers_str(database_adaptor): 28 | """ 29 | :return: database identifier fragments associated to the given database_adaptor used 30 | in backtesting 31 | """ 32 | separator = ( 33 | os.path.sep if database_adaptor.is_file_system_based else constants.DB_SEPARATOR 34 | ) 35 | return { 36 | f"{separator}{enums.RunDatabases.BACKTESTING.value}{separator}", 37 | f"{separator}{enums.RunDatabases.OPTIMIZER.value}{separator}", 38 | } 39 | 40 | 41 | def get_global_run_database_identifier(runs_identifier): 42 | """ 43 | :return: a RunDatabasesIdentifier associated to the given runs_identifier 44 | """ 45 | # used to split paths into str parts 46 | split_path = pathlib.Path(runs_identifier).parts 47 | try: 48 | if split_path[-2] == enums.RunDatabases.OPTIMIZER.value: 49 | # in optimizer 50 | # ex: [..., 'DipAnalyserTradingMode', 'Dip Analyser strat designer test', 'optimizer', 'optimizer_1'] 51 | optimizer_id = ( 52 | run_databases_identifier.RunDatabasesIdentifier.parse_optimizer_id( 53 | split_path[-1] 54 | ) 55 | ) 56 | campaign_name = split_path[-3] 57 | trading_mode = split_path[-4] 58 | return run_databases_identifier.RunDatabasesIdentifier( 59 | trading_mode, 60 | optimization_campaign_name=campaign_name, 61 | backtesting_id=0, 62 | optimizer_id=optimizer_id, 63 | ) 64 | # in backtesting 65 | # ex: [..., 'DipAnalyserTradingMode', 'Dip Analyser strat designer test', 'backtesting'] 66 | campaign_name = split_path[-2] 67 | trading_mode = split_path[-3] 68 | return run_databases_identifier.RunDatabasesIdentifier( 69 | trading_mode, 70 | optimization_campaign_name=campaign_name, 71 | backtesting_id=0, 72 | ) 73 | except IndexError as err: 74 | logging.get_logger("run_databases_utils").exception( 75 | err, True, f"Unhandled backtesting data path format: {runs_identifier}" 76 | ) 77 | return None 78 | -------------------------------------------------------------------------------- /octobot_commons/dataclasses/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | from octobot_commons.dataclasses import flexible_dataclass 18 | from octobot_commons.dataclasses.flexible_dataclass import ( 19 | FlexibleDataclass, 20 | ) 21 | 22 | from octobot_commons.dataclasses import minimizable_dataclass 23 | from octobot_commons.dataclasses.minimizable_dataclass import ( 24 | MinimizableDataclass, 25 | ) 26 | 27 | from octobot_commons.dataclasses import updatable_dataclass 28 | from octobot_commons.dataclasses.updatable_dataclass import ( 29 | UpdatableDataclass, 30 | ) 31 | 32 | 33 | __all__ = ["FlexibleDataclass", "MinimizableDataclass", "UpdatableDataclass"] 34 | -------------------------------------------------------------------------------- /octobot_commons/dataclasses/flexible_dataclass.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import dataclasses 17 | import typing 18 | 19 | 20 | @dataclasses.dataclass 21 | class FlexibleDataclass: 22 | _class_field_cache: typing.ClassVar[dict] = {} 23 | """ 24 | Implements from_dict which can be called to instantiate a new instance of this class from a dict. Using from_dict 25 | ignores any additional key from the given dict that is not defined as a dataclass field. 26 | Nested dataclasses to be parsed inside a list or other container should be calling .from_dict in __post_init__ 27 | """ 28 | 29 | @classmethod 30 | def from_dict(cls, dict_value: dict): 31 | """ 32 | Creates a new instance of cls from the given dict, ignoring additional dict values 33 | """ 34 | if isinstance(dict_value, dict): 35 | fields_values = { 36 | k: _get_nested_class(v, cls._class_field_cache[k]) 37 | for k, v in dict_value.items() 38 | if k in cls.get_field_names() 39 | } 40 | return cls(**fields_values) 41 | return dict_value 42 | 43 | @classmethod 44 | def get_field_names(cls): 45 | """ 46 | :return a generator over the given FlexibleDataclass field names 47 | """ 48 | if not cls._class_field_cache: 49 | cls._class_field_cache = { 50 | f.name: f.type for f in dataclasses.fields(cls) if f.init 51 | } 52 | return cls._class_field_cache.keys() 53 | 54 | 55 | def _get_nested_class(value, target_type): 56 | # does not support lists or dicts 57 | if isinstance(target_type, type) and issubclass(target_type, FlexibleDataclass): 58 | return target_type.from_dict(value) 59 | return value 60 | -------------------------------------------------------------------------------- /octobot_commons/dataclasses/minimizable_dataclass.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import dataclasses 17 | import octobot_commons.dataclasses.flexible_dataclass as flexible_dataclass 18 | 19 | 20 | class MinimizableDataclass(flexible_dataclass.FlexibleDataclass): 21 | def to_dict(self, include_default_values=True) -> dict: 22 | """ 23 | Creates a new dict from self. Recursively processes any MinimizableDataclass instance attribute 24 | """ 25 | if include_default_values: 26 | # use default factory 27 | return dataclasses.asdict(self) 28 | factory = _asdict_without_default_factory( 29 | (self.__class__,) 30 | + tuple( 31 | getattr(self, attr.name)[0].__class__ 32 | if isinstance(getattr(self, attr.name), list) 33 | and getattr(self, attr.name) 34 | else getattr(self, attr.name).__class__ 35 | for attr in dataclasses.fields(self) 36 | ) 37 | ) 38 | return dataclasses.asdict(self, dict_factory=factory) 39 | 40 | 41 | def _asdict_without_default_factory(possible_classes): 42 | def factory(obj) -> dict: 43 | formatted_dict = {} 44 | found_class = None 45 | for possible_class in possible_classes: 46 | if possible_class in (int, float, str, list, dict): 47 | continue 48 | if all(key in possible_class.__dataclass_fields__ for key, _ in obj): 49 | found_class = possible_class 50 | if found_class is None: 51 | # class not found, include all values 52 | return dict(obj) 53 | for key, val in obj: 54 | default_field_value = found_class.__dataclass_fields__[key].default 55 | if default_field_value is dataclasses.MISSING and ( 56 | found_class.__dataclass_fields__[key].default_factory 57 | is not dataclasses.MISSING 58 | ): 59 | # try with default factory 60 | default_field_value = found_class.__dataclass_fields__[ 61 | key 62 | ].default_factory() 63 | if default_field_value is dataclasses.MISSING or default_field_value != val: 64 | formatted_dict[key] = val 65 | 66 | return formatted_dict 67 | 68 | return factory 69 | -------------------------------------------------------------------------------- /octobot_commons/display/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | from octobot_commons.display import display_translator 18 | from octobot_commons.display.display_translator import ( 19 | DisplayTranslator, 20 | Element, 21 | ) 22 | 23 | from octobot_commons.display import display_factory 24 | from octobot_commons.display.display_factory import ( 25 | display_translator_factory, 26 | ) 27 | 28 | from octobot_commons.display import plot_settings 29 | from octobot_commons.display.plot_settings import ( 30 | PlotSettings, 31 | ) 32 | 33 | 34 | __all__ = ["DisplayTranslator", "Element", "display_translator_factory", "PlotSettings"] 35 | -------------------------------------------------------------------------------- /octobot_commons/display/display_factory.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.tentacles_management as tentacles_management 17 | import octobot_commons.display.display_translator as display_translator 18 | 19 | 20 | def display_translator_factory(**kwargs): 21 | """ 22 | Returns a new instance of the available display_translator.DisplayTranslator implementation 23 | :param kwargs: kwargs to pass to the construction 24 | :return: the created instance 25 | """ 26 | return tentacles_management.get_single_deepest_child_class( 27 | display_translator.DisplayTranslator 28 | )(**kwargs) 29 | -------------------------------------------------------------------------------- /octobot_commons/display/plot_settings.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=R0913 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | import octobot_commons.enums as enums 18 | 19 | 20 | class PlotSettings: 21 | def __init__( 22 | self, 23 | chart=enums.PlotCharts.MAIN_CHART.value, 24 | x_multiplier=1000, 25 | kind="scattergl", 26 | mode="markers", 27 | y_data=None, 28 | ): 29 | self.chart = chart 30 | self.x_multiplier = x_multiplier 31 | self.kind = kind 32 | self.mode = mode 33 | self.y_data = y_data 34 | -------------------------------------------------------------------------------- /octobot_commons/errors.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | 18 | class ConfigError(Exception): 19 | """ 20 | Config related Exception 21 | """ 22 | 23 | 24 | class RemoteConfigError(ConfigError): 25 | """ 26 | Fetched config related Exception 27 | """ 28 | 29 | 30 | class NoProfileError(Exception): 31 | """ 32 | Profile related Exception: raised when the current profile can't be found and default profile can't be loaded 33 | """ 34 | 35 | 36 | class ProfileConflictError(Exception): 37 | """ 38 | Profile related Exception: raised when the current profile can't be renamed as expected 39 | """ 40 | 41 | 42 | class ProfileRemovalError(Exception): 43 | """ 44 | Profile related Exception: raised when the current profile can't be can't be removed 45 | """ 46 | 47 | 48 | class ProfileImportError(Exception): 49 | """ 50 | Profile related Exception: raised when the imported profile is invalid 51 | """ 52 | 53 | 54 | class ConfigEvaluatorError(Exception): 55 | """ 56 | Evaluator config related Exception 57 | """ 58 | 59 | 60 | class ConfigTradingError(Exception): 61 | """ 62 | Trading config related Exception 63 | """ 64 | 65 | 66 | class TentacleNotFound(Exception): 67 | """ 68 | Tentacle not found related Exception 69 | """ 70 | 71 | 72 | class UninitializedCache(Exception): 73 | """ 74 | Raised when a cache is requested but has not yet been initialized 75 | """ 76 | 77 | 78 | class NoCacheValue(Exception): 79 | """ 80 | Raised when a cache value is selected but is not available in database 81 | """ 82 | 83 | 84 | class UncachableValue(Exception): 85 | """ 86 | Raised when a cache value is selected but is not available in database 87 | """ 88 | 89 | 90 | class DatabaseNotFoundError(Exception): 91 | """ 92 | Raised when a database can't be found 93 | """ 94 | 95 | 96 | class MissingDataError(Exception): 97 | """ 98 | Raised when there is not enough available candles 99 | """ 100 | 101 | 102 | class MissingExchangeDataError(Exception): 103 | """ 104 | Raised when there is no available data for this exchange 105 | """ 106 | 107 | 108 | class ExecutionAborted(Exception): 109 | """ 110 | Raised when the current execution should be aborted 111 | """ 112 | 113 | 114 | class LogicalOperatorError(Exception): 115 | """ 116 | Raised when a logical operation is invalid 117 | """ 118 | 119 | 120 | class UnsupportedError(Exception): 121 | """ 122 | Raised when an unsupported message is received 123 | """ 124 | 125 | 126 | class InvalidUserInputError(Exception): 127 | """ 128 | Raised when a user input in invalid 129 | """ 130 | 131 | 132 | class MissingSignalBuilder(Exception): 133 | """ 134 | Raised when a signal builder is not found 135 | """ 136 | -------------------------------------------------------------------------------- /octobot_commons/evaluators_util.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=R0913 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | 18 | import octobot_commons.constants as constants 19 | 20 | UNSET_EVAL_TYPE = "unset_eval_type_param" 21 | 22 | 23 | def check_valid_eval_note( 24 | eval_note, 25 | eval_type=UNSET_EVAL_TYPE, 26 | expected_eval_type=None, 27 | eval_time=None, 28 | expiry_delay=None, 29 | current_time=None, 30 | ): 31 | """ 32 | Will also test evaluation type if if eval_type is provided. 33 | :param eval_note: The evaluation value 34 | :param eval_type: The evaluation type 35 | :param expected_eval_type: The expected type. Default is EVALUATOR_EVAL_DEFAULT_TYPE 36 | :param eval_time: The evaluation time 37 | :param expiry_delay: The allowed evaluation delay 38 | :param current_time: The current time 39 | :return: True when evaluation value is valid 40 | """ 41 | if eval_type != UNSET_EVAL_TYPE and ( 42 | eval_type != expected_eval_type or expected_eval_type is None 43 | ): 44 | return False 45 | return ( 46 | eval_note is not None 47 | and eval_note is not constants.START_PENDING_EVAL_NOTE 48 | and (eval_time is None or eval_time + expiry_delay - current_time > 0) 49 | ) 50 | -------------------------------------------------------------------------------- /octobot_commons/external_resources_manager.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=W0703,W3101 2 | # Drakkar-Software OctoBot 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | import json 18 | import requests 19 | 20 | import octobot_commons.logging as logging_util 21 | import octobot_commons.constants as constants 22 | 23 | 24 | def _handle_exception(exception, resource_key, catch_exception, default_response): 25 | """ 26 | Handle exception when fetching external resources 27 | :param exception: the exception 28 | :param resource_key: the resource key 29 | :param catch_exception: if exception should be caught 30 | :param default_response: the default response 31 | :return: the default response if an exception has been caught 32 | """ 33 | if catch_exception: 34 | logging_util.get_logger("ExternalResourcesManager").warning( 35 | f"Exception when calling get_external_resource for {resource_key} key: {exception}" 36 | ) 37 | return default_response 38 | raise exception 39 | 40 | 41 | def get_external_resource( 42 | resource_key, catch_exception=False, default_response="" 43 | ) -> object: 44 | """ 45 | Get an external resource 46 | :param resource_key: the resource key 47 | :param catch_exception: if exception should be caught 48 | :param default_response: the default response 49 | :return: the external resource key value 50 | """ 51 | try: 52 | external_resources = json.loads( 53 | requests.get(constants.EXTERNAL_RESOURCE_URL).text 54 | ) 55 | return external_resources[resource_key] 56 | except Exception as global_exception: 57 | return _handle_exception( 58 | global_exception, resource_key, catch_exception, default_response 59 | ) 60 | 61 | 62 | async def async_get_external_resource( 63 | resource_key, aiohttp_session, catch_exception=False, default_response="" 64 | ) -> object: 65 | """ 66 | Get an external resource in async way 67 | :param resource_key: the resource key 68 | :param aiohttp_session: the aiohttp session 69 | :param catch_exception: if exception should be caught 70 | :param default_response: the default reponse 71 | :return: the external resource key value 72 | """ 73 | try: 74 | async with aiohttp_session.get(constants.EXTERNAL_RESOURCE_URL) as resp: 75 | external_resources = json.loads(resp.text()) 76 | return external_resources[resource_key] 77 | except Exception as global_exception: 78 | return _handle_exception( 79 | global_exception, resource_key, catch_exception, default_response 80 | ) 81 | -------------------------------------------------------------------------------- /octobot_commons/list_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import functools 17 | 18 | 19 | def flatten_list(list_to_flatten): 20 | """ 21 | Flatten the list :list_to_flatten: 22 | :param list_to_flatten: the list to flatten 23 | :return: the flattened list 24 | """ 25 | return functools.reduce( 26 | lambda first_level, second_level: first_level + second_level, list_to_flatten 27 | ) 28 | 29 | 30 | def deduplicate(elements: list) -> list: 31 | """ 32 | remove duplicated values from a list while preserving order 33 | """ 34 | # from https://stackoverflow.com/questions/480214/how-do-i-remove-duplicates-from-a-list-while-preserving-order 35 | seen = set() 36 | seen_add = seen.add 37 | return [x for x in elements if not (x in seen or seen_add(x))] 38 | -------------------------------------------------------------------------------- /octobot_commons/logging/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | from octobot_commons.logging import logging_util 18 | from octobot_commons.logging.logging_util import ( 19 | BotLogger, 20 | set_global_logger_level, 21 | get_global_logger_level, 22 | temporary_log_level, 23 | get_logger_level_per_handler, 24 | get_logger, 25 | set_logging_level, 26 | get_backtesting_errors_count, 27 | reset_backtesting_errors, 28 | set_error_publication_enabled, 29 | BACKTESTING_NEW_ERRORS_COUNT, 30 | LOG_DATABASE, 31 | LOG_NEW_ERRORS_COUNT, 32 | logs_database, 33 | error_notifier_callbacks, 34 | LOGS_MAX_COUNT, 35 | add_log, 36 | get_errors_count, 37 | reset_errors_count, 38 | register_error_notifier, 39 | register_log_callback, 40 | set_enable_web_interface_logs, 41 | ) 42 | 43 | __all__ = [ 44 | "BotLogger", 45 | "set_global_logger_level", 46 | "get_global_logger_level", 47 | "temporary_log_level", 48 | "get_logger_level_per_handler", 49 | "get_logger", 50 | "set_logging_level", 51 | "get_backtesting_errors_count", 52 | "reset_backtesting_errors", 53 | "set_error_publication_enabled", 54 | "BACKTESTING_NEW_ERRORS_COUNT", 55 | "LOG_DATABASE", 56 | "LOG_NEW_ERRORS_COUNT", 57 | "logs_database", 58 | "error_notifier_callbacks", 59 | "LOGS_MAX_COUNT", 60 | "add_log", 61 | "get_errors_count", 62 | "reset_errors_count", 63 | "register_error_notifier", 64 | "register_log_callback", 65 | "set_enable_web_interface_logs", 66 | ] 67 | -------------------------------------------------------------------------------- /octobot_commons/logical_operators.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.enums as enums 17 | import octobot_commons.errors as errors 18 | 19 | 20 | def evaluate_condition(left_operand, right_operand, operator: str) -> bool: 21 | """ 22 | evaluates the given condition 23 | :param left_operand: the left operand of the condition 24 | :param right_operand: the right operand of the condition 25 | :param operator: the operator of the condition 26 | :return: True if the evaluated condition is True, False otherwise 27 | """ 28 | if operator == enums.LogicalOperators.LOWER_THAN.value: 29 | return left_operand < right_operand 30 | if operator == enums.LogicalOperators.HIGHER_THAN.value: 31 | return left_operand > right_operand 32 | if operator == enums.LogicalOperators.LOWER_OR_EQUAL_TO.value: 33 | return left_operand <= right_operand 34 | if operator == enums.LogicalOperators.HIGHER_OR_EQUAL_TO.value: 35 | return left_operand >= right_operand 36 | if operator == enums.LogicalOperators.EQUAL_TO.value: 37 | return left_operand == right_operand 38 | if operator == enums.LogicalOperators.DIFFERENT_FROM.value: 39 | return left_operand != right_operand 40 | raise errors.LogicalOperatorError(f"Unknown operator: {operator}") 41 | -------------------------------------------------------------------------------- /octobot_commons/multiprocessing_util.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=C0103 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | import multiprocessing 18 | import contextlib 19 | 20 | 21 | _LOCKS = {} 22 | _ELEMENTS = {} 23 | 24 | 25 | def register_lock_and_shared_elements( 26 | name: str, lock: multiprocessing.RLock, shared_elements: dict 27 | ): 28 | """ 29 | Add elements to the globally available elements 30 | """ 31 | _LOCKS[name] = lock 32 | _ELEMENTS.update(shared_elements) 33 | 34 | 35 | def unregister_lock_and_shared_elements( 36 | name: str, shared_elements=None 37 | ) -> multiprocessing.RLock: 38 | """ 39 | Remove elements to the globally available elements 40 | """ 41 | if shared_elements is None: 42 | _ELEMENTS.clear() 43 | else: 44 | for key in shared_elements: 45 | _ELEMENTS.pop(key) 46 | return _LOCKS.pop(name) 47 | 48 | 49 | @contextlib.contextmanager 50 | def registered_lock_and_shared_elements( 51 | name: str, lock: multiprocessing.RLock, shared_elements: dict 52 | ): 53 | """ 54 | Add and remove elements to the globally available elements 55 | """ 56 | try: 57 | register_lock_and_shared_elements(name, lock, shared_elements) 58 | yield lock 59 | finally: 60 | unregister_lock_and_shared_elements(name, shared_elements) 61 | 62 | 63 | def get_lock(name: str) -> multiprocessing.RLock: 64 | """ 65 | Returns a shared lock 66 | """ 67 | return _LOCKS[name] 68 | 69 | 70 | def get_shared_element(shared_elements_name: str) -> multiprocessing.RLock: 71 | """ 72 | Returns a shared element 73 | """ 74 | return _ELEMENTS[shared_elements_name] 75 | -------------------------------------------------------------------------------- /octobot_commons/number_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import decimal 17 | import math 18 | import typing 19 | 20 | 21 | def round_into_str_with_max_digits(number: float, digits_count: int) -> str: 22 | """ 23 | Round the number with digits_count 24 | :param number: the number to round 25 | :param digits_count: the digit count 26 | :return: the rounded number 27 | """ 28 | return "{:.{}f}".format(round(number, digits_count), digits_count) 29 | 30 | 31 | def round_into_float_with_max_digits(number: float, digits_count: int) -> float: 32 | """ 33 | Round the float number with digits_count 34 | :param number: the number to round 35 | :param digits_count: the digit count 36 | :return: the rounded number 37 | """ 38 | return float( 39 | round_into_str_with_max_digits(number=number, digits_count=digits_count) 40 | ) 41 | 42 | 43 | def get_digits_count(value: typing.Union[float, decimal.Decimal]): 44 | """ 45 | :return: the number of digits in the given number 46 | """ 47 | return round(abs(math.log(value, 10))) 48 | -------------------------------------------------------------------------------- /octobot_commons/optimization_campaign.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=C0103,W0603 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | import octobot_commons.constants as constants 18 | 19 | 20 | class OptimizationCampaign: 21 | def __init__(self, name=None): 22 | self.name = name or self.get_campaign_name() 23 | 24 | @classmethod 25 | def get_campaign_name(cls, *args): 26 | """ 27 | Returns the name of the current optimization campaign 28 | :param args: arguments passed to the optimization_campaign_name_proxy 29 | """ 30 | return _optimization_name_proxy(*args) 31 | 32 | 33 | def _default_optimization_name_proxy(*_): 34 | return constants.DEFAULT_CAMPAIGN 35 | 36 | 37 | _name_proxy = _default_optimization_name_proxy 38 | 39 | 40 | def _optimization_name_proxy(*args): 41 | return _name_proxy(*args) 42 | 43 | 44 | def register_optimization_campaign_name_proxy(new_proxy): 45 | """ 46 | Registers a new campaign name provider as a proxy function 47 | :param new_proxy: the proxy function to be called by OptimizationCampaign.get_campaign_name 48 | """ 49 | global _name_proxy 50 | _name_proxy = new_proxy 51 | -------------------------------------------------------------------------------- /octobot_commons/os_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | 18 | import sys 19 | import os 20 | import platform 21 | import ctypes 22 | import psutil 23 | 24 | import octobot_commons.constants as constants 25 | import octobot_commons.enums as enums 26 | 27 | 28 | def get_current_platform(): 29 | """ 30 | Return the current platform details 31 | Return examples 32 | For Windows : 33 | >>> 'Windows:10:AMD64' 34 | For Linux : 35 | >>> 'Linux:4.15.0-46-generic:x86_64' 36 | For Raspberry : 37 | >>> 'Linux:4.14.98-v7+:armv7l' 38 | :return: the current platform details 39 | """ 40 | return ( 41 | f"{platform.system()}{constants.PLATFORM_DATA_SEPARATOR}{platform.release()}{constants.PLATFORM_DATA_SEPARATOR}" 42 | f"{platform.machine()}" 43 | ) 44 | 45 | 46 | def get_octobot_type(): 47 | """ 48 | Return OctoBot running type from OctoBotTypes 49 | :return: the OctoBot running type 50 | """ 51 | try: 52 | execution_arg = sys.argv[0] 53 | # sys.argv[0] is always the name of the python script called when using a command "python xyz.py" 54 | if execution_arg.endswith(".py"): 55 | if _is_on_docker(): 56 | return enums.OctoBotTypes.DOCKER.value 57 | return enums.OctoBotTypes.PYTHON.value 58 | # sys.argv[0] is the name of the binary when using a binary version: ends with nothing or .exe" 59 | return enums.OctoBotTypes.BINARY.value 60 | except IndexError: 61 | return enums.OctoBotTypes.BINARY.value 62 | 63 | 64 | def get_os(): 65 | """ 66 | Return the OS name 67 | :return: the OS name 68 | """ 69 | return enums.PlatformsName(os.name) 70 | 71 | 72 | def has_admin_rights() -> bool: 73 | """ 74 | :return: True if the current thread has admin rights 75 | """ 76 | try: 77 | return os.getuid() == 0 78 | except AttributeError: 79 | return ctypes.windll.shell32.IsUserAnAdmin() 80 | 81 | 82 | def is_machine_64bit() -> bool: 83 | """ 84 | Win: AMD64 85 | Debian-64: x86_64 86 | From https://stackoverflow.com/questions/2208828/detect-64bit-os-windows-in-python 87 | :return: True if the machine is 64bit 88 | """ 89 | return platform.machine().endswith("64") 90 | 91 | 92 | def is_arm_machine() -> bool: 93 | """ 94 | Can be armv7l or aarch64 (raspberry, Android smartphone...) 95 | From https://raspberrypi.stackexchange.com/questions/5100/detect-that-a-python-program-is-running-on-the-pi 96 | :return: True if the machine is 64bit 97 | """ 98 | return platform.machine() in ["armv7l", "aarch64"] 99 | 100 | 101 | def _is_on_docker(): 102 | """ 103 | Check if the current platform is docker 104 | :return: True if OctoBot is running with docker 105 | """ 106 | file_to_check = "/proc/self/cgroup" 107 | try: 108 | return os.path.exists("/.dockerenv") or ( 109 | os.path.isfile(file_to_check) 110 | and any("docker" in line for line in open(file_to_check)) 111 | ) 112 | except FileNotFoundError: 113 | return False 114 | 115 | 116 | def parse_boolean_environment_var(env_key: str, default_value: str) -> bool: 117 | """ 118 | :param env_key: the environment var key 119 | :param default_value: the default value 120 | :return: True when the var value is "True" or "true" else false 121 | """ 122 | return bool(os.getenv(env_key, default_value).lower() == "true") 123 | 124 | 125 | def get_cpu_and_ram_usage(cpu_watching_seconds): 126 | """ 127 | WARNING: blocking the current thread for the given cpu_watching_seconds seconds 128 | :return: the CPU usage percent, RAM usaage %, total RAM used and total RAM used by this process 129 | """ 130 | mem_ret = psutil.virtual_memory() 131 | process_used_ram = psutil.Process(os.getpid()).memory_info().rss 132 | return ( 133 | psutil.cpu_percent(cpu_watching_seconds), 134 | mem_ret[2], 135 | mem_ret[3] / constants.BYTES_BY_GB, 136 | process_used_ram / constants.BYTES_BY_GB, 137 | ) 138 | -------------------------------------------------------------------------------- /octobot_commons/profiles/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | 18 | from octobot_commons.profiles import profile 19 | 20 | from octobot_commons.profiles.profile import ( 21 | Profile, 22 | ) 23 | 24 | from octobot_commons.profiles import profile_sharing 25 | from octobot_commons.profiles.profile_sharing import ( 26 | export_profile, 27 | install_profile, 28 | import_profile, 29 | import_profile_data_as_profile, 30 | update_profile, 31 | download_profile, 32 | download_and_install_profile, 33 | ) 34 | 35 | from octobot_commons.profiles import profile_data 36 | 37 | from octobot_commons.profiles.profile_data import ( 38 | ProfileData, 39 | ExchangeData, 40 | MinimalFund, 41 | OptionsData, 42 | ) 43 | 44 | from octobot_commons.profiles import profile_sync 45 | 46 | from octobot_commons.profiles.profile_sync import ( 47 | start_profile_synchronizer, 48 | stop_profile_synchronizer, 49 | ) 50 | 51 | from octobot_commons.profiles import exchange_auth_data 52 | 53 | from octobot_commons.profiles.exchange_auth_data import ( 54 | ExchangeAuthData, 55 | ) 56 | 57 | from octobot_commons.profiles import tentacles_profile_data_translator 58 | 59 | from octobot_commons.profiles.tentacles_profile_data_translator import ( 60 | TentaclesProfileDataTranslator, 61 | ) 62 | 63 | from octobot_commons.profiles import tentacles_profile_data_adapter 64 | 65 | from octobot_commons.profiles.tentacles_profile_data_adapter import ( 66 | TentaclesProfileDataAdapter, 67 | ) 68 | 69 | 70 | __all__ = [ 71 | "Profile", 72 | "export_profile", 73 | "install_profile", 74 | "import_profile", 75 | "import_profile_data_as_profile", 76 | "update_profile", 77 | "download_profile", 78 | "download_and_install_profile", 79 | "ProfileData", 80 | "ExchangeData", 81 | "MinimalFund", 82 | "OptionsData", 83 | "start_profile_synchronizer", 84 | "stop_profile_synchronizer", 85 | "TentaclesProfileDataTranslator", 86 | "TentaclesProfileDataAdapter", 87 | "ExchangeAuthData", 88 | ] 89 | -------------------------------------------------------------------------------- /octobot_commons/profiles/exchange_auth_data.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=C0103,R0902 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | import dataclasses 18 | 19 | import octobot_commons.dataclasses 20 | import octobot_commons.constants 21 | 22 | 23 | @dataclasses.dataclass 24 | class ExchangeAuthData(octobot_commons.dataclasses.FlexibleDataclass): 25 | internal_name: str 26 | exchange_credential_id: str = "" 27 | api_key: str = "" 28 | api_secret: str = "" 29 | api_password: str = "" 30 | exchange_type: str = octobot_commons.constants.DEFAULT_EXCHANGE_TYPE 31 | sandboxed: bool = False 32 | 33 | def apply_to_exchange_config(self, config): 34 | """ 35 | Updates the given Configuration object to use the local authentication data 36 | :param config: Configuration object to update 37 | """ 38 | applied = False 39 | for exchange, exchange_config in config.config[ 40 | octobot_commons.constants.CONFIG_EXCHANGES 41 | ].items(): 42 | if exchange == self.internal_name: 43 | self._apply_config(exchange_config) 44 | applied = True 45 | break 46 | if not applied: 47 | # exchange doesn't already exist: add it 48 | exchange_config = {octobot_commons.constants.CONFIG_ENABLED_OPTION: True} 49 | self._apply_config(exchange_config) 50 | config.config[octobot_commons.constants.CONFIG_EXCHANGES][ 51 | self.internal_name 52 | ] = exchange_config 53 | 54 | def _apply_config(self, exchange_config: dict): 55 | exchange_config[octobot_commons.constants.CONFIG_EXCHANGE_KEY] = self.api_key 56 | exchange_config[ 57 | octobot_commons.constants.CONFIG_EXCHANGE_SECRET 58 | ] = self.api_secret 59 | exchange_config[ 60 | octobot_commons.constants.CONFIG_EXCHANGE_PASSWORD 61 | ] = self.api_password 62 | exchange_config[ 63 | octobot_commons.constants.CONFIG_EXCHANGE_SANDBOXED 64 | ] = self.sandboxed 65 | exchange_config[ 66 | octobot_commons.constants.CONFIG_EXCHANGE_TYPE 67 | ] = self.exchange_type 68 | -------------------------------------------------------------------------------- /octobot_commons/profiles/profile_sync.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | import octobot_commons.constants as commons_constants 18 | import octobot_commons.singleton as singleton 19 | import octobot_commons.logging as logging 20 | import octobot_commons.async_job as async_job 21 | import octobot_commons.authentication as authentication 22 | import octobot_commons.profiles.profile_sharing as profile_sharing 23 | 24 | 25 | class ProfileSynchronizer(singleton.Singleton): 26 | """ 27 | Async job to maintain the profile associated to the given configuration up-to-date 28 | """ 29 | 30 | DEFAULT_SYNC_REFRESH_INTERVAL = ( 31 | commons_constants.PROFILE_REFRESH_HOURS_INTERVAL 32 | * commons_constants.HOURS_TO_SECONDS 33 | ) 34 | 35 | def __init__(self, current_config, on_profile_change): 36 | super().__init__() 37 | self.current_config = current_config 38 | self._on_profile_change = on_profile_change 39 | self.sync_job = None 40 | self.sync_interval = self.DEFAULT_SYNC_REFRESH_INTERVAL 41 | self.logger = logging.get_logger(self.__class__.__name__) 42 | 43 | async def _sync_profile(self): 44 | if not self.current_config.profile.auto_update: 45 | self.logger.debug("Skipping profile update check: auto_update is False") 46 | return 47 | if not self.current_config.profile.slug: 48 | self.logger.error( 49 | "Impossible to check profile updates: profile slug is unset" 50 | ) 51 | return 52 | self.logger.info(f"Synchronizing {self.current_config.profile.name} profile") 53 | if await profile_sharing.update_profile(self.current_config.profile): 54 | self.logger.info(f"{self.current_config.profile.name} profile updated") 55 | await self._on_profile_change(self.current_config.profile.name) 56 | else: 57 | self.logger.info( 58 | f"{self.current_config.profile.name} profile already up-to-date" 59 | ) 60 | 61 | async def _should_sync_profiles(self): 62 | return ( 63 | await authentication.Authenticator.wait_and_check_has_open_source_package() 64 | ) 65 | 66 | async def start(self) -> bool: 67 | """ 68 | Synch the profile if necessary 69 | """ 70 | if not await self._should_sync_profiles(): 71 | self.logger.debug("Profile synch loop disabled") 72 | return False 73 | self.logger.debug("Starting profile synchronizer") 74 | self.sync_job = async_job.AsyncJob( 75 | self._sync_profile, 76 | first_execution_delay=0, 77 | execution_interval_delay=self.sync_interval, 78 | ) 79 | await self.sync_job.run() 80 | return True 81 | 82 | def stop(self): 83 | """ 84 | Stop the synchronization loop 85 | """ 86 | if self.sync_job is not None and not self.sync_job.is_stopped(): 87 | self.logger.debug("Stopping profile synchronizer") 88 | self.sync_job.stop() 89 | 90 | 91 | async def start_profile_synchronizer(current_config, on_profile_change): 92 | """ 93 | Start the clock synchronization loop if possible on this system 94 | :return: True if the loop has been started 95 | """ 96 | return await ProfileSynchronizer.instance(current_config, on_profile_change).start() 97 | 98 | 99 | async def stop_profile_synchronizer(): 100 | """ 101 | Stop the synchronization loop 102 | """ 103 | return ProfileSynchronizer.instance().stop() 104 | -------------------------------------------------------------------------------- /octobot_commons/profiles/tentacles_profile_data_adapter.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import typing 17 | 18 | import octobot_commons.profiles.profile_data as profile_data_import 19 | import octobot_commons.profiles.exchange_auth_data as exchange_auth_data_import 20 | 21 | 22 | class TentaclesProfileDataAdapter: 23 | """ 24 | Used to adapt the content of a ProfileData using the given TentaclesData 25 | """ 26 | 27 | def __init__( 28 | self, 29 | tentacles_data: list[profile_data_import.TentaclesData], 30 | additional_data: dict, 31 | authenticator, 32 | auth_key: typing.Optional[str], 33 | ): 34 | self.tentacles_data: list[profile_data_import.TentaclesData] = tentacles_data 35 | self.additional_data: dict = additional_data 36 | self.authenticator = authenticator 37 | self.auth_key = auth_key 38 | 39 | async def adapt( 40 | self, 41 | profile_data: profile_data_import.ProfileData, 42 | auth_data: list[exchange_auth_data_import.ExchangeAuthData], 43 | ) -> None: 44 | """ 45 | Use self.tentacles_data to adapt the given profile_data 46 | """ 47 | raise NotImplementedError("adapt is not implemented") 48 | 49 | @classmethod 50 | def get_tentacle_name(cls) -> str: 51 | """ 52 | :return: the name of the adapter 53 | """ 54 | raise NotImplementedError("get_tentacle_name is not implemented") 55 | -------------------------------------------------------------------------------- /octobot_commons/profiles/tentacles_profile_data_translator.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import typing 17 | 18 | import octobot_commons.tentacles_management.class_inspector as class_inspector 19 | import octobot_commons.profiles.profile_data as profile_data_import 20 | import octobot_commons.profiles.exchange_auth_data as exchange_auth_data_import 21 | import octobot_commons.profiles.tentacles_profile_data_adapter as tentacles_profile_data_adapter 22 | 23 | 24 | class TentaclesProfileDataTranslator: 25 | """ 26 | Translates a tentacle-specific configuration into a ProfileData 27 | """ 28 | 29 | def __init__( 30 | self, 31 | profile_data: profile_data_import.ProfileData, 32 | auth_data: list[exchange_auth_data_import.ExchangeAuthData], 33 | ): 34 | self.profile_data: profile_data_import.ProfileData = profile_data 35 | self.auth_data: list[exchange_auth_data_import.ExchangeAuthData] = auth_data 36 | 37 | async def translate( 38 | self, 39 | tentacles_data: list[profile_data_import.TentaclesData], 40 | additional_data: dict, 41 | authenticator, 42 | auth_key: typing.Optional[str], 43 | ) -> None: 44 | """ 45 | updates self.profile_data by applying the given tentacles_data and 46 | additional_data configuration 47 | :param tentacles_data: the tentacles data to use 48 | :param additional_data: other data that can be useful in translation 49 | :param authenticator: authenticator to fetch data from if necessary 50 | :param auth_key: auth key to used if necessary 51 | :return: 52 | """ 53 | adapter = self._get_adapter(tentacles_data) 54 | await adapter(tentacles_data, additional_data, authenticator, auth_key).adapt( 55 | self.profile_data, self.auth_data 56 | ) 57 | 58 | @classmethod 59 | def _get_adapter(cls, tentacles_data: list[profile_data_import.TentaclesData]): 60 | """ 61 | :return: the first adapter matching a TentaclesData name 62 | """ 63 | adapters = cls._get_adapters() 64 | for tentacles_data_element in tentacles_data: 65 | if adapter := adapters.get(tentacles_data_element.name): 66 | return adapter 67 | raise KeyError("TentaclesData adapter not found") 68 | 69 | @classmethod 70 | def _get_adapters(cls) -> dict: 71 | return { 72 | adapter.get_tentacle_name(): adapter 73 | for adapter in class_inspector.get_all_classes_from_parent( 74 | tentacles_profile_data_adapter.TentaclesProfileDataAdapter 75 | ) 76 | } 77 | -------------------------------------------------------------------------------- /octobot_commons/signals/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | 18 | 19 | from octobot_commons.signals import signal 20 | 21 | from octobot_commons.signals.signal import ( 22 | Signal, 23 | ) 24 | 25 | from octobot_commons.signals import signal_bundle 26 | 27 | from octobot_commons.signals.signal_bundle import ( 28 | SignalBundle, 29 | ) 30 | 31 | from octobot_commons.signals import signal_bundle_builder 32 | 33 | from octobot_commons.signals.signal_bundle_builder import ( 34 | SignalBundleBuilder, 35 | ) 36 | 37 | from octobot_commons.signals import signal_factory 38 | 39 | from octobot_commons.signals.signal_factory import ( 40 | create_signal_bundle, 41 | create_signal, 42 | ) 43 | 44 | from octobot_commons.signals import signals_emitter 45 | 46 | from octobot_commons.signals.signals_emitter import ( 47 | emit_signal_bundle, 48 | ) 49 | 50 | from octobot_commons.signals import signal_builder_wrapper 51 | 52 | from octobot_commons.signals.signal_builder_wrapper import ( 53 | SignalBuilderWrapper, 54 | ) 55 | 56 | from octobot_commons.signals import signal_publisher 57 | 58 | from octobot_commons.signals.signal_publisher import ( 59 | SignalPublisher, 60 | ) 61 | 62 | 63 | __all__ = [ 64 | "Signal", 65 | "SignalBundle", 66 | "create_signal_bundle", 67 | "create_signal", 68 | "emit_signal_bundle", 69 | "SignalBuilderWrapper", 70 | "SignalPublisher", 71 | ] 72 | -------------------------------------------------------------------------------- /octobot_commons/signals/signal.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=C0116 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | import octobot_commons.enums 18 | 19 | 20 | class Signal: 21 | def __init__(self, topic: str, content: dict, **_): 22 | self.topic: str = topic 23 | self.content: dict = content 24 | 25 | def to_dict(self) -> dict: 26 | return { 27 | octobot_commons.enums.SignalsAttrs.TOPIC.value: self.topic, 28 | octobot_commons.enums.SignalsAttrs.CONTENT.value: self.content, 29 | } 30 | 31 | def __str__(self): 32 | return f"{self.to_dict()}" 33 | -------------------------------------------------------------------------------- /octobot_commons/signals/signal_builder_wrapper.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=C0116 2 | # Drakkar-Software OctoBot-Trading 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | import asyncio 18 | import time 19 | 20 | import octobot_commons.signals.signal_bundle_builder as signal_bundle_builder 21 | 22 | 23 | class SignalBuilderWrapper: 24 | NO_TIMEOUT_VALUE = -1 25 | 26 | def __init__( 27 | self, 28 | identifier: str, 29 | signal_builder_class=signal_bundle_builder.SignalBundleBuilder, 30 | timeout: float = NO_TIMEOUT_VALUE, 31 | builder_args: tuple = None, 32 | ): 33 | self.signal_builder_class = signal_builder_class 34 | self.signal_bundle_builder = ( 35 | signal_builder_class(identifier, *builder_args) 36 | if builder_args 37 | else signal_builder_class(identifier) 38 | ) 39 | self.is_being_emitted = False 40 | self.timeout = timeout 41 | self.timeout_event = asyncio.Event() 42 | self.signal_emit_time = time.time() + timeout 43 | self._users_count = 0 44 | 45 | def register_user(self): 46 | self._users_count += 1 47 | 48 | def unregister_user(self): 49 | self._users_count -= 1 50 | 51 | def has_single_user(self): 52 | return self._users_count == 1 53 | -------------------------------------------------------------------------------- /octobot_commons/signals/signal_bundle.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=C0116 2 | # Drakkar-Software OctoBot-Commons 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | import octobot_commons.enums 18 | 19 | 20 | class SignalBundle: 21 | def __init__(self, identifier: str, signals=None, version=None): 22 | self.identifier: str = identifier 23 | self.signals: list = signals or [] 24 | self.version: str = version or self._get_version() 25 | 26 | def to_dict(self) -> dict: 27 | return { 28 | octobot_commons.enums.SignalBundlesAttrs.IDENTIFIER.value: self.identifier, 29 | octobot_commons.enums.SignalBundlesAttrs.SIGNALS.value: [ 30 | signal.to_dict() for signal in self.signals 31 | ], 32 | octobot_commons.enums.SignalBundlesAttrs.VERSION.value: self.version, 33 | } 34 | 35 | def __str__(self): 36 | return f"{self.to_dict()}" 37 | 38 | # pylint: disable=C0415 39 | def _get_version(self) -> str: 40 | try: 41 | import octobot.constants 42 | 43 | return octobot.constants.COMMUNITY_FEED_CURRENT_MINIMUM_VERSION 44 | except ImportError: 45 | return "1.0.0" 46 | -------------------------------------------------------------------------------- /octobot_commons/signals/signal_bundle_builder.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Trading 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.signals.signal_bundle as signal_bundle 17 | import octobot_commons.signals.signal as signal 18 | import octobot_commons.logging as logging 19 | 20 | 21 | class SignalBundleBuilder: 22 | DEFAULT_SIGNAL_CLASS = signal.Signal 23 | 24 | def __init__(self, identifier: str): 25 | self.signals: list = [] 26 | self.identifier: str = identifier 27 | self.version: str = None 28 | self.signal_class = self.__class__.DEFAULT_SIGNAL_CLASS 29 | self.logger = logging.get_logger(self.__class__.__name__) 30 | self.reset() 31 | 32 | def register_signal(self, topic: str, content: dict, **kwargs): 33 | """ 34 | Store a signal to be packed on build call 35 | """ 36 | self.signals.append(self.create_signal(topic, content, **kwargs)) 37 | 38 | def create_signal(self, topic: str, content: dict, **kwargs): 39 | """ 40 | Create a signal from self.signal_class 41 | """ 42 | return self.signal_class(topic, content, **kwargs) 43 | 44 | def is_empty(self) -> bool: 45 | """ 46 | Return True when no signal are to be built 47 | """ 48 | return not self.signals 49 | 50 | def build(self) -> signal_bundle.SignalBundle: 51 | """ 52 | Create a signal_bundle.SignalBundle from registered signals 53 | """ 54 | return signal_bundle.SignalBundle( 55 | self.identifier, 56 | signals=self.signals, 57 | version=self.version, 58 | ) 59 | 60 | def sort_signals(self): 61 | """ 62 | Implement if necessary 63 | """ 64 | return self 65 | 66 | def reset(self): 67 | """ 68 | Remove all registered signals 69 | """ 70 | self.signals = [] 71 | -------------------------------------------------------------------------------- /octobot_commons/signals/signal_factory.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=C0116 2 | # Drakkar-Software OctoBot-Trading 3 | # Copyright (c) Drakkar-Software, All rights reserved. 4 | # 5 | # This library is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU Lesser General Public 7 | # License as published by the Free Software Foundation; either 8 | # version 3.0 of the License, or (at your option) any later version. 9 | # 10 | # This library is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public 16 | # License along with this library. 17 | import octobot_commons.signals.signal as signal 18 | import octobot_commons.signals.signal_bundle as signal_bundle 19 | import octobot_commons.enums as commons_enums 20 | 21 | 22 | def create_signal_bundle(signal_bundle_dict: dict) -> signal_bundle.SignalBundle: 23 | signal_bundle_value = signal_bundle_dict[ 24 | commons_enums.CommunityFeedAttrs.VALUE.value 25 | ] 26 | return signal_bundle.SignalBundle( 27 | signal_bundle_value.get(commons_enums.SignalBundlesAttrs.IDENTIFIER.value), 28 | signals=[ 29 | create_signal(s) 30 | for s in signal_bundle_value.get( 31 | commons_enums.SignalBundlesAttrs.SIGNALS.value, [] 32 | ) 33 | ], 34 | version=signal_bundle_value.get(commons_enums.SignalBundlesAttrs.VERSION.value), 35 | ) 36 | 37 | 38 | def create_signal(signal_dict: dict) -> signal.Signal: 39 | return signal.Signal( 40 | signal_dict.get(commons_enums.SignalsAttrs.TOPIC.value), 41 | signal_dict.get(commons_enums.SignalsAttrs.CONTENT.value), 42 | ) 43 | -------------------------------------------------------------------------------- /octobot_commons/signals/signals_emitter.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Trading 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.enums as commons_enums 17 | import octobot_commons.authentication as authentication 18 | import octobot_commons.signals.signal_bundle as signal_bundle 19 | 20 | 21 | async def emit_signal_bundle(to_send_signal_bundle: signal_bundle.SignalBundle): 22 | """ 23 | Emits a signal bundle 24 | """ 25 | await authentication.Authenticator.instance().send( 26 | to_send_signal_bundle.to_dict(), 27 | commons_enums.CommunityChannelTypes.SIGNAL, 28 | identifier=to_send_signal_bundle.identifier, 29 | ) 30 | -------------------------------------------------------------------------------- /octobot_commons/singleton/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | from octobot_commons.singleton import singleton_class 18 | 19 | from octobot_commons.singleton.singleton_class import Singleton 20 | 21 | __all__ = [ 22 | "Singleton", 23 | ] 24 | -------------------------------------------------------------------------------- /octobot_commons/singleton/singleton_class.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | 18 | class Singleton: 19 | """ 20 | From https://stackoverflow.com/questions/51245056/singleton-is-not-working-in-cython 21 | """ 22 | 23 | _instances = {} 24 | 25 | @classmethod 26 | def instance(cls, *args, **kwargs): 27 | """ 28 | Create the instance if not already created 29 | Return the class instance 30 | :param args: the constructor arguments 31 | :param kwargs: the constructor optional arguments 32 | :return: the class only instance 33 | """ 34 | if cls not in cls._instances: 35 | cls._instances[cls] = cls(*args, **kwargs) 36 | return cls._instances[cls] 37 | 38 | @classmethod 39 | def get_instance_if_exists(cls): 40 | """ 41 | Return the instance if it exist 42 | Return the class instance if it exist 43 | :return: the class only instance if it exist otherwise None 44 | """ 45 | try: 46 | return cls._instances[cls] 47 | except KeyError: 48 | return None 49 | -------------------------------------------------------------------------------- /octobot_commons/support.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import abc 17 | 18 | 19 | class Support: 20 | """ 21 | Abstract class to be implemented when using supports 22 | """ 23 | 24 | @abc.abstractmethod 25 | def is_supporting(self) -> bool: 26 | """ 27 | Return True when supporting 28 | """ 29 | raise NotImplementedError 30 | -------------------------------------------------------------------------------- /octobot_commons/symbols/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | from octobot_commons.symbols import symbol_util 18 | 19 | from octobot_commons.symbols.symbol_util import ( 20 | parse_symbol, 21 | merge_symbol, 22 | merge_currencies, 23 | convert_symbol, 24 | is_usd_like_coin, 25 | get_most_common_usd_like_symbol, 26 | ) 27 | 28 | from octobot_commons.symbols import symbol 29 | 30 | from octobot_commons.symbols.symbol import ( 31 | Symbol, 32 | ) 33 | 34 | 35 | __all__ = [ 36 | "parse_symbol", 37 | "merge_symbol", 38 | "merge_currencies", 39 | "convert_symbol", 40 | "is_usd_like_coin", 41 | "get_most_common_usd_like_symbol", 42 | "Symbol", 43 | ] 44 | -------------------------------------------------------------------------------- /octobot_commons/tentacles_management/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | from octobot_commons.tentacles_management import abstract_tentacle 18 | from octobot_commons.tentacles_management import class_inspector 19 | 20 | from octobot_commons.tentacles_management.abstract_tentacle import AbstractTentacle 21 | from octobot_commons.tentacles_management.class_inspector import ( 22 | default_parent_inspection, 23 | default_parents_inspection, 24 | evaluator_parent_inspection, 25 | trading_mode_parent_inspection, 26 | get_class_from_parent_subclasses, 27 | get_deep_class_from_parent_subclasses, 28 | get_class_from_string, 29 | is_abstract_using_inspection_and_class_naming, 30 | get_all_classes_from_parent, 31 | get_single_deepest_child_class, 32 | ) 33 | 34 | __all__ = [ 35 | "AbstractTentacle", 36 | "default_parent_inspection", 37 | "default_parents_inspection", 38 | "evaluator_parent_inspection", 39 | "trading_mode_parent_inspection", 40 | "get_class_from_parent_subclasses", 41 | "get_deep_class_from_parent_subclasses", 42 | "get_class_from_string", 43 | "is_abstract_using_inspection_and_class_naming", 44 | "get_all_classes_from_parent", 45 | "get_single_deepest_child_class", 46 | ] 47 | -------------------------------------------------------------------------------- /octobot_commons/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | from octobot_commons.tests import test_config 18 | 19 | from octobot_commons.tests.test_config import ( 20 | get_test_config, 21 | init_config_time_frame_for_tests, 22 | load_test_config, 23 | TEST_CONFIG_FOLDER, 24 | ) 25 | 26 | __all__ = [ 27 | "get_test_config", 28 | "init_config_time_frame_for_tests", 29 | "load_test_config", 30 | "TEST_CONFIG_FOLDER", 31 | ] 32 | -------------------------------------------------------------------------------- /octobot_commons/tests/test_config.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import os 17 | 18 | import octobot_commons.configuration as configuration 19 | import octobot_commons.constants as constants 20 | import octobot_commons.enums as enums 21 | 22 | TEST_FOLDER = "tests" 23 | STATIC_FOLDER = "static" 24 | TEST_CONFIG_FOLDER = f"{TEST_FOLDER}/static" 25 | 26 | 27 | def get_test_config(test_folder=TEST_FOLDER): 28 | """ 29 | Return test default config 30 | :return: test default config 31 | """ 32 | return os.path.join(test_folder, STATIC_FOLDER, constants.CONFIG_FILE) 33 | 34 | 35 | def get_test_profile(test_folder=TEST_FOLDER): 36 | """ 37 | Return test default config 38 | :return: test default config 39 | """ 40 | return test_folder 41 | 42 | 43 | def init_config_time_frame_for_tests(config): 44 | """ 45 | Append time frames to config for tests 46 | :param config: the test config 47 | :return: the test config with time frames 48 | """ 49 | result = [] 50 | for time_frame in config[constants.CONFIG_TIME_FRAME]: 51 | result.append(enums.TimeFrames(time_frame)) 52 | config[constants.CONFIG_TIME_FRAME] = result 53 | 54 | 55 | def load_test_config(dict_only=True, test_folder=TEST_FOLDER): 56 | """ 57 | Return the complete default test configs 58 | :return: the complete default test config 59 | """ 60 | config = configuration.Configuration( 61 | get_test_config(test_folder=test_folder), 62 | get_test_profile(test_folder=test_folder), 63 | ) 64 | config.read() 65 | init_config_time_frame_for_tests(config.config) 66 | return config.config if dict_only else config 67 | -------------------------------------------------------------------------------- /octobot_commons/thread_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import sys 17 | import concurrent.futures as futures 18 | 19 | 20 | # pylint: disable=W0212 21 | def stop_thread_pool_executor_non_gracefully(executor: futures.ThreadPoolExecutor): 22 | """ 23 | Should only be used in python 3.8 24 | From https://gist.github.com/clchiou/f2608cbe54403edb0b13 25 | Non graceful and non clean but only way to shutdown a ThreadPoolExecutor 26 | :param executor: the ThreadPoolExecutor to stop 27 | """ 28 | if sys.version_info.minor >= 9: 29 | executor.shutdown(True) 30 | else: 31 | executor.shutdown(False) 32 | executor._threads.clear() 33 | futures.thread._threads_queues.clear() 34 | -------------------------------------------------------------------------------- /octobot_commons/timestamp_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | import time 18 | from datetime import datetime 19 | 20 | _EPOCH = time.time() 21 | TIMEZONE_DELTA = datetime.fromtimestamp(_EPOCH) - datetime.utcfromtimestamp(_EPOCH) 22 | 23 | 24 | def convert_timestamp_to_datetime( 25 | timestamp, time_format="%d/%m/%y %H:%M", force_timezone=False 26 | ): 27 | """ 28 | Convert a timestamp to a datetime object 29 | :param timestamp: the timestamp to convert 30 | :param time_format: the time format 31 | :param force_timezone: if the timezone should be forced 32 | :return: the created datetime object 33 | """ 34 | if force_timezone: 35 | timestamp += TIMEZONE_DELTA.seconds 36 | return datetime.fromtimestamp(timestamp).strftime(time_format) 37 | 38 | 39 | def convert_timestamps_to_datetime( 40 | timestamps, time_format="%d/%m/%y %H:%M", force_timezone=False 41 | ): 42 | """ 43 | Convert multiple timestamps to datetime objects 44 | :param timestamps: the timestamp to convert list 45 | :param time_format: the time format 46 | :param force_timezone: if the timezone should be forced 47 | :return: the created datetime objects 48 | """ 49 | return [ 50 | convert_timestamp_to_datetime( 51 | timestamp, time_format=time_format, force_timezone=force_timezone 52 | ) 53 | for timestamp in timestamps 54 | ] 55 | 56 | 57 | def is_valid_timestamp(timestamp): 58 | """ 59 | Check if the timestamp is valid 60 | :param timestamp: the timestamp to check 61 | :return: the check result 62 | """ 63 | if timestamp: 64 | try: 65 | datetime.fromtimestamp(timestamp) 66 | except OSError: 67 | return False 68 | except ValueError: 69 | return False 70 | except OverflowError: 71 | return False 72 | except TypeError: 73 | return False 74 | return True 75 | 76 | 77 | def get_now_time(time_format="%Y-%m-%d %H:%M:%S"): 78 | """ 79 | Get the current time 80 | :param time_format: the time format 81 | :return: the current timestamp 82 | """ 83 | return datetime.fromtimestamp(time.time()).strftime(time_format) 84 | 85 | 86 | def datetime_to_timestamp(date_time_str: str, date_time_format: str) -> float: 87 | """ 88 | Convert a datetime to timestamp 89 | :param date_time_str: the datetime string 90 | :param date_time_format: the datetime format 91 | :return: the timestamp 92 | """ 93 | return time.mktime( 94 | create_datetime_from_string(date_time_str, date_time_format).timetuple() 95 | ) 96 | 97 | 98 | def create_datetime_from_string(date_time_str: str, date_time_format: str) -> datetime: 99 | """ 100 | Convert a string to datetime 101 | :param date_time_str: the datetime string 102 | :param date_time_format: the datetime format 103 | :return: the converted datetime 104 | """ 105 | return datetime.strptime(date_time_str, date_time_format) 106 | -------------------------------------------------------------------------------- /octobot_commons/tree/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | from octobot_commons.tree import base_tree 18 | 19 | from octobot_commons.tree.base_tree import ( 20 | BaseTree, 21 | BaseTreeNode, 22 | NodeExistsError, 23 | ) 24 | 25 | from octobot_commons.tree import event_tree 26 | 27 | from octobot_commons.tree.event_tree import ( 28 | EventTreeNode, 29 | EventTree, 30 | ) 31 | 32 | from octobot_commons.tree import event_provider 33 | 34 | from octobot_commons.tree.event_provider import ( 35 | EventProvider, 36 | get_exchange_path, 37 | ) 38 | 39 | 40 | __all__ = [ 41 | "BaseTree", 42 | "BaseTreeNode", 43 | "NodeExistsError", 44 | "EventTreeNode", 45 | "EventTree", 46 | "EventProvider", 47 | "get_exchange_path", 48 | ] 49 | -------------------------------------------------------------------------------- /octobot_commons/tree/event_provider.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import asyncio 17 | import concurrent.futures 18 | 19 | import octobot_commons.singleton as singleton 20 | import octobot_commons.logging as logging 21 | import octobot_commons.tree.event_tree as event_tree 22 | import octobot_commons.tree.base_tree as base_tree 23 | 24 | 25 | def _create_tree_if_missing(func): 26 | """ 27 | Create the required node if missing and then recalls the function 28 | """ 29 | 30 | def wrapper(self, bot_id, *args, **kwargs): 31 | try: 32 | return func(self, bot_id, *args, **kwargs) 33 | except KeyError: 34 | self.create_event_tree(bot_id) 35 | return func(self, bot_id, *args, **kwargs) 36 | 37 | return wrapper 38 | 39 | 40 | class EventProvider(singleton.Singleton): 41 | def __init__(self): 42 | self.logger = logging.get_logger(self.__class__.__name__) 43 | self._event_tree_by_bot_id = {} 44 | 45 | @_create_tree_if_missing 46 | def get_or_create_event(self, bot_id, path, allow_creation=True): 47 | """ 48 | Return the event at the given path for the given bot_id (or create it) 49 | """ 50 | try: 51 | return self._event_tree_by_bot_id[bot_id].get_node(path) 52 | except base_tree.NodeExistsError: 53 | if allow_creation: 54 | self.create_event_at_path(bot_id, path, triggered=False) 55 | return self._event_tree_by_bot_id[bot_id].get_node(path) 56 | raise 57 | 58 | @_create_tree_if_missing 59 | def trigger_event(self, bot_id, path, allow_creation=True): 60 | """ 61 | Trigger the event at the given path for the given bot_id (or create it) 62 | """ 63 | try: 64 | self._event_tree_by_bot_id[bot_id].get_node(path).trigger() 65 | except base_tree.NodeExistsError: 66 | if allow_creation: 67 | self.create_event_at_path(bot_id, path, triggered=True) 68 | self._event_tree_by_bot_id[bot_id].get_node(path).trigger() 69 | 70 | @_create_tree_if_missing 71 | def create_event_at_path(self, bot_id, path, triggered=False): 72 | """ 73 | Create a new event at the given path for the given bot_id 74 | """ 75 | return self._event_tree_by_bot_id[bot_id].create_node_at_path(path, triggered) 76 | 77 | def create_event_tree(self, bot_id): 78 | """ 79 | Create a new event tree for the given bot_id 80 | """ 81 | self._event_tree_by_bot_id[bot_id] = event_tree.EventTree() 82 | 83 | def remove_event_tree(self, bot_id): 84 | """ 85 | Removes the event tree for the given bot_id 86 | """ 87 | self._event_tree_by_bot_id.pop(bot_id, None) 88 | 89 | async def wait_for_event(self, bot_id, path, timeout) -> bool: 90 | """ 91 | Wait for the event at the given path for the given bot_id. 92 | Returns instantly if the path doesn't lead to an event or if timeout is 0 93 | :return: False if the event is not triggered after timeout 94 | """ 95 | try: 96 | event = self.get_or_create_event(bot_id, path, allow_creation=False) 97 | if timeout == 0: 98 | return event.is_triggered() 99 | if not event.is_triggered(): 100 | await asyncio.wait_for(event.wait(), timeout) 101 | except base_tree.NodeExistsError: 102 | # nothing to wait for 103 | pass 104 | except (asyncio.TimeoutError, concurrent.futures.TimeoutError): 105 | return False 106 | return True 107 | 108 | 109 | def get_exchange_path(exchange, topic, symbol=None, time_frame=None): 110 | """ 111 | Return a path associated to the given exchange and topic 112 | as well as symbol and timeframe if provided 113 | """ 114 | node_path = [exchange, topic] 115 | if symbol is not None: 116 | node_path.append(symbol) 117 | if time_frame is not None: 118 | node_path.append(time_frame) 119 | return node_path 120 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Setup requirements 2 | numpy==1.26.3 3 | 4 | # Commons requirements 5 | jsonschema==4.20.0 6 | cryptography 7 | sortedcontainers 8 | requests 9 | psutil==5.9.7 10 | 11 | aiohttp>=3.9.5 12 | certifi==2024.02.02 13 | 14 | # async sqlite connection 15 | # update asap (0.19 working) 16 | # but required <= 0.17.0 from asyncpraw https://github.com/praw-dev/asyncpraw/blob/master/pyproject.toml 17 | aiosqlite==0.17.0 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | # from distutils.extension import Extension 17 | from setuptools import find_packages 18 | from setuptools import setup 19 | 20 | from octobot_commons import PROJECT_NAME, VERSION 21 | 22 | PACKAGES = find_packages(exclude=["tests"]) 23 | 24 | # long description from README file 25 | with open('README.md', encoding='utf-8') as f: 26 | DESCRIPTION = f.read() 27 | 28 | REQUIRED = open('requirements.txt').readlines() 29 | REQUIRES_PYTHON = '>=3.8' 30 | 31 | setup( 32 | name=PROJECT_NAME, 33 | version=VERSION, 34 | url='https://github.com/Drakkar-Software/OctoBot-Commons', 35 | license='LGPL-3.0', 36 | author='Drakkar-Software', 37 | author_email='contact@drakkar.software', 38 | description='OctoBot project common modules', 39 | packages=PACKAGES, 40 | include_package_data=True, 41 | long_description=DESCRIPTION, 42 | tests_require=["pytest"], 43 | test_suite="tests", 44 | zip_safe=False, 45 | data_files=[], 46 | install_requires=REQUIRED, 47 | python_requires=REQUIRES_PYTHON, 48 | classifiers=[ 49 | 'Development Status :: 5 - Production/Stable', 50 | 'Operating System :: OS Independent', 51 | 'Operating System :: MacOS :: MacOS X', 52 | 'Operating System :: Microsoft :: Windows', 53 | 'Operating System :: POSIX', 54 | 'Programming Language :: Python :: 3.8', 55 | 'Programming Language :: Python :: 3.9', 56 | ], 57 | ) 58 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | -------------------------------------------------------------------------------- /tests/configuration/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | -------------------------------------------------------------------------------- /tests/configuration/test_fields_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import uuid 17 | import octobot_commons.configuration as configuration 18 | 19 | 20 | def test_get_password_hash(): 21 | assert len(configuration.get_password_hash("")) == 64 22 | assert len(configuration.get_password_hash("1")) == 64 23 | assert len(configuration.get_password_hash("1a")) == 64 24 | for _ in range(100): 25 | rand_password = str(uuid.uuid4()) 26 | hashed_password = configuration.get_password_hash(rand_password) 27 | assert len(hashed_password) == 64 28 | assert not hashed_password == rand_password 29 | -------------------------------------------------------------------------------- /tests/databases/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2 | -------------------------------------------------------------------------------- /tests/databases/global_storage/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2 | -------------------------------------------------------------------------------- /tests/databases/global_storage/test_global_shared_memory_storage.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.databases as databases 17 | 18 | 19 | def test_remove_oldest_elements(): 20 | databases.GlobalSharedMemoryStorage.instance()["10"] = "a" 21 | databases.GlobalSharedMemoryStorage.instance()["2"] = 1 22 | databases.GlobalSharedMemoryStorage.instance()["-1"] = "af" 23 | databases.GlobalSharedMemoryStorage.instance()["0"] = "as" 24 | databases.GlobalSharedMemoryStorage.instance()[10] = "asfgvfs" 25 | databases.GlobalSharedMemoryStorage.instance()["12"] = "bbcc" 26 | assert len(databases.GlobalSharedMemoryStorage.instance()) == 6 27 | assert databases.GlobalSharedMemoryStorage.instance() == { 28 | "10": "a", 29 | "2": 1, 30 | "-1": "af", 31 | "0": "as", 32 | 10: "asfgvfs", 33 | "12": "bbcc", 34 | } 35 | databases.GlobalSharedMemoryStorage.instance().remove_oldest_elements(0) 36 | assert databases.GlobalSharedMemoryStorage.instance() == { 37 | "10": "a", 38 | "2": 1, 39 | "-1": "af", 40 | "0": "as", 41 | 10: "asfgvfs", 42 | "12": "bbcc", 43 | } 44 | databases.GlobalSharedMemoryStorage.instance().remove_oldest_elements(1) 45 | assert databases.GlobalSharedMemoryStorage.instance() == { 46 | "2": 1, 47 | "-1": "af", 48 | "0": "as", 49 | 10: "asfgvfs", 50 | "12": "bbcc", 51 | } 52 | databases.GlobalSharedMemoryStorage.instance().remove_oldest_elements(4) 53 | assert databases.GlobalSharedMemoryStorage.instance() == { 54 | "12": "bbcc", 55 | } 56 | databases.GlobalSharedMemoryStorage.instance()["2"] = 1 57 | assert databases.GlobalSharedMemoryStorage.instance() == { 58 | "12": "bbcc", 59 | "2": 1, 60 | } 61 | databases.GlobalSharedMemoryStorage.instance().remove_oldest_elements(1) 62 | assert databases.GlobalSharedMemoryStorage.instance() == { 63 | "2": 1, 64 | } 65 | databases.GlobalSharedMemoryStorage.instance().remove_oldest_elements(10) 66 | assert databases.GlobalSharedMemoryStorage.instance() == {} 67 | 68 | 69 | def test_get_bytes_size(): 70 | assert 0 < databases.GlobalSharedMemoryStorage.instance().get_bytes_size() < 1000 71 | for i in range(10000): 72 | databases.GlobalSharedMemoryStorage.instance()[i] = "aaaaaaaaaaaaaaaaaaaaaa" 73 | assert 200000 < databases.GlobalSharedMemoryStorage.instance().get_bytes_size() < 800000 74 | databases.GlobalSharedMemoryStorage.instance().remove_oldest_elements(10000) 75 | -------------------------------------------------------------------------------- /tests/databases/relational_databases/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2 | -------------------------------------------------------------------------------- /tests/databases/relational_databases/sqlite/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2 | -------------------------------------------------------------------------------- /tests/databases/run_databases/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drakkar-Software/OctoBot-Commons/6df1e0a99907e949a63f297efe4724b71458b4c3/tests/databases/run_databases/__init__.py -------------------------------------------------------------------------------- /tests/databases/run_databases/test_run_databases_provider.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import mock 17 | import pytest 18 | import pytest_asyncio 19 | import octobot_commons.databases as databases 20 | 21 | 22 | # All test coroutines will be treated as marked. 23 | pytestmark = pytest.mark.asyncio 24 | 25 | 26 | @pytest.fixture 27 | def run_database_identifier(): 28 | return mock.Mock( 29 | initialize=mock.AsyncMock(), 30 | close=mock.AsyncMock() 31 | ) 32 | 33 | 34 | @pytest.fixture 35 | def run_database_provider(): 36 | return databases.RunDatabasesProvider.instance() 37 | 38 | 39 | async def test_add_bot_id(run_database_provider, run_database_identifier): 40 | await run_database_provider.add_bot_id("123", run_database_identifier) 41 | run_database_provider.get_run_databases_identifier("123").initialize.assert_called_once() 42 | assert "123" in run_database_provider.run_databases 43 | 44 | 45 | async def test_has_bot_id(run_database_provider, run_database_identifier): 46 | await run_database_provider.add_bot_id("123", run_database_identifier) 47 | assert run_database_provider.has_bot_id("123") is True 48 | assert run_database_provider.has_bot_id("1232") is False 49 | 50 | 51 | async def test_close(run_database_provider, run_database_identifier): 52 | await run_database_provider.add_bot_id("123", run_database_identifier) 53 | await run_database_provider.close("123") 54 | with pytest.raises(KeyError): 55 | await run_database_provider.close("aa") 56 | -------------------------------------------------------------------------------- /tests/dataclasses/test_flexible_dataclass.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library.* 16 | import dataclasses 17 | 18 | import octobot_commons.dataclasses 19 | 20 | 21 | @dataclasses.dataclass 22 | class TestPersonClass(octobot_commons.dataclasses.FlexibleDataclass): 23 | name: str = "" 24 | age: int = 0 25 | likes: list = dataclasses.field(default_factory=list) 26 | 27 | 28 | @dataclasses.dataclass 29 | class TestPersonGroupClass(octobot_commons.dataclasses.FlexibleDataclass): 30 | identifier: str = "" 31 | present_people: list[TestPersonClass] = dataclasses.field(default_factory=list) 32 | absent_people: list[TestPersonClass] = dataclasses.field(default_factory=list) 33 | leader: TestPersonClass = dataclasses.field(default_factory=TestPersonClass) 34 | 35 | def __post_init__(self): 36 | if self.present_people and isinstance(self.present_people[0], dict): 37 | self.present_people = [TestPersonClass.from_dict(p) for p in self.present_people] if self.present_people else [] 38 | if self.absent_people and isinstance(self.absent_people[0], dict): 39 | self.absent_people = [TestPersonClass.from_dict(p) for p in self.absent_people] if self.absent_people else [] 40 | 41 | 42 | def test_from_dict(): 43 | person_1 = TestPersonClass(name="rhombur", age=33) 44 | dict_1 = dataclasses.asdict(person_1) 45 | person_1_1 = TestPersonClass.from_dict(dict_1) 46 | assert list(person_1_1.get_field_names()) == list(person_1.get_field_names()) == ['name', 'age', 'likes'] 47 | assert person_1 == person_1_1 48 | person_1_1.name = "leto" 49 | 50 | group_1 = TestPersonGroupClass(identifier="plop", absent_people=[person_1], leader=person_1_1) 51 | dict_group = dataclasses.asdict(group_1) 52 | assert TestPersonGroupClass.from_dict(dict_group) == group_1 53 | 54 | # added values are not an issue 55 | dict_group["new_attr"] = 1 56 | dict_group["absent_people"][0]["other_attr"] = None 57 | dict_group["leader"].pop("age", None) 58 | dict_group["leader"]["age2"] = 22 59 | 60 | new_group = TestPersonGroupClass.from_dict(dict_group) 61 | assert new_group.leader.age == 0 # default value 62 | 63 | 64 | def test_default_values(): 65 | group_0 = TestPersonGroupClass() 66 | group_0_1 = TestPersonGroupClass() 67 | 68 | assert group_0.leader.name == group_0_1.leader.name 69 | assert group_0.leader is not group_0_1.leader 70 | group_0.leader.name = "erasme" 71 | assert group_0.leader.name != group_0_1.leader.name 72 | 73 | 74 | def test_get_field_names(): 75 | person_1 = TestPersonClass() 76 | person_2 = TestPersonClass() 77 | group_1 = TestPersonGroupClass() 78 | group_2 = TestPersonGroupClass() 79 | 80 | assert list(person_1.get_field_names()) == list(person_2.get_field_names()) == ['name', 'age', 'likes'] 81 | assert list(group_1.get_field_names()) == list(group_2.get_field_names()) == \ 82 | ['identifier', 'present_people', 'absent_people', 'leader'] 83 | -------------------------------------------------------------------------------- /tests/logging/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drakkar-Software/OctoBot-Commons/6df1e0a99907e949a63f297efe4724b71458b4c3/tests/logging/__init__.py -------------------------------------------------------------------------------- /tests/logging/test_logging_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import mock 17 | import pytest 18 | 19 | import octobot_commons.logging as logging 20 | import octobot_commons.logging.logging_util as logging_util 21 | 22 | 23 | @pytest.fixture 24 | def logger(): 25 | return logging.get_logger("test") 26 | 27 | 28 | @pytest.fixture 29 | def call_wrapper(): 30 | callback_mock = mock.Mock() 31 | 32 | class Wrapper: 33 | def __init__(self): 34 | self.callback_mock = callback_mock 35 | 36 | def other_callback(self, *args, **kwargs): 37 | callback_mock(*args, **kwargs) 38 | return Wrapper() 39 | 40 | 41 | def test_register_error_callback(): 42 | def other_call_back(): 43 | pass 44 | logging.BotLogger.register_error_callback(logging_util._default_callback) 45 | assert logging_util._ERROR_CALLBACK is logging_util._default_callback 46 | logging.BotLogger.register_error_callback(other_call_back) 47 | assert logging_util._ERROR_CALLBACK is other_call_back 48 | 49 | 50 | def test_error(logger, call_wrapper): 51 | logging.BotLogger.register_error_callback(call_wrapper.other_callback) 52 | logger.error("err") 53 | call_wrapper.callback_mock.assert_called_once_with(None, "err") 54 | call_wrapper.callback_mock.reset_mock() 55 | 56 | logger.error("err", skip_post_callback=True) 57 | call_wrapper.callback_mock.assert_not_called() 58 | 59 | 60 | def test_exception(logger, call_wrapper): 61 | logging.BotLogger.register_error_callback(call_wrapper.other_callback) 62 | err = None 63 | def raiser(): 64 | def other(): 65 | 1/0 66 | other() 67 | try: 68 | raiser() 69 | except Exception as exc: 70 | err = exc 71 | logger.exception(err, True, "error") 72 | call_wrapper.callback_mock.assert_called_once_with(err, "error") 73 | call_wrapper.callback_mock.reset_mock() 74 | 75 | logger.exception(err, True, "error", skip_post_callback=True) 76 | call_wrapper.callback_mock.assert_not_called() 77 | -------------------------------------------------------------------------------- /tests/profiles/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import os 17 | 18 | import pytest 19 | import pathlib 20 | import octobot_commons.profiles as profiles 21 | import octobot_commons.tests.test_config as test_config 22 | 23 | 24 | def get_profile_path(): 25 | return test_config.TEST_CONFIG_FOLDER 26 | 27 | 28 | def get_profiles_path(): 29 | return pathlib.Path(get_profile_path()).parent 30 | 31 | 32 | @pytest.fixture 33 | def profile(): 34 | return profiles.Profile(get_profile_path()) 35 | 36 | 37 | @pytest.fixture 38 | def invalid_profile(): 39 | return profiles.Profile(os.path.join(get_profile_path(), "invalid_profile")) 40 | -------------------------------------------------------------------------------- /tests/signals/__init__.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import pytest 17 | 18 | import octobot_commons.signals as signals 19 | import octobot_commons.enums as enums 20 | 21 | 22 | @pytest.fixture 23 | def signal(): 24 | return signals.Signal("hello topic", {"hi": "plop"}) 25 | 26 | 27 | @pytest.fixture 28 | def signal_bundle(signal): 29 | return signals.SignalBundle("hello identifier", [signal], "version") 30 | 31 | 32 | @pytest.fixture 33 | def signal_bundle_builder(): 34 | return signals.SignalBundleBuilder("hello builder identifier") 35 | 36 | 37 | @pytest.fixture 38 | def signal_builder_wrapper(): 39 | return signals.SignalBuilderWrapper( 40 | "hello wrapper identifier", 41 | signal_builder_class=signals.SignalBundleBuilder, 42 | timeout=-2 43 | ) 44 | 45 | 46 | @pytest.fixture 47 | def signal_dict(): 48 | return { 49 | enums.SignalsAttrs.TOPIC.value: "dict topic", 50 | enums.SignalsAttrs.CONTENT.value: {"dict": "content", "hi": 1}, 51 | } 52 | 53 | 54 | @pytest.fixture 55 | def signal_bundle_dict(signal_dict): 56 | return { 57 | enums.CommunityFeedAttrs.VALUE.value: { 58 | enums.SignalBundlesAttrs.IDENTIFIER.value: "dict identifier", 59 | enums.SignalBundlesAttrs.SIGNALS.value: [signal_dict], 60 | enums.SignalBundlesAttrs.VERSION.value: "dict_version" 61 | }, 62 | } 63 | -------------------------------------------------------------------------------- /tests/signals/test_signal.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.enums 17 | 18 | from tests.signals import signal 19 | 20 | 21 | def test_to_dict(signal): 22 | assert signal.to_dict() == { 23 | octobot_commons.enums.SignalsAttrs.TOPIC.value: "hello topic", 24 | octobot_commons.enums.SignalsAttrs.CONTENT.value: {"hi": "plop"}, 25 | } 26 | 27 | 28 | def test__str__(signal): 29 | assert all(sub_str in str(signal) for sub_str in ("hello topic", "hi", "plop")) 30 | -------------------------------------------------------------------------------- /tests/signals/test_signal_builder_wrapper.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.enums 17 | import octobot_commons.signals as signals 18 | 19 | from tests.signals import signal_builder_wrapper 20 | 21 | 22 | def test_register_user(signal_builder_wrapper): 23 | assert signal_builder_wrapper._users_count == 0 24 | signal_builder_wrapper.register_user() 25 | assert signal_builder_wrapper._users_count == 1 26 | signal_builder_wrapper.register_user() 27 | assert signal_builder_wrapper._users_count == 2 28 | 29 | 30 | def test_unregister_user(signal_builder_wrapper): 31 | assert signal_builder_wrapper._users_count == 0 32 | signal_builder_wrapper.register_user() 33 | assert signal_builder_wrapper._users_count == 1 34 | signal_builder_wrapper.register_user() 35 | assert signal_builder_wrapper._users_count == 2 36 | signal_builder_wrapper.unregister_user() 37 | assert signal_builder_wrapper._users_count == 1 38 | signal_builder_wrapper.register_user() 39 | assert signal_builder_wrapper._users_count == 2 40 | signal_builder_wrapper.unregister_user() 41 | assert signal_builder_wrapper._users_count == 1 42 | signal_builder_wrapper.unregister_user() 43 | assert signal_builder_wrapper._users_count == 0 44 | 45 | 46 | def test_has_single_user(signal_builder_wrapper): 47 | assert signal_builder_wrapper.has_single_user() is False 48 | signal_builder_wrapper._users_count = 1 49 | assert signal_builder_wrapper.has_single_user() is True 50 | signal_builder_wrapper._users_count = 10 51 | assert signal_builder_wrapper.has_single_user() is False 52 | signal_builder_wrapper._users_count = 1 53 | assert signal_builder_wrapper.has_single_user() is True 54 | -------------------------------------------------------------------------------- /tests/signals/test_signal_bundle.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.enums 17 | 18 | from tests.signals import signal, signal_bundle 19 | 20 | 21 | def test_to_dict(signal_bundle, signal): 22 | assert signal_bundle.to_dict() == { 23 | octobot_commons.enums.SignalBundlesAttrs.IDENTIFIER.value: "hello identifier", 24 | octobot_commons.enums.SignalBundlesAttrs.SIGNALS.value: [signal.to_dict()], 25 | octobot_commons.enums.SignalBundlesAttrs.VERSION.value: "version", 26 | } 27 | 28 | 29 | def test__str__(signal_bundle): 30 | assert all(sub_str in str(signal_bundle) 31 | for sub_str in ("hello identifier", "version", "hello topic", "hi", "plop")) 32 | 33 | 34 | def test_get_version(signal_bundle): 35 | assert signal_bundle._get_version() == "1.0.0" 36 | -------------------------------------------------------------------------------- /tests/signals/test_signal_bundle_builder.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.enums 17 | import octobot_commons.signals as signals 18 | 19 | from tests.signals import signal_bundle_builder 20 | 21 | 22 | def test_register_signal(signal_bundle_builder): 23 | assert signal_bundle_builder.signals == [] 24 | signal_bundle_builder.register_signal("plop_topic", {"content_key1": 0}, other_kwarg=11) 25 | assert len(signal_bundle_builder.signals) == 1 26 | assert signal_bundle_builder.signals[0].topic == "plop_topic" 27 | assert signal_bundle_builder.signals[0].content == {"content_key1": 0} 28 | 29 | 30 | def test_create_signal(signal_bundle_builder): 31 | created_signal = signal_bundle_builder.create_signal("plop_topic", {"content_key1": 0}, other_kwarg=11) 32 | assert created_signal.topic == "plop_topic" 33 | assert created_signal.content == {"content_key1": 0} 34 | 35 | 36 | def test_is_empty(signal_bundle_builder): 37 | assert signal_bundle_builder.is_empty() 38 | signal_bundle_builder.register_signal("plop_topic", {"content_key1": 0}, other_kwarg=11) 39 | assert not signal_bundle_builder.is_empty() 40 | 41 | 42 | def test_build(signal_bundle_builder): 43 | signal_bundle_builder.version = "1" 44 | empty_build_bundle = signal_bundle_builder.build() 45 | assert empty_build_bundle.identifier == "hello builder identifier" 46 | assert empty_build_bundle.signals == [] 47 | assert empty_build_bundle.version == "1" 48 | signal_bundle_builder.register_signal("plop_topic", {"content_key1": 0}, other_kwarg=11) 49 | full_build_bundle = signal_bundle_builder.build() 50 | assert full_build_bundle.identifier == "hello builder identifier" 51 | assert full_build_bundle.signals is signal_bundle_builder.signals 52 | assert full_build_bundle.version == "1" 53 | 54 | 55 | def test_reset(signal_bundle_builder): 56 | assert signal_bundle_builder.signals == [] 57 | signal_bundle_builder.register_signal("plop_topic", {"content_key1": 0}, other_kwarg=11) 58 | assert len(signal_bundle_builder.signals) == 1 59 | signal_bundle_builder.reset() 60 | assert signal_bundle_builder.signals == [] 61 | -------------------------------------------------------------------------------- /tests/signals/test_signal_factory.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.signals as signals 17 | 18 | from tests.signals import signal_dict, signal_bundle_dict 19 | 20 | 21 | def test_create_signal_bundle(signal_bundle_dict): 22 | created_bundle = signals.create_signal_bundle(signal_bundle_dict) 23 | assert len(created_bundle.signals) == 1 24 | assert created_bundle.identifier == "dict identifier" 25 | assert created_bundle.version == "dict_version" 26 | assert created_bundle.signals[0].topic == "dict topic" 27 | assert created_bundle.signals[0].content == {"dict": "content", "hi": 1} 28 | 29 | 30 | def test_create_signal(signal_dict): 31 | created_signal = signals.create_signal(signal_dict) 32 | assert created_signal.topic == "dict topic" 33 | assert created_signal.content == {"dict": "content", "hi": 1} 34 | -------------------------------------------------------------------------------- /tests/static/ExchangeHistoryDataCollector_1589740606.4862757.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drakkar-Software/OctoBot-Commons/6df1e0a99907e949a63f297efe4724b71458b4c3/tests/static/ExchangeHistoryDataCollector_1589740606.4862757.data -------------------------------------------------------------------------------- /tests/static/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "time_frame": ["1h", "4h", "1d"], 3 | "crypto-currencies":{ 4 | "Bitcoin": { 5 | "pairs" : ["BTC/USDT", "BTC/USD"] 6 | } 7 | }, 8 | "exchanges": {} 9 | } -------------------------------------------------------------------------------- /tests/static/default_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "backtesting": { 3 | "files": [] 4 | }, 5 | "exchanges": { 6 | "binance": { 7 | "api-key": "your-api-key-here", 8 | "api-secret": "your-api-secret-here" 9 | } 10 | }, 11 | "services": {}, 12 | "notification":{ 13 | "global-info": true, 14 | "price-alerts": true, 15 | "trades": true, 16 | "notification-type": [] 17 | }, 18 | "accepted_terms": false 19 | } 20 | -------------------------------------------------------------------------------- /tests/static/default_profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drakkar-Software/OctoBot-Commons/6df1e0a99907e949a63f297efe4724b71458b4c3/tests/static/default_profile.png -------------------------------------------------------------------------------- /tests/static/invalid_profile/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "profile": { 3 | "avatar": "default_profile.png", 4 | "description": "OctoBot default profile.", 5 | "id": "invalid_profile", 6 | "name": "default", 7 | "origin_url": "https://default.url" 8 | }, 9 | "config": { 10 | "crypto-currencies": { 11 | "Bitcoin": { 12 | "pairs": [ 13 | "BTC/USDT" 14 | ] 15 | }, 16 | "plop": { 17 | "pairs": [ 18 | "BTC/USDT" 19 | ], 20 | "config": { 21 | "i should not be there": true 22 | } 23 | } 24 | }, 25 | "exchanges": { 26 | "binance": { 27 | "enabled": true 28 | } 29 | }, 30 | "trading": { 31 | "reference-market": "BTC", 32 | "risk": 0.5 33 | }, 34 | "trader": { 35 | "enabled": false, 36 | "load-trade-history": true 37 | }, 38 | "trader-simulator": { 39 | "enabled": true, 40 | "fees": { 41 | "maker": 0.1, 42 | "taker": 0.1 43 | }, 44 | "starting-portfolio": { 45 | "BTC": 10, 46 | "USDT": 1000 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/static/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "profile": { 3 | "avatar": "default_profile.png", 4 | "description": "OctoBot default profile.", 5 | "id": "default", 6 | "name": "default", 7 | "origin_url": "https://default.url" 8 | }, 9 | "config": { 10 | "crypto-currencies": { 11 | "Bitcoin": { 12 | "pairs": [ 13 | "BTC/USDT" 14 | ] 15 | } 16 | }, 17 | "exchanges": { 18 | "binance": { 19 | "enabled": true 20 | } 21 | }, 22 | "trading": { 23 | "reference-market": "BTC", 24 | "risk": 0.5 25 | }, 26 | "trader": { 27 | "enabled": false, 28 | "load-trade-history": true 29 | }, 30 | "trader-simulator": { 31 | "enabled": true, 32 | "fees": { 33 | "maker": 0.1, 34 | "taker": 0.1 35 | }, 36 | "starting-portfolio": { 37 | "BTC": 10, 38 | "USDT": 1000 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/static/second_ExchangeHistoryDataCollector_1589740606.4862757.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drakkar-Software/OctoBot-Commons/6df1e0a99907e949a63f297efe4724b71458b4c3/tests/static/second_ExchangeHistoryDataCollector_1589740606.4862757.data -------------------------------------------------------------------------------- /tests/symbols/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2 | -------------------------------------------------------------------------------- /tests/symbols/test_symbol.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import pytest 17 | 18 | import octobot_commons.symbols 19 | 20 | 21 | @pytest.fixture 22 | def spot_symbol(): 23 | return octobot_commons.symbols.Symbol("BTC/USDT") 24 | 25 | 26 | @pytest.fixture 27 | def perpetual_future_symbol(): 28 | return octobot_commons.symbols.Symbol("BTC/USDT:BTC") 29 | 30 | 31 | @pytest.fixture 32 | def future_symbol(): 33 | return octobot_commons.symbols.Symbol("ETH/USDT:USDT-210625") 34 | 35 | 36 | @pytest.fixture 37 | def option_symbol(): 38 | return octobot_commons.symbols.Symbol("ETH/USDT:USDT-211225-40000-C") 39 | 40 | 41 | def test_parse_spot_symbol(spot_symbol): 42 | assert spot_symbol.base == "BTC" 43 | assert spot_symbol.quote == "USDT" 44 | assert spot_symbol.settlement_asset == spot_symbol.identifier == spot_symbol.strike_price \ 45 | == spot_symbol.option_type == "" 46 | 47 | 48 | def test_parse_perpetual_future_symbol(perpetual_future_symbol): 49 | assert perpetual_future_symbol.base == "BTC" 50 | assert perpetual_future_symbol.quote == "USDT" 51 | assert perpetual_future_symbol.settlement_asset == "BTC" 52 | assert perpetual_future_symbol.strike_price == perpetual_future_symbol.option_type == "" 53 | 54 | 55 | def test_parse_future_symbol(future_symbol): 56 | assert future_symbol.base == "ETH" 57 | assert future_symbol.quote == "USDT" 58 | assert future_symbol.settlement_asset == "USDT" 59 | assert future_symbol.identifier == "210625" 60 | assert future_symbol.strike_price == future_symbol.option_type == "" 61 | 62 | 63 | def test_parse_option_symbol(option_symbol): 64 | assert option_symbol.base == "ETH" 65 | assert option_symbol.quote == "USDT" 66 | assert option_symbol.settlement_asset == "USDT" 67 | assert option_symbol.identifier == "211225" 68 | assert option_symbol.strike_price == "40000" 69 | assert option_symbol.option_type == "C" 70 | 71 | 72 | def test_base_and_quote(spot_symbol, option_symbol): 73 | assert spot_symbol.base_and_quote() == ("BTC", "USDT") 74 | assert option_symbol.base_and_quote() == ("ETH", "USDT") 75 | 76 | 77 | def test_is_linear(spot_symbol, perpetual_future_symbol, option_symbol): 78 | assert spot_symbol.is_linear() is True 79 | assert perpetual_future_symbol.is_linear() is False 80 | assert option_symbol.is_linear() is True 81 | 82 | 83 | def test_is_inverse(spot_symbol, perpetual_future_symbol, option_symbol): 84 | assert spot_symbol.is_inverse() is False 85 | assert perpetual_future_symbol.is_inverse() is True 86 | assert option_symbol.is_inverse() is False 87 | 88 | 89 | def test__eq__(spot_symbol, option_symbol): 90 | assert spot_symbol == octobot_commons.symbols.Symbol("BTC/USDT") 91 | assert spot_symbol != octobot_commons.symbols.Symbol("BTC/USD") 92 | assert spot_symbol != option_symbol 93 | assert option_symbol == octobot_commons.symbols.Symbol("ETH/USDT:USDT-211225-40000-C") 94 | assert option_symbol != octobot_commons.symbols.Symbol("ETH/USDT:USDT-211225-40000-P") 95 | -------------------------------------------------------------------------------- /tests/symbols/test_symbol_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.symbols 17 | 18 | 19 | def test_parse_symbol(): 20 | assert octobot_commons.symbols.parse_symbol("BTC/USDT") == octobot_commons.symbols.Symbol("BTC/USDT") 21 | 22 | 23 | def test_merge_symbol(): 24 | assert octobot_commons.symbols.merge_symbol("BTC/USDT") == "BTCUSDT" 25 | assert octobot_commons.symbols.merge_symbol("BTC/USDT:USDT") == "BTCUSDT_USDT" 26 | 27 | 28 | def test_merge_currencies(): 29 | assert octobot_commons.symbols.merge_currencies("BTC", "USDT") == "BTC/USDT" 30 | assert octobot_commons.symbols.merge_currencies("BTC", "USDT", "BTC") == "BTC/USDT:BTC" 31 | assert octobot_commons.symbols.merge_currencies("BTC", "USDT", "XXX", "g", "d") == "BTCgUSDTdXXX" 32 | 33 | 34 | def test_convert_symbol(): 35 | assert octobot_commons.symbols.convert_symbol("BTC-USDT", symbol_separator="-") == "BTC/USDT" 36 | -------------------------------------------------------------------------------- /tests/tentacles_management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drakkar-Software/OctoBot-Commons/6df1e0a99907e949a63f297efe4724b71458b4c3/tests/tentacles_management/__init__.py -------------------------------------------------------------------------------- /tests/tentacles_management/test_abstract_tentacle.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.configuration as configuration 17 | from octobot_commons.tentacles_management.abstract_tentacle import AbstractTentacle 18 | 19 | 20 | class TentacleTest(AbstractTentacle): 21 | def __init__(self): 22 | super().__init__() 23 | 24 | 25 | class TentacleTestChild(TentacleTest): 26 | def __init__(self): 27 | super().__init__() 28 | self.plop = 1 29 | 30 | 31 | def test_get_name(): 32 | assert TentacleTest().get_name() == "TentacleTest" 33 | assert TentacleTestChild().get_name() == "TentacleTestChild" 34 | 35 | 36 | def test_get_all_subclasses(): 37 | assert TentacleTest().get_all_subclasses() == [TentacleTestChild] 38 | 39 | 40 | def test_user_input_factories(): 41 | tentacle = TentacleTest() 42 | assert isinstance(tentacle.UI, configuration.UserInputFactory) 43 | assert isinstance(tentacle.CLASS_UI, configuration.UserInputFactory) 44 | assert isinstance(tentacle.__class__.CLASS_UI, configuration.UserInputFactory) 45 | assert isinstance(TentacleTestChild.CLASS_UI, configuration.UserInputFactory) 46 | assert TentacleTestChild.CLASS_UI is tentacle.CLASS_UI 47 | 48 | child_tentacle = TentacleTestChild() 49 | assert TentacleTestChild.CLASS_UI is child_tentacle.CLASS_UI 50 | assert child_tentacle.plop == 1 51 | assert isinstance(child_tentacle.UI, configuration.UserInputFactory) 52 | assert isinstance(child_tentacle.CLASS_UI, configuration.UserInputFactory) 53 | assert isinstance(tentacle.__class__.CLASS_UI, configuration.UserInputFactory) 54 | assert isinstance(TentacleTestChild.CLASS_UI, configuration.UserInputFactory) 55 | assert TentacleTestChild.CLASS_UI is child_tentacle.CLASS_UI 56 | assert tentacle.CLASS_UI is child_tentacle.CLASS_UI 57 | -------------------------------------------------------------------------------- /tests/tentacles_management/test_class_inspector.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | from octobot_commons.tentacles_management.class_inspector import default_parent_inspection, default_parents_inspection, \ 18 | get_class_from_parent_subclasses, get_deep_class_from_parent_subclasses, \ 19 | is_abstract_using_inspection_and_class_naming, get_all_classes_from_parent, get_single_deepest_child_class 20 | 21 | 22 | class AbstractParent: 23 | pass 24 | 25 | 26 | class Parent(AbstractParent): 27 | pass 28 | 29 | 30 | class BasicChild(Parent): 31 | pass 32 | 33 | 34 | class ChildOfChild(BasicChild): 35 | pass 36 | 37 | 38 | def test_default_parent_inspection(): 39 | assert default_parent_inspection(BasicChild, Parent) 40 | assert not default_parent_inspection(ChildOfChild, Parent) 41 | assert default_parent_inspection(ChildOfChild, BasicChild) 42 | assert not default_parent_inspection(BasicChild, ChildOfChild) 43 | 44 | 45 | def test_default_parents_inspection(): 46 | assert default_parents_inspection(BasicChild, Parent) 47 | assert default_parents_inspection(ChildOfChild, Parent) 48 | assert default_parents_inspection(ChildOfChild, BasicChild) 49 | assert not default_parents_inspection(BasicChild, ChildOfChild) 50 | 51 | 52 | def test_get_class_from_parent_subclasses(): 53 | assert get_class_from_parent_subclasses("BasicChild", Parent) is BasicChild 54 | assert get_class_from_parent_subclasses("ChildOfChild", Parent) is None 55 | assert get_class_from_parent_subclasses("ChildOfChild", BasicChild) is ChildOfChild 56 | assert get_class_from_parent_subclasses("BasicChild", ChildOfChild) is None 57 | 58 | 59 | def test_get_deep_class_from_parent_subclasses(): 60 | assert get_deep_class_from_parent_subclasses("BasicChild", Parent) is BasicChild 61 | assert get_deep_class_from_parent_subclasses("ChildOfChild", Parent) is ChildOfChild 62 | assert get_deep_class_from_parent_subclasses("ChildOfChild", BasicChild) is ChildOfChild 63 | assert get_deep_class_from_parent_subclasses("BasicChild", ChildOfChild) is None 64 | 65 | 66 | def test_is_abstract_using_inspection_and_class_naming(): 67 | assert is_abstract_using_inspection_and_class_naming(AbstractParent) 68 | assert not is_abstract_using_inspection_and_class_naming(Parent) 69 | assert not is_abstract_using_inspection_and_class_naming(ChildOfChild) 70 | 71 | 72 | def test_get_all_classes_from_parent(): 73 | assert get_all_classes_from_parent(Parent) == [BasicChild, ChildOfChild] 74 | assert get_all_classes_from_parent(BasicChild) == [ChildOfChild] 75 | assert get_all_classes_from_parent(ChildOfChild) == [] 76 | 77 | 78 | def test_get_single_deepest_child_class(): 79 | assert get_single_deepest_child_class(Parent) == ChildOfChild 80 | assert get_single_deepest_child_class(BasicChild) == ChildOfChild 81 | assert get_single_deepest_child_class(ChildOfChild) == ChildOfChild 82 | -------------------------------------------------------------------------------- /tests/test_aiohttp_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import contextlib 17 | import mock 18 | import pytest 19 | import aiohttp 20 | import certifi 21 | 22 | import octobot_commons.aiohttp_util as aiohttp_util 23 | import octobot_commons.constants as commons_constants 24 | 25 | pytestmark = pytest.mark.asyncio 26 | 27 | 28 | async def test_get_ssl_fallback_aiohttp_client_session(): 29 | origin_where = certifi.where 30 | ok_get_mock_calls = [] 31 | ko_get_mock = [] 32 | 33 | @contextlib.asynccontextmanager 34 | async def _ok_get_mock(*args, **kwargs): 35 | ok_get_mock_calls.append(1) 36 | yield mock.Mock(status=200) 37 | 38 | @contextlib.asynccontextmanager 39 | async def _ko_get_mock(*args, **kwargs): 40 | ko_get_mock.append(1) 41 | yield mock.Mock(status=200) 42 | raise aiohttp.ClientConnectorCertificateError("ssl blabla", RuntimeError()) 43 | 44 | with mock.patch.object(certifi, "where", mock.Mock(side_effect=origin_where)) as where_mock: 45 | 46 | # no need for certifi 47 | with mock.patch.object(aiohttp.ClientSession, "get", _ok_get_mock): 48 | session = await aiohttp_util.get_ssl_fallback_aiohttp_client_session( 49 | commons_constants.KNOWN_POTENTIALLY_SSL_FAILED_REQUIRED_URL 50 | ) 51 | assert isinstance(session, aiohttp.ClientSession) 52 | assert len(ok_get_mock_calls) == 1 53 | ok_get_mock_calls.clear() 54 | assert len(ko_get_mock) == 0 55 | where_mock.assert_not_called() 56 | await session.close() 57 | 58 | # need for certifi 59 | with mock.patch.object(aiohttp.ClientSession, "get", _ko_get_mock): 60 | async with aiohttp_util.ssl_fallback_aiohttp_client_session( 61 | commons_constants.KNOWN_POTENTIALLY_SSL_FAILED_REQUIRED_URL 62 | ): 63 | assert isinstance(session, aiohttp.ClientSession) 64 | assert len(ok_get_mock_calls) == 0 65 | assert len(ko_get_mock) == 1 66 | where_mock.assert_called_once() 67 | 68 | 69 | async def test_fetch_test_url_with_and_without_certify(): 70 | base_session = aiohttp.ClientSession() 71 | certify_session = aiohttp_util._get_certify_aiohttp_client_session() 72 | try: 73 | async with base_session.get(commons_constants.KNOWN_POTENTIALLY_SSL_FAILED_REQUIRED_URL) as resp: 74 | assert resp.status < 400 75 | base_text = await resp.text() 76 | assert "DrakkarSoftware" in base_text 77 | async with certify_session.get(commons_constants.KNOWN_POTENTIALLY_SSL_FAILED_REQUIRED_URL) as resp: 78 | assert resp.status < 400 79 | certifi_text = await resp.text() 80 | assert base_text == certifi_text 81 | finally: 82 | if base_session: 83 | await base_session.close() 84 | if certify_session: 85 | await certify_session.close() 86 | 87 | 88 | async def test_certify_aiohttp_client_session(): 89 | origin_where = certifi.where 90 | 91 | with mock.patch.object(certifi, "where", mock.Mock(side_effect=origin_where)) as where_mock: 92 | async with aiohttp_util.certify_aiohttp_client_session() as session: 93 | assert isinstance(session, aiohttp.ClientSession) 94 | where_mock.assert_called_once() 95 | -------------------------------------------------------------------------------- /tests/test_data_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import numpy as np 17 | 18 | from octobot_commons.data_util import drop_nan, mean, shift_value_array 19 | 20 | 21 | def test_drop_nan(): 22 | assert np.array_equal(drop_nan(np.array([1, np.nan, 2, 3, np.nan])), np.array([1, 2, 3])) 23 | assert np.array_equal(drop_nan(np.array([np.nan, np.nan, np.nan])), np.array([])) 24 | 25 | 26 | def test_mean(): 27 | assert mean([1, 2, 3, 4, 5, 6, 7]) == 4.0 28 | assert mean([0.684, 1, 2, 3, 4, 5.5, 6, 7.5]) == 3.7105 29 | assert mean([]) == 0 30 | 31 | 32 | def test_shift_value_array(): 33 | array = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=np.float64) 34 | np.testing.assert_array_equal(shift_value_array(array, shift_count=-1, fill_value=np.nan), 35 | np.array([2, 3, 4, 5, 6, 7, 8, 9, np.nan], dtype=np.float64)) 36 | 37 | array = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=np.float64) 38 | np.testing.assert_array_equal(shift_value_array(array, shift_count=2, fill_value=np.nan), 39 | np.array([np.nan, np.nan, 1, 2, 3, 4, 5, 6, 7], dtype=np.float64)) 40 | -------------------------------------------------------------------------------- /tests/test_evaluator_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import math 17 | import numpy as np 18 | 19 | from octobot_commons.constants import START_PENDING_EVAL_NOTE, INIT_EVAL_NOTE 20 | from octobot_commons.evaluators_util import check_valid_eval_note 21 | 22 | 23 | def test_check_valid_eval_note(): 24 | assert not check_valid_eval_note(START_PENDING_EVAL_NOTE) 25 | assert check_valid_eval_note(True) 26 | assert check_valid_eval_note({"a": 1}) 27 | assert check_valid_eval_note({"a": 1}, eval_time=1, expiry_delay=2, current_time=1) 28 | assert check_valid_eval_note({"a": 1}, eval_time=1, expiry_delay=2, current_time=2) 29 | assert not check_valid_eval_note({"a": 1}, eval_time=1, expiry_delay=2, current_time=3) 30 | 31 | assert check_valid_eval_note(INIT_EVAL_NOTE) 32 | # UNSET_EVAL_TYPE 33 | 34 | -------------------------------------------------------------------------------- /tests/test_list_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | from octobot_commons.list_util import flatten_list 17 | 18 | 19 | def test_flatten_list(): 20 | assert flatten_list([["a", "b", "c"], [1, 2, 3], [1, "5"]]) == ["a", "b", "c", 1, 2, 3, 1, "5"] 21 | -------------------------------------------------------------------------------- /tests/test_number_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | from octobot_commons.number_util import round_into_str_with_max_digits, round_into_float_with_max_digits 17 | 18 | 19 | def test_round_into_max_digits(): 20 | assert round_into_str_with_max_digits(125.0256, 2) == '125.03' 21 | assert round_into_float_with_max_digits(125.0210, 2) == 125.02 22 | assert round_into_float_with_max_digits(1301, 5) == 1301.00000 23 | assert round_into_float_with_max_digits(59866, 0) == 59866 24 | assert round_into_float_with_max_digits(1.567824117582484154178, 15) == 1.567824117582484 25 | assert round_into_float_with_max_digits(0.000000059, 8) == 0.00000006 26 | assert not round_into_float_with_max_digits(8712661000.1273185137283, 10) == 8712661000.127318 27 | assert round_into_float_with_max_digits(8712661000.1273185137283, 10) == 8712661000.1273185 28 | assert round_into_float_with_max_digits(8712661000.1273185137283, 10) == 8712661000.12731851 29 | assert round_into_float_with_max_digits(8712661000.1273185137283, 10) == 8712661000.127318513 30 | assert round_into_float_with_max_digits(8712661000.1273185137283, 10) == 8712661000.1273185137 31 | assert round_into_float_with_max_digits(0.0000000000001, 5) == 0 32 | assert not round_into_float_with_max_digits(0.0000000000001, 13) == 0 33 | 34 | -------------------------------------------------------------------------------- /tests/test_os_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import octobot_commons.os_util as os_util 17 | 18 | 19 | def test_get_cpu_and_ram_usage(): 20 | cpu, percent_ram, used_ram, process_ram = os_util.get_cpu_and_ram_usage(0.1) 21 | assert isinstance(cpu, float) 22 | assert percent_ram > 0 23 | assert used_ram > 0 24 | assert process_ram > 0 25 | -------------------------------------------------------------------------------- /tests/test_singleton.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | from octobot_commons.singleton.singleton_class import Singleton 17 | 18 | 19 | class SingletonTest(Singleton): 20 | def __init__(self): 21 | self.test_attr = "" 22 | 23 | 24 | instance = SingletonTest().instance() 25 | 26 | 27 | def test_create_instance(): 28 | assert SingletonTest.instance() is instance 29 | 30 | 31 | def test_instance_attribute(): 32 | instance.test_attr = "test" 33 | assert SingletonTest.instance().test_attr == "test" 34 | -------------------------------------------------------------------------------- /tests/test_time_frame_manager.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | from octobot_commons.enums import TimeFrames 17 | from octobot_commons.tests.test_config import load_test_config 18 | from octobot_commons.time_frame_manager import get_config_time_frame, parse_time_frames, find_min_time_frame, \ 19 | get_previous_time_frame, get_display_time_frame, sort_time_frames 20 | 21 | 22 | def test_get_config_time_frame(): 23 | assert get_config_time_frame(load_test_config()) == [TimeFrames("1h"), TimeFrames("4h"), TimeFrames("1d")] 24 | 25 | 26 | def test_parse_time_frames(): 27 | assert parse_time_frames(["3d", "5d", "1m", "6h"]) == [TimeFrames("3d"), TimeFrames("1m"), TimeFrames("6h")] 28 | 29 | 30 | def test_sort_time_frames(): 31 | assert sort_time_frames([TimeFrames("3d"), TimeFrames("1m"), TimeFrames("6h")]) == \ 32 | [TimeFrames("1m"), TimeFrames("6h"), TimeFrames("3d")] 33 | assert sort_time_frames([TimeFrames("1M"), TimeFrames("3d"), TimeFrames("12h"), TimeFrames("1h"), 34 | TimeFrames("1m"), TimeFrames("6h")]) == \ 35 | [TimeFrames("1m"), TimeFrames("1h"), TimeFrames("6h"), TimeFrames("12h"), TimeFrames("3d"), TimeFrames("1M")] 36 | 37 | 38 | def test_find_min_time_frame(): 39 | assert find_min_time_frame([TimeFrames.FOUR_HOURS, TimeFrames.ONE_DAY, TimeFrames.ONE_MONTH, 40 | TimeFrames.FIFTEEN_MINUTES]) == TimeFrames.FIFTEEN_MINUTES 41 | assert find_min_time_frame([TimeFrames.ONE_MONTH, TimeFrames.ONE_WEEK]) == TimeFrames.ONE_WEEK 42 | assert find_min_time_frame([TimeFrames.ONE_MINUTE]) == TimeFrames.ONE_MINUTE 43 | 44 | 45 | def test_get_previous_time_frame(): 46 | assert get_previous_time_frame(get_config_time_frame(load_test_config()), 47 | TimeFrames.ONE_DAY, TimeFrames.ONE_DAY) == TimeFrames.FOUR_HOURS 48 | assert get_previous_time_frame(get_config_time_frame(load_test_config()), 49 | TimeFrames.ONE_MINUTE, TimeFrames.ONE_MINUTE) == TimeFrames.ONE_MINUTE 50 | assert get_previous_time_frame(get_config_time_frame(load_test_config()), 51 | TimeFrames.ONE_HOUR, TimeFrames.ONE_HOUR) == TimeFrames.ONE_HOUR 52 | assert get_previous_time_frame(get_config_time_frame(load_test_config()), 53 | TimeFrames.ONE_MONTH, TimeFrames.ONE_MONTH) == TimeFrames.ONE_DAY 54 | 55 | 56 | def test_get_display_time_frame(): 57 | assert get_display_time_frame(load_test_config(), TimeFrames.ONE_MONTH) == TimeFrames.ONE_DAY 58 | assert get_display_time_frame(load_test_config(), TimeFrames.FOUR_HOURS) == TimeFrames.FOUR_HOURS 59 | -------------------------------------------------------------------------------- /tests/test_timestamp_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | import time 17 | 18 | from octobot_commons.timestamp_util import is_valid_timestamp, get_now_time, datetime_to_timestamp, \ 19 | convert_timestamp_to_datetime 20 | 21 | 22 | def test_is_valid_timestamp(): 23 | assert not is_valid_timestamp(get_now_time()) 24 | assert is_valid_timestamp(time.time()) 25 | 26 | 27 | def test_datetime_to_timestamp(): 28 | assert datetime_to_timestamp(convert_timestamp_to_datetime(322548600, time_format="%d/%m/%y %H:%M"), 29 | date_time_format="%d/%m/%y %H:%M") == 322548600 30 | 31 | -------------------------------------------------------------------------------- /tests/thread_util.py: -------------------------------------------------------------------------------- 1 | # Drakkar-Software OctoBot-Commons 2 | # Copyright (c) Drakkar-Software, All rights reserved. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3.0 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library. 16 | 17 | import concurrent.futures as futures 18 | import time 19 | 20 | import octobot_commons.thread_util as thread_util 21 | 22 | 23 | def test_method(): 24 | time.sleep(100000000) 25 | 26 | 27 | def test_stop_thread_pool_executor_non_gracefully(): 28 | executor = futures.ThreadPoolExecutor(max_workers=2) 29 | for _ in range(2): 30 | executor.submit(test_method) 31 | thread_util.stop_thread_pool_executor_non_gracefully(executor) 32 | -------------------------------------------------------------------------------- /tests/tree/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drakkar-Software/OctoBot-Commons/6df1e0a99907e949a63f297efe4724b71458b4c3/tests/tree/__init__.py --------------------------------------------------------------------------------