├── .dockerignore
├── .github
└── workflows
│ ├── docker.yml
│ └── main.yml
├── .gitignore
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── MANIFEST.in
├── README.md
├── SUMMARY.md
├── ai-example.py
├── dev_requirements.txt
├── docs
├── data
│ └── fetching_data.md
├── plotting
│ └── basis.md
├── strategies
│ └── basis.md
└── trading
│ └── orders.md
├── example.py
├── octobot_script
├── __init__.py
├── ai
│ ├── __init__.py
│ ├── agents.py
│ ├── environments.py
│ └── models.py
├── api
│ ├── __init__.py
│ ├── data_fetching.py
│ ├── execution.py
│ └── ploting.py
├── cli.py
├── config
│ ├── config_mock.json
│ └── logging_config.ini
├── constants.py
├── internal
│ ├── __init__.py
│ ├── backtester_trading_mode.py
│ ├── logging_util.py
│ ├── octobot_mocks.py
│ └── runners.py
├── model
│ ├── __init__.py
│ ├── backtest_plot.py
│ ├── backtest_result.py
│ ├── errors.py
│ └── strategy.py
└── resources
│ ├── __init__.py
│ └── reports
│ ├── css
│ ├── style.css
│ └── w2ui_template.css
│ ├── default_report_template.html
│ ├── header.html
│ ├── js
│ ├── common.js
│ ├── data.js
│ ├── graphs.js
│ ├── tables.js
│ └── texts.js
│ └── scripts.html
├── requirements-ai.txt
├── requirements.txt
├── setup.py
├── standard.rc
├── start.py
└── tests
├── __init__.py
├── api
├── __init__.py
├── test_data_fetching.py
└── test_execution.py
├── functionnal
├── __init__.py
└── example_scripts
│ ├── __init__.py
│ └── test_precomputed_vs_iteration_rsi.py
└── test_util
└── ExchangeHistoryDataCollector_1673796151.325921.data
/.dockerignore:
--------------------------------------------------------------------------------
1 | # dev
2 | .idea
3 |
4 | # CI files
5 | .coveragerc
6 | .coveralls.yml
7 | .travis.yml
8 | appveyor.yml
9 | renovate.json
10 | setup.cfg
11 | tox.ini
12 |
13 | # octobot
14 | tentacles
15 | user
16 | logs
17 |
18 | # Git
19 | .git
20 | Dockerfile
21 | .DS_Store
22 | .gitignore
23 | .dockerignore
24 | .github
25 |
26 | # Files that might appear in the root of a volume
27 | .DocumentRevisions-V100
28 | .fseventsd
29 | .Spotlight-V100
30 | .TemporaryItems
31 | .Trashes
32 | .VolumeIcon.icns
33 | .com.apple.timemachine.donotpresent
34 |
35 | # Directories potentially created on remote AFP share
36 | .AppleDB
37 | .AppleDesktop
38 | Network Trash Folder
39 | Temporary Items
40 | .apdisk
41 |
42 | # Byte-compiled / optimized / DLL files
43 | __pycache__/
44 | *.py[cod]
45 | *$py.class
46 |
47 | # C extensions
48 | *.so
49 |
50 | # Distribution / packaging
51 | .Python
52 | build/
53 | develop-eggs/
54 | dist/
55 | downloads/
56 | eggs/
57 | .eggs/
58 | lib64/
59 | parts/
60 | sdist/
61 | var/
62 | wheels/
63 | *.egg-info/
64 | .installed.cfg
65 | *.egg
66 |
67 | # PyInstaller
68 | *.manifest
69 | *.spec
70 |
71 | # Installer logs
72 | pip-log.txt
73 | pip-delete-this-directory.txt
74 |
75 | # Unit test / coverage reports
76 | htmlcov/
77 | .tox/
78 | .coverage
79 | .coverage.*
80 | .cache
81 | nosetests.xml
82 | coverage.xml
83 |
84 | # Flask stuff:
85 | instance/
86 | .webassets-cache
87 |
88 | # PyBuilder
89 | target/
90 |
91 | # Jupyter Notebook
92 | .ipynb_checkpoints
93 |
94 | # pyenv
95 | .python-version
96 |
97 | # Environments
98 | .env
99 | .venv
100 | env/
101 | venv/
102 | ENV/
103 |
104 | # documentation
105 | docs
106 |
--------------------------------------------------------------------------------
/.github/workflows/docker.yml:
--------------------------------------------------------------------------------
1 | name: OctoBot-Script - Docker
2 | on:
3 | push:
4 | branches:
5 | - "master"
6 | tags:
7 | - "*"
8 | pull_request:
9 |
10 | jobs:
11 | lint:
12 | name: ubuntu-latest - Docker - lint
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v3
16 |
17 | - name: Run hadolint
18 | uses: reviewdog/action-hadolint@v1
19 | with:
20 | github_token: ${{ secrets.github_token }}
21 | hadolint_ignore: DL3013 DL3008
22 |
23 | build:
24 | needs: lint
25 | name: ubuntu-latest - Docker - build & test & push
26 | runs-on: ubuntu-latest
27 |
28 | steps:
29 | - uses: actions/checkout@v3
30 |
31 | - name: Set Environment Variables
32 | run: |
33 | OWNER="$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]' | tr -d '-')"
34 | IMG=octobot-script
35 | echo "VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV
36 | echo "IMAGE=${OWNER}/${IMG}" >> $GITHUB_ENV
37 | echo "LATEST=latest" >> $GITHUB_ENV
38 |
39 | - name: Set up QEMU
40 | id: qemu-setup
41 | uses: docker/setup-qemu-action@master
42 | with:
43 | platforms: all
44 |
45 | - name: Print available platforms
46 | run: echo ${{ steps.qemu.outputs.platforms }}
47 |
48 | - name: Set up Docker Buildx
49 | id: buildx
50 | uses: docker/setup-buildx-action@master
51 | with:
52 | driver: docker-container
53 | use: true
54 |
55 | - name: Cache Docker layers
56 | uses: actions/cache@v4
57 | with:
58 | path: /tmp/.buildx-cache
59 | key: ${{ runner.os }}-buildx-${{ github.sha }}
60 | restore-keys: |
61 | ${{ runner.os }}-buildx-
62 |
63 | - name: Login to DockerHub
64 | if: github.event_name == 'push'
65 | uses: docker/login-action@v1
66 | with:
67 | username: ${{ secrets.DOCKERHUB_USERNAME }}
68 | password: ${{ secrets.DOCKERHUB_TOKEN }}
69 |
70 | - name: Build and push latest
71 | if: github.event_name == 'push' && !startsWith(github.ref, 'refs/tags') && github.ref == 'refs/heads/master'
72 | uses: docker/build-push-action@master
73 | with:
74 | context: .
75 | builder: ${{ steps.buildx.outputs.name }}
76 | platforms: linux/amd64,linux/arm64
77 | push: true
78 | tags: ${{ env.IMAGE }}:${{ env.LATEST }}
79 | build-args: |
80 | TENTACLES_URL_TAG=${{ env.LATEST }}
81 | cache-from: type=local,src=/tmp/.buildx-cache
82 | cache-to: type=local,dest=/tmp/.buildx-cache
83 |
84 | - name: Build and push on tag
85 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
86 | uses: docker/build-push-action@master
87 | with:
88 | context: .
89 | file: ./Dockerfile
90 | builder: ${{ steps.buildx.outputs.name }}
91 | platforms: linux/amd64,linux/arm64
92 | push: true
93 | tags: |
94 | ${{ env.IMAGE }}:${{ env.LATEST }}
95 | ${{ env.IMAGE }}:${{ env.VERSION }}
96 | cache-from: type=local,src=/tmp/.buildx-cache
97 | cache-to: type=local,dest=/tmp/.buildx-cache
98 |
99 | notify:
100 | if: ${{ failure() }}
101 | needs:
102 | - lint
103 | - build
104 | uses: Drakkar-Software/.github/.github/workflows/failure_notify_workflow.yml@master
105 | secrets:
106 | DISCORD_GITHUB_WEBHOOK: ${{ secrets.DISCORD_GITHUB_WEBHOOK }}
107 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: OctoBot-Script - 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_script
15 |
16 | tests:
17 | name: ${{ matrix.os }} - Python - ${{ matrix.type }} - tests
18 | runs-on: ${{ matrix.os }}
19 | strategy:
20 | matrix:
21 | os: [ macos-13, windows-latest, ubuntu-latest ]
22 | type: [ sources ]
23 |
24 | steps:
25 | - uses: actions/checkout@v3
26 | - name: Set up Python 3.10
27 | uses: actions/setup-python@v4
28 | with:
29 | python-version: 3.10.x
30 | architecture: x64
31 |
32 | - name: Install dependencies
33 | run: pip install --prefer-binary -r dev_requirements.txt -r requirements.txt
34 |
35 | - name: Install tentacles from cli
36 | run: python start.py install_tentacles
37 |
38 | - name: Pytests
39 | run: pytest --cov=. --cov-config=.coveragerc --durations=0 -rw tests
40 |
41 | - name: Publish coverage
42 | if: matrix.type == 'sources' && github.event_name == 'push'
43 | run: coveralls
44 | continue-on-error: true
45 | env:
46 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
47 |
48 | publish:
49 | needs:
50 | - lint
51 | - tests
52 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
53 | name: Source distribution - Python - deploy
54 | runs-on: ubuntu-latest
55 | steps:
56 | - uses: actions/checkout@v3
57 | - name: Set up Python
58 | uses: actions/setup-python@v4
59 | with:
60 | python-version: 3.10.x
61 | architecture: x64
62 |
63 | - name: Install dependencies
64 | run: pip install --prefer-binary -r dev_requirements.txt -r requirements.txt
65 |
66 | - name: Build sdist
67 | run: python setup.py sdist
68 |
69 | - name: Publish package
70 | run: |
71 | python -m twine upload --repository-url ${{ secrets.PYPI_OFFICIAL_UPLOAD_URL }} -u __token__ -p ${{ secrets.PYPI_TOKEN }} --skip-existing dist/*
72 |
73 | notify:
74 | if: ${{ failure() }}
75 | needs:
76 | - lint
77 | - tests
78 | - publish
79 | uses: Drakkar-Software/.github/.github/workflows/failure_notify_workflow.yml@master
80 | secrets:
81 | DISCORD_GITHUB_WEBHOOK: ${{ secrets.DISCORD_GITHUB_WEBHOOK }}
82 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 | *.orig
6 |
7 | # C extensions
8 | *.so
9 |
10 | # Distribution / packaging
11 | .Python
12 | .pytest_cache/
13 | env/
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | wheels/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | # *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | .hypothesis/
50 |
51 | # Translations
52 | *.mo
53 | *.pot
54 |
55 | # Django stuff:
56 | *.log
57 | local_settings.py
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 | # virtualenv
85 | .venv
86 | venv/
87 | ENV/
88 |
89 | # Spyder project settings
90 | .spyderproject
91 | .spyproject
92 |
93 | # Rope project settings
94 | .ropeproject
95 |
96 | # mkdocs documentation
97 | /site
98 |
99 | # mypy
100 | .mypy_cache/
101 |
102 | # IDE
103 | .vscode/
104 | .idea
105 | .gitpod.yml
106 |
107 | # Tentacles manager temporary files
108 | octobot/creator_temp/
109 | creator_temp/
110 |
111 | # Data
112 | backtesting/collector/data/
113 | backtesting/data/
114 | report.html
115 |
116 | # Tentacles
117 | tentacles
118 | downloaded_temp_tentacles
119 |
120 | # User config
121 | user/
122 | temp_config.json
123 |
124 | *.csv
125 | *.ods
126 | *.c
127 | *.h
128 |
129 | # OctoBot logs
130 | logs
131 |
132 | # Debug
133 | cython_debug/
134 |
135 | # dev env
136 | .env
137 |
138 | *.zip
139 |
140 | # tensorboard
141 | tensorboard_logs
142 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [0.0.25] - 2025-06-06
8 | ### Updated
9 | - OctoBot to 2.0.11
10 |
11 | ## [0.0.24] - 2025-05-25
12 | ### Updated
13 | - OctoBot to 2.0.10
14 |
15 | ## [0.0.23] - 2025-03-10
16 | ### Updated
17 | - OctoBot to 2.0.9
18 |
19 | ## [0.0.22] - 2025-01-29
20 | ### Updated
21 | - OctoBot to 2.0.8
22 |
23 | ## [0.0.21] - 2024-10-28
24 | ### Updated
25 | - OctoBot to 2.0.7
26 | ### Fixed
27 | - Indicators plotting
28 |
29 | ## [0.0.20] - 2024-10-11
30 | ### Updated
31 | - OctoBot to 2.0.6
32 |
33 | ## [0.0.19] - 2024-10-01
34 | ### Updated
35 | - OctoBot to 2.0.5
36 |
37 | ## [0.0.18] - 2024-07-10
38 | ### Updated
39 | - OctoBot to 2.0.1
40 |
41 | ## [0.0.17] - 2024-05-01
42 | ### Updated
43 | - OctoBot to 1.0.10
44 |
45 | ## [0.0.16] - 2023-01-14
46 | ### Updated
47 | - OctoBot to 1.0.6
48 |
49 | ## [0.0.15] - 2023-12-15
50 | ### Updated
51 | - OctoBot to 1.0.4
52 |
53 | ## [0.0.14] - 2023-10-30
54 | ### Updated
55 | - OctoBot to 1.0.2
56 |
57 | ## [0.0.13] - 2023-10-09
58 | ### Updated
59 | - Renamed from OctoBot Pro to OctoBot Script
60 |
61 | ## [0.0.12] - 2023-09-27
62 | ### Added
63 | - Dockerfile
64 | - Docker image on dockerhub
65 | ### Update
66 | - Update to OctoBot 1.0.0
67 |
68 | ## [0.0.11] - 2023-09-23
69 | ### Update
70 | - Update to OctoBot 0.4.54
71 |
72 | ## [0.0.10] - 2022-05-13
73 | ### Update
74 | - Added python 3.9 and 3.10 support
75 | - Update to OctoBot 0.4.50
76 |
77 | ## [0.0.9] - 2022-05-02
78 | ### Update
79 | - Update to OctoBot 0.4.49
80 |
81 | ## [0.0.8] - 2022-03-24
82 | ### Update
83 | - Update to OctoBot 0.4.45
84 |
85 | ## [0.0.7] - 2022-03-07
86 | ### Update
87 | - Update to OctoBot 0.4.41
88 |
89 | ## [0.0.6] - 2022-01-21
90 | ### Fix
91 | - Typeerror and report issues
92 |
93 | ## [0.0.5] - 2022-01-14
94 | ### Fix
95 | - Installation: remove cryptofeed requirement in OctoBot
96 |
97 | ## [0.0.4] - 2022-30-12
98 | ### Added
99 | - Report generation time
100 |
101 | ## [0.0.3] - 2022-29-12
102 | ### Fixed
103 | - Install
104 |
105 | ## [0.0.2] - 2022-29-12
106 | ### Updated
107 | - Install method
108 |
109 | ## [0.0.1] - 2022-10-12
110 | ### Added
111 | - OctoBot Pro alpha version
112 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.10-slim-buster AS base
2 |
3 | WORKDIR /app
4 |
5 | # requires git to install requirements with git+https
6 | RUN apt-get update \
7 | && apt-get install -y --no-install-recommends build-essential git gcc binutils
8 |
9 | COPY . .
10 |
11 | RUN pip3 install --no-cache-dir -U setuptools wheel pip \
12 | && pip3 install --no-cache-dir -r requirements.txt \
13 | && python3 setup.py install
14 |
15 | ENTRYPOINT ["bash"]
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include octobot_script/config *.json *.ini
2 | recursive-include octobot_script/resources *.js *.css *.html
3 |
4 | include README.md
5 | include LICENSE
6 | include CHANGELOG.md
7 | include requirements.txt
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OctoBot-Script [0.0.25](https://github.com/Drakkar-Software/OctoBot-Script/tree/master/CHANGELOG.md)
2 | [](https://pypi.python.org/pypi/octobot-script/)
3 | [](https://pepy.tech/project/octobot-script)
4 | [](https://hub.docker.com/r/drakkarsoftware/octobot-script)
5 | [](https://github.com/Drakkar-Software/OctoBot-Script/actions/workflows/main.yml)
6 |
7 | ## OctoBot-Script Community
8 | [](https://t.me/+366CLLZ2NC0xMjFk)
9 | [](https://discord.com/invite/vHkcb8W)
10 | [](https://twitter.com/DrakkarsOctoBot)
11 |
12 |
13 | ## OctoBot Script is the Quant framework by OctoBot
14 |
15 | > OctoBot Script is in alpha version
16 |
17 | Documentation available at [octobot.cloud/en/guides/octobot-script](https://www.octobot.cloud/en/guides/octobot-script?utm_source=octobot&utm_medium=dk&utm_campaign=regular_open_source_content&utm_content=octobot_script_readme)
18 |
19 |
20 | ## Install OctoBot Script from pip
21 |
22 | > OctoBot Script requires **Python 3.10**
23 |
24 | ``` {.sourceCode .bash}
25 | python3 -m pip install OctoBot wheel appdirs==1.4.4
26 | python3 -m pip install octobot-script
27 | ```
28 |
29 | ## Example of a backtesting script
30 |
31 | ### Script
32 | ``` python
33 | import asyncio
34 | import tulipy # Can be any TA library.
35 | import octobot_script as obs
36 |
37 |
38 | async def rsi_test():
39 | async def strategy(ctx):
40 | # Will be called at each candle.
41 | if run_data["entries"] is None:
42 | # Compute entries only once per backtest.
43 | closes = await obs.Close(ctx, max_history=True)
44 | times = await obs.Time(ctx, max_history=True, use_close_time=True)
45 | rsi_v = tulipy.rsi(closes, period=ctx.tentacle.trading_config["period"])
46 | delta = len(closes) - len(rsi_v)
47 | # Populate entries with timestamps of candles where RSI is
48 | # bellow the "rsi_value_buy_threshold" configuration.
49 | run_data["entries"] = {
50 | times[index + delta]
51 | for index, rsi_val in enumerate(rsi_v)
52 | if rsi_val < ctx.tentacle.trading_config["rsi_value_buy_threshold"]
53 | }
54 | await obs.plot_indicator(ctx, "RSI", times[delta:], rsi_v, run_data["entries"])
55 | if obs.current_live_time(ctx) in run_data["entries"]:
56 | # Uses pre-computed entries times to enter positions when relevant.
57 | # Also, instantly set take profits and stop losses.
58 | # Position exists could also be set separately.
59 | await obs.market(ctx, "buy", amount="10%", stop_loss_offset="-15%", take_profit_offset="25%")
60 |
61 | # Configuration that will be passed to each run.
62 | # It will be accessible under "ctx.tentacle.trading_config".
63 | config = {
64 | "period": 10,
65 | "rsi_value_buy_threshold": 28,
66 | }
67 |
68 | # Read and cache candle data to make subsequent backtesting runs faster.
69 | data = await obs.get_data("BTC/USDT", "1d", start_timestamp=1505606400)
70 | run_data = {
71 | "entries": None,
72 | }
73 | # Run a backtest using the above data, strategy and configuration.
74 | res = await obs.run(data, strategy, config)
75 | print(res.describe())
76 | # Generate and open report including indicators plots
77 | await res.plot(show=True)
78 | # Stop data to release local databases.
79 | await data.stop()
80 |
81 |
82 | # Call the execution of the script inside "asyncio.run" as
83 | # OctoBot-Script runs using the python asyncio framework.
84 | asyncio.run(rsi_test())
85 | ```
86 |
87 | ### Generated report
88 | 
89 |
90 | ### Join the community
91 | We recently created a telegram channel dedicated to OctoBot Script.
92 |
93 | [](https://t.me/+366CLLZ2NC0xMjFk)
94 |
--------------------------------------------------------------------------------
/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Table of contents
2 |
3 | * [Getting Started](README.md)
4 | * [OctoBot](https://www.octobot.cloud)
5 |
6 | ## Trading strategies
7 |
8 | * [Strategy basis](docs/strategies/basis.md)
9 |
10 | ## Trading keywords
11 |
12 | * [Creating orders](docs/trading/orders.md)
13 |
14 | ## Plotting
15 |
16 | * [Plotting](docs/plotting/basis.md)
17 |
18 | ## Trading data
19 |
20 | * [Fetching data](docs/data/fetching_data.md)
21 |
--------------------------------------------------------------------------------
/ai-example.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import tulipy
3 | import gymnasium as gym
4 | from datetime import datetime, timedelta
5 | import numpy as np
6 | import time
7 | from tensorflow.keras.callbacks import TensorBoard
8 | import argparse
9 |
10 | import octobot_commons.symbols as symbols
11 | import octobot_script as obs
12 |
13 | async def basic_evaluation_function(ctx):
14 | closes = await obs.Close(ctx, max_history=True)
15 | open = await obs.Open(ctx, limit=30)
16 | high = await obs.High(ctx, limit=30)
17 | low = await obs.Low(ctx, limit=30)
18 | vol = await obs.Volume(ctx, limit=30)
19 | rsi_v = tulipy.rsi(closes, period=10)
20 | ema_values = tulipy.ema(closes, period=21)
21 |
22 | try:
23 | if (len(rsi_v) > 15 and len(ema_values) > 15):
24 | return np.array([
25 | closes[-10:],
26 | open[-10:],
27 | high[-10:],
28 | low[-10:],
29 | vol[-10:],
30 | rsi_v[-15:],
31 | ema_values[-15:]
32 | ], dtype=np.float32).flatten()
33 | else:
34 | return np.zeros(80, dtype=np.float32)
35 | except ValueError:
36 | return np.zeros(80, dtype=np.float32)
37 |
38 | async def run_strategy(data, env, agent, symbol, time_frame, is_training=False, plot=False):
39 | async def strategy(ctx):
40 | state = None
41 | if not env.env.get_wrapper_attr('is_reset'):
42 | state = await env.reset(options={
43 | 'ctx': ctx
44 | })
45 | else:
46 | state = await env.get_obs(ctx)
47 |
48 | action = agent.act(state)
49 | next_state, reward, done, info = await env.step({
50 | 'ctx': ctx,
51 | 'content': action
52 | })
53 |
54 | if is_training:
55 | agent.remember(state, action, reward, next_state, done)
56 |
57 | # Run a backtest using the above data, strategy and configuration.
58 | res = await obs.run(data, strategy, {}, enable_logs=False)
59 |
60 | if plot:
61 | print(res.describe())
62 | await res.plot(show=True)
63 |
64 | def init_argparse() -> argparse.ArgumentParser:
65 | parser = argparse.ArgumentParser()
66 | parser.add_argument("-ex", "--exchange", type=str, default="binance")
67 | parser.add_argument("-s", "--symbol", type=str, default="BTC/USDT")
68 | parser.add_argument("-tf", "--timeframe", type=str, default="1d")
69 | parser.add_argument("-e", "--episode", type=int, default=1)
70 | parser.add_argument('-b', '--batch_size', type=int, default=32,
71 | help='batch size for experience replay')
72 | parser.add_argument("-t", "--train", action=argparse.BooleanOptionalAction)
73 | parser.add_argument("-p", "--plot", action=argparse.BooleanOptionalAction)
74 | parser.add_argument('-w', '--weights', type=str, help='a trained model weights')
75 | parser.add_argument("-d", "--days", type=int, default=365)
76 | parser.add_argument("-ev", "--evaluate", action=argparse.BooleanOptionalAction)
77 | parser.add_argument("-ep", "--epochs", type=int, default=20)
78 | return parser
79 |
80 |
81 |
82 | def main():
83 | parser = init_argparse()
84 | args = parser.parse_args()
85 |
86 | timestamp = time.strftime('%Y%m%d%H%M')
87 | symbol = symbols.parse_symbol(args.symbol)
88 | time_frame = args.timeframe
89 | data = asyncio.run(obs.get_data(symbol.merged_str_symbol(), time_frame, exchange=args.exchange, start_timestamp=int(float(str((datetime.now() - timedelta(days=args.days)).timestamp()))))) # start_timestamp=1505606400
90 |
91 | action_size = 9
92 | gym_env = gym.make(action_size=action_size, id='TradingEnv', name= "test", dynamic_feature_functions=[basic_evaluation_function], traded_symbols=[symbol])
93 | agent = obs.DQNAgent(action_size)
94 |
95 | logdir = "tensorboard_logs/scalars/" + datetime.now().strftime("%Y%m%d-%H%M%S")
96 | tensorboard_callback = TensorBoard(log_dir=logdir, histogram_freq=1, write_images=False, batch_size=args.batch_size)
97 |
98 | if args.weights:
99 | print(f"Loading model {args.weights}...")
100 |
101 | # load trained weights
102 | agent.load(args.weights)
103 | elif not args.train:
104 | print("weights has not be provided when using model!")
105 | return
106 |
107 | for episode in range(args.episode):
108 | print(f"Starting episode {episode}...")
109 | asyncio.run(run_strategy(data, gym_env, agent, symbol, time_frame, is_training=args.train, plot=args.plot))
110 |
111 | if args.train and len(agent.memory) > args.batch_size:
112 | print("Starting replay...")
113 | score = agent.replay(args.batch_size, args.epochs, args.evaluate, tensorboard_callback)
114 | if args.evaluate:
115 | print(f"Score = {score}")
116 |
117 | if args.train and (episode + 1) % 10 == 0: # checkpoint weights
118 | print("Saving...")
119 | agent.save(f"weights/{timestamp}-dqn.h5")
120 |
121 | if args.train:
122 | agent.save(f"weights/{timestamp}-final-dqn.h5")
123 |
124 | asyncio.run(data.stop())
125 |
126 | main()
127 |
--------------------------------------------------------------------------------
/dev_requirements.txt:
--------------------------------------------------------------------------------
1 | pytest>=7.1
2 | pytest-asyncio>=0.19
3 | pytest-pep8
4 | pytest-cov
5 | pytest-xdist
6 |
7 | mock>=4.0.2
8 |
9 |
10 | coverage
11 | coveralls
12 |
13 | twine
14 | pip
15 | setuptools
16 | wheel
17 |
18 | pur
19 |
20 | pylint
21 |
--------------------------------------------------------------------------------
/docs/data/fetching_data.md:
--------------------------------------------------------------------------------
1 | # Fetching trading data
2 |
3 | In order to run a backtest, OctoBot-Script requires historical
4 | trading data, which is at least candles history.
5 |
6 | ## Fetching new data
7 | When using OctoBot-Script, historical data can be fetched using:
8 | `await op.get_data(symbol, time frame)`
9 |
10 | Where:
11 | - symbol: the trading symbol to fetch data from. It can also be a list of symbols
12 | - time frame: the time frame to fetch (1h, 4h, 1d, etc). It can also be a list of time frames
13 |
14 | Optional arguments:
15 | - start_timestamp: the unix timestamp to start fetching data from. Use [this converter](https://www.epochconverter.com/) if you are unsure what you should use.
16 | - exchange: the exchange to fetch data from. Default is "binance"
17 | - exchange_type: the exchange trading type to fetch data from. Default is "spot", "future" is also possible on supported exchanges
18 | ``` python
19 | data = await op.get_data("BTC/USDT", "1d", start_timestamp=1505606400)
20 | ```
21 |
22 | ## Re-using fetched data
23 | Calling `data = await op.get_data` will save the downloaded data into the `backtesting/data` local folder.
24 | If you want to speedup subsequent calls, you can provide the `data_file` optional argument to read
25 | data from this file instead of downloading historical data. This also makes it possible to run a
26 | script while being offline.
27 |
28 | You can get the name of the downloaded backtesting file by accessing
29 | `data.data_files[0]`
30 |
31 | ``` python
32 | data = await op.get_data("BTC/USDT", "1d", start_timestamp=1505606400)
33 | # print the name of the downloaded data file
34 | print(data.data_files[0])
35 | ```
36 |
37 | ``` python
38 | datafile = "ExchangeHistoryDataCollector_1671754854.5234916.data"
39 | # will not download historical data as a local data_file is provided
40 | data = await op.get_data("BTC/USDT", "1d", start_timestamp=1505606400, data_file=datafile)
41 | ```
42 |
--------------------------------------------------------------------------------
/docs/plotting/basis.md:
--------------------------------------------------------------------------------
1 | # Plotting
2 |
3 | ## Plotting indicators
4 | Indicators and associated signals can be easily plotted using the
5 | `plot_indicator(ctx, name, x, y, signals)` keyword.
6 |
7 | Where:
8 | - `name`: name of the indicator on the chart
9 | - `x`: values to use for the x axis
10 | - `y`: values to use for the y axis
11 | - `signal`: (optional) x values for which a signal is fired
12 |
13 | Example where the goal is to plot the value of the rsi indicator from
14 | the [example script](../../#script).
15 | ``` python
16 | await op.plot_indicator(ctx, "RSI", time_values, indicator_values, signal_times)
17 | ```
18 |
19 | ## Plotting anything
20 |
21 | Anything can be plotted using the `plot(ctx, name, ...)` keyword.
22 | The plot arguments are converted into [plotly](https://plotly.com/javascript/) charts parameters.
23 |
24 | Where:
25 | - `name`: name of the indicator on the chart
26 |
27 | Optional arguments:
28 | - `x`: values to use for the x axis
29 | - `y`: values to use for the y axis
30 | - `z`: values to use for the z axis
31 | - `text`: point labels
32 | - `mode`: plotly mode ("lines", "markers", "lines+markers", "lines+markers+text", "none")
33 | - `chart`: "main-chart" or "sub-chart" (default is "sub-chart")
34 | - `own_yaxis`: when True, uses an independent y axis for this plot (default is False)
35 | - `color`: color the of plot
36 | - `open`: open values for a candlestick chart
37 | - `high`: high values for a candlestick chart
38 | - `low`: low values for a candlestick chart
39 | - `close`: close values for a candlestick chart
40 | - `volume`: volume values for a candlestick chart
41 | - `low`: low values for a candlestick chart
42 |
43 | Example:
44 | ``` python
45 | await op.plot(ctx, "RSI", x=time_values, y=indicator_values, mode="markers")
46 | ```
47 |
--------------------------------------------------------------------------------
/docs/strategies/basis.md:
--------------------------------------------------------------------------------
1 | # OctoBot-Script strategies
2 |
3 | On OctoBot-Script, a trading strategy is a python async function that will be called at new price data.
4 | ``` python
5 | async def strategy(ctx):
6 | # your strategy content
7 | ```
8 |
9 | In most cases, a strategy will:
10 | 1. Read price data
11 | 2. Use technical evaluators or statistics
12 | 3. Decide to take (or not take) action depending on its configuration
13 | 4. Create / cancel or edit orders (see [Creating orders](../../docs/trading/orders.md))
14 |
15 | As OctoBot-Script strategies are meant for backtesting, it is possible to create a strategy in 2 ways:
16 | ## Pre-computed strategies
17 | Pre-computed are only possible in backtesting: since the data is already known, when dealing with technical
18 | evaluator based strategies, it is possible to compute the values of the evaluators for the whole backtest at once.
19 | This approach is faster than iterative strategies as evaluators call only called once.
20 |
21 | Warning: when writing a pre-computed strategy, always make sure to associate the evaluator values to the
22 | right time otherwise you might be reading data from the past of the future when running the strategy.
23 |
24 | ``` python
25 | config = {
26 | "period": 10,
27 | "rsi_value_buy_threshold": 28,
28 | }
29 | run_data = {
30 | "entries": None,
31 | }
32 | async def strategy(ctx):
33 | if run_data["entries"] is None:
34 | # 1. Read price data
35 | closes = await op.Close(ctx, max_history=True)
36 | times = await op.Time(ctx, max_history=True, use_close_time=True)
37 | # 2. Use technical evaluators or statistics
38 | rsi_v = tulipy.rsi(closes, period=ctx.tentacle.trading_config["period"])
39 | delta = len(closes) - len(rsi_v)
40 | # 3. Decide to take (or not take) action depending on its configuration
41 | run_data["entries"] = {
42 | times[index + delta]
43 | for index, rsi_val in enumerate(rsi_v)
44 | if rsi_val < ctx.tentacle.trading_config["rsi_value_buy_threshold"]
45 | }
46 | await op.plot_indicator(ctx, "RSI", times[delta:], rsi_v, run_data["entries"])
47 | if op.current_live_time(ctx) in run_data["entries"]:
48 | # 4. Create / cancel or edit orders
49 | await op.market(ctx, "buy", amount="10%", stop_loss_offset="-15%", take_profit_offset="25%")
50 | ```
51 | This pre-computed strategy computes entries using the RSI: times of favorable entries are stored into
52 | `run_data["entries"]` which is defined outside on the `strategy` function in order to keep its values
53 | throughout iterations.
54 |
55 | Please note the `max_history=True` in `op.Close` and `op.Time` keywords. This is allowing to select
56 | data using the whole run available data and only call `tulipy.rsi` once and populate `run_data["entries"]`
57 | only once.
58 |
59 | In each subsequent call, `run_data["entries"] is None` will be `True` and only the last 2 lines of
60 | the strategy will be executed.
61 |
62 | ## Iterative strategies
63 | ``` python
64 | config = {
65 | "period": 10,
66 | "rsi_value_buy_threshold": 28,
67 | }
68 | async def strategy(ctx):
69 | # 1. Read price data
70 | close = await op.Close(ctx)
71 | if len(close) <= ctx.tentacle.trading_config["period"]:
72 | # not enough data to compute RSI
73 | return
74 | # 2. Use technical evaluators or statistics
75 | rsi_v = tulipy.rsi(close, period=ctx.tentacle.trading_config["period"])
76 | # 3. Decide to take (or not take) action depending on its configuration
77 | if rsi_v[-1] < ctx.tentacle.trading_config["rsi_value_buy_threshold"]:
78 | # 4. Create / cancel or edit orders
79 | await op.market(ctx, "buy", amount="10%", stop_loss_offset="-15%", take_profit_offset="25%")
80 | ```
81 | This iterative strategy is similar to the above pre-computed strategy except that it is evaluating the RSI
82 | at each candle to know if an entry should be created.
83 |
84 | This type of strategy is simpler to create than a pre-computed strategy and can be used in
85 | OctoBot live trading.
86 |
87 | ## Running a strategy
88 |
89 | When running a backtest, a strategy should be referenced alongside:
90 | - The [data it should be run on](../../docs/data/fetching_data.md) using `op.run`:
91 | - Its configuration (a dict in above examples, it could be anything)
92 |
93 | ``` python
94 | res = await op.run(data, strategy, config)
95 | ```
96 |
97 | Have a look [here](../../#script) for a full example of
98 | how to run a strategy within a python script.
99 |
--------------------------------------------------------------------------------
/docs/trading/orders.md:
--------------------------------------------------------------------------------
1 | # Orders
2 |
3 | Orders can be created using the following keywords:
4 | - `market`
5 | - `limit`
6 | - `stop_loss`
7 | - `trailing_market`
8 |
9 | ## Amount
10 | Each order accept the following optional arguments:
11 | - `amount`: for spot and futures trading
12 | - `target_position`: futures trading only
13 |
14 | To specify the amount per order, use the following syntax:
15 | - `0.1` to trade 0.1 BTC on BTC/USD
16 | - `2%` to trade 2% of the total portfolio value
17 | - `12%a` to trade 12% of the available holdings
18 |
19 | ``` python
20 | # create a buy market order using 10% of the total portfolio
21 | await op.market(ctx, "buy", amount="10%")
22 | ```
23 |
24 | ## Price
25 | Orders set their price using the `offset` argument.
26 |
27 | To specify the order price, use the following syntax:
28 | - `10` to set the price 10 USD above the current BTC/USD market price
29 | - `2%` to set the price 2% USD above the current BTC/USD market price
30 | - `@15555` to set the price at exactly 15555 USD regardless of the current BTC/USD market price
31 |
32 | ``` python
33 | # create a buy limit order of 0.2 units (BTC when trading BTC/USD)
34 | # with a price at 1% bellow the current price
35 | await op.limit(ctx, "buy", amount="0.2", offset="-1%")
36 | ```
37 |
38 | Note: market orders do not accept the `offset` argument.
39 |
40 | ## Automated take profit and stop losses
41 | When creating orders, it is possible to automate the associated
42 | stop loss and / or take profits. When doing to, the associated take profit/stop loss will have
43 | the same amount as the initial order.
44 |
45 | Their price can be set according to the same rules as the initial order price
46 | (the `offset` argument) using the following optional argument:
47 | - `stop_loss_offset`: automate a stop loss creation when the initial order is filled and set the stop loss price
48 | - `take_profit_offset`: automate a take profit creation when the initial order is filled and set the take profit price
49 |
50 | ``` python
51 | # create a buy limit order of 0.2 units (BTC when trading BTC/USD) with:
52 | # - price at 1% bellow the current price
53 | # - stop loss at 10% loss
54 | # - take profit at 15% profit
55 | await op.limit(ctx, "buy", amount="0.2", offset="-1%", stop_loss_offset="-10%", take_profit_offset="15%")
56 | ```
57 |
58 | {% hint style="info" %}
59 | When using both `stop_loss_offset` and `take_profit_offset`, two orders will be created after the initial order fill.
60 | Those two orders will be grouped together, meaning that if one is cancelled or filled, the other will be cancelled.
61 | {% endhint %}
62 |
--------------------------------------------------------------------------------
/example.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import tulipy # Can be any TA library.
3 | import octobot_script as obs
4 |
5 |
6 | async def rsi_test():
7 | async def strategy(ctx):
8 | # Will be called at each candle.
9 | if run_data["entries"] is None:
10 | # Compute entries only once per backtest.
11 | closes = await obs.Close(ctx, max_history=True)
12 | times = await obs.Time(ctx, max_history=True, use_close_time=True)
13 | rsi_v = tulipy.rsi(closes, period=ctx.tentacle.trading_config["period"])
14 | delta = len(closes) - len(rsi_v)
15 | # Populate entries with timestamps of candles where RSI is
16 | # bellow the "rsi_value_buy_threshold" configuration.
17 | run_data["entries"] = {
18 | times[index + delta]
19 | for index, rsi_val in enumerate(rsi_v)
20 | if rsi_val < ctx.tentacle.trading_config["rsi_value_buy_threshold"]
21 | }
22 | await obs.plot_indicator(ctx, "RSI", times[delta:], rsi_v, run_data["entries"])
23 | if obs.current_live_time(ctx) in run_data["entries"]:
24 | # Uses pre-computed entries times to enter positions when relevant.
25 | # Also, instantly set take profits and stop losses.
26 | # Position exists could also be set separately.
27 | await obs.market(ctx, "buy", amount="10%", stop_loss_offset="-15%", take_profit_offset="25%")
28 |
29 | # Configuration that will be passed to each run.
30 | # It will be accessible under "ctx.tentacle.trading_config".
31 | config = {
32 | "period": 10,
33 | "rsi_value_buy_threshold": 28,
34 | }
35 |
36 | # Read and cache candle data to make subsequent backtesting runs faster.
37 | data = await obs.get_data("BTC/USDT", "1d", start_timestamp=1505606400)
38 | run_data = {
39 | "entries": None,
40 | }
41 | # Run a backtest using the above data, strategy and configuration.
42 | res = await obs.run(data, strategy, config)
43 | print(res.describe())
44 | # Generate and open report including indicators plots
45 | await res.plot(show=True)
46 | # Stop data to release local databases.
47 | await data.stop()
48 |
49 |
50 | # Call the execution of the script inside "asyncio.run" as
51 | # OctoBot-Script runs using the python asyncio framework.
52 | asyncio.run(rsi_test())
53 |
--------------------------------------------------------------------------------
/octobot_script/__init__.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | PROJECT_NAME = "OctoBot-Script"
18 | AUTHOR = "Drakkar-Software"
19 | VERSION = "0.0.25" # major.minor.revision => don't forget to also update the setup.py version
20 |
21 |
22 | def _use_module_local_tentacles():
23 | import sys
24 | import os
25 | import appdirs
26 | if os.getenv("USE_CUSTOM_TENTACLES", "").lower() == "true":
27 | # do not use octobot_script/imports tentacles
28 | # WARNING: in this case, all the required tentacles imports still have to work
29 | # and therefore be bound to another tentacles folder
30 | return
31 | # import tentacles from user-appdirs/imports directory
32 | dirs = appdirs.AppDirs(PROJECT_NAME, AUTHOR, VERSION)
33 | internal_import_path = os.path.join(dirs.user_data_dir, "imports")
34 | sys.path.insert(0, internal_import_path)
35 |
36 |
37 | # run this before any other code as only octobot_script module-local tentacles should be used
38 | _use_module_local_tentacles()
39 |
40 | try:
41 | # import tentacles from octobot_script/imports directory after "_use_local_tentacles()" call
42 | from tentacles.Meta.Keywords import *
43 | # populate tentacles config helpers
44 | import octobot_tentacles_manager.loaders as loaders
45 | import octobot_script.internal.octobot_mocks as octobot_mocks
46 | loaders.reload_tentacle_by_tentacle_class(
47 | tentacles_path=octobot_mocks.get_imported_tentacles_path()
48 | )
49 | # do not expose those when importing this file
50 | loaders = octobot_mocks = None
51 |
52 | except ImportError:
53 | # tentacles not available during first install
54 | pass
55 |
56 | from octobot_script.constants import *
57 | from octobot_script.api import *
58 | from octobot_script.model import *
59 | from octobot_script.ai import *
60 |
--------------------------------------------------------------------------------
/octobot_script/ai/__init__.py:
--------------------------------------------------------------------------------
1 | try:
2 | from octobot_script.ai.environments import *
3 | from octobot_script.ai.models import *
4 | from octobot_script.ai.agents import *
5 |
6 | from gymnasium.envs.registration import register
7 |
8 | register(
9 | id='TradingEnv',
10 | entry_point='octobot_script.ai.environments:TradingEnv',
11 | disable_env_checker = True
12 | )
13 | except ImportError:
14 | pass
15 |
--------------------------------------------------------------------------------
/octobot_script/ai/agents.py:
--------------------------------------------------------------------------------
1 | from collections import deque
2 | import random
3 | import numpy as np
4 |
5 | from octobot_script.ai.models import mlp
6 |
7 | class DQNAgent:
8 | def __init__(self, action_size):
9 | self.action_size = action_size
10 | self.memory = deque(maxlen=2000)
11 | self.gamma = 0.95 # discount rate
12 | self.epsilon = 1.0 # exploration rate
13 | self.epsilon_min = 0.01
14 | self.epsilon_decay = 0.995
15 | self.model = mlp(action_size)
16 |
17 | def remember(self, state, action, reward, next_state, done):
18 | self.memory.append((state, action, reward, next_state, done))
19 |
20 | def act(self, state):
21 | if np.random.rand() <= self.epsilon:
22 | return random.randrange(self.action_size)
23 | act_values = self.model.predict(state)
24 | return np.argmax(act_values[0]) # returns action
25 |
26 | def replay(self, batch_size=32, epochs=1, evaluate=False, tensorboard_callback=None):
27 | # pylint: disable=unsubscriptable-object
28 | """ vectorized implementation; 30x speed up compared with for loop """
29 | minibatch = random.sample(self.memory, batch_size)
30 |
31 | states = np.array([tup[0][0] for tup in minibatch])
32 | actions = np.array([tup[1] for tup in minibatch])
33 | rewards = np.array([tup[2] for tup in minibatch])
34 | next_states = np.array([tup[3][0] for tup in minibatch])
35 | done = np.array([tup[4] for tup in minibatch])
36 |
37 | # Q(s', a)
38 | target = rewards + self.gamma * np.amax(self.model.predict(next_states), axis=1)
39 | # end state target is reward itself (no lookahead)
40 | target[done] = rewards[done]
41 |
42 | # Q(s, a)
43 | target_f = self.model.predict(states)
44 | # make the agent to approximately map the current state to future discounted reward
45 | target_f[range(batch_size), actions] = target
46 |
47 | self.model.fit(states, target_f, batch_size=batch_size, epochs=epochs, verbose=0, callbacks=[tensorboard_callback])
48 |
49 | if self.epsilon > self.epsilon_min:
50 | self.epsilon *= self.epsilon_decay
51 |
52 | if evaluate:
53 | return self.model.evaluate(states, target_f, batch_size=32)
54 | return 0
55 |
56 | def load(self, name):
57 | self.model.load_weights(name)
58 |
59 | def save(self, name):
60 | self.model.save_weights(name)
--------------------------------------------------------------------------------
/octobot_script/ai/environments.py:
--------------------------------------------------------------------------------
1 | # pylint: disable=maybe-no-member
2 | import gymnasium as gym
3 | from gymnasium import spaces
4 | import numpy as np
5 |
6 | import octobot_script as obs
7 | import octobot_trading.errors as octobot_trading_errors
8 | import octobot_trading.api as trading_api
9 |
10 | def basic_reward_function(current_portfolio_value, previous_portfolio_value, current_profitability, market_profitability, created_orders):
11 | if previous_portfolio_value is None:
12 | return 0
13 | try:
14 | pf_reward = np.log(float(current_portfolio_value) / float(previous_portfolio_value))
15 | prof_reward = np.log(float(current_profitability) / float(market_profitability))
16 | reward = 0 if np.isnan(pf_reward) else pf_reward + 0 if np.isnan(prof_reward) else prof_reward
17 | return reward
18 | except ZeroDivisionError:
19 | return 0
20 |
21 | async def basic_trade_function(ctx, action):
22 | try:
23 | created_orders = []
24 | if action == 0:
25 | # TODO cancel orders
26 | pass
27 | elif action == 1:
28 | created_orders.append(await obs.market(
29 | ctx,
30 | "buy",
31 | amount=f"10%"
32 | ))
33 | elif action == 2:
34 | created_orders.append(await obs.market(
35 | ctx,
36 | "sell",
37 | amount=f"{10}%"
38 | ))
39 | elif action in [3, 4, 5]:
40 | created_orders.append(await obs.limit(
41 | ctx,
42 | "buy",
43 | amount=f"{1 if action == 3 else 10 if action == 4 else 30}%",
44 | offset=f"-{1 if action == 3 else 2 if action == 4 else 3}%",
45 | ))
46 | elif action in [6, 7, 8]:
47 | created_orders.append(await obs.limit(
48 | ctx,
49 | "sell",
50 | amount=f"{1 if action == 6 else 10 if action == 7 else 30}%",
51 | offset=f"{1 if action == 6 else 2 if action == 7 else 3}%",
52 | ))
53 | else:
54 | # Nothing for now
55 | pass
56 | return created_orders
57 | except TypeError:
58 | pass
59 |
60 | # TODO move somewhere else
61 | def get_profitabilities(ctx):
62 | return trading_api.get_profitability_stats(ctx.exchange_manager)
63 |
64 | # TODO move somewhere else
65 | def get_open_orders(ctx):
66 | return [] # TODO
67 |
68 | # TODO move somewhere else
69 | def get_current_portfolio_value(ctx):
70 | return trading_api.get_current_portfolio_value(ctx.exchange_manager)
71 |
72 | # TODO move somewhere else
73 | def get_current_portfolio(ctx):
74 | return trading_api.portfolio.get_portfolio(ctx.exchange_manager)
75 |
76 | def get_flatten_pf(current_portfolio, symbol):
77 | return np.array([float(current_portfolio[symbol.base].available),
78 | float(current_portfolio[symbol.base].total),
79 | float(current_portfolio[symbol.quote].available),
80 | float(current_portfolio[symbol.quote].total)], dtype=np.float32)
81 |
82 | class TradingEnv(gym.Env):
83 | def __init__(self,
84 | action_size=1,
85 | dynamic_feature_functions = [],
86 | reward_function = basic_reward_function,
87 | trade_function = basic_trade_function,
88 | max_episode_duration = 'max',
89 | verbose = 1,
90 | name = "Rl",
91 | traded_symbols=[]
92 | ):
93 | self.max_episode_duration = max_episode_duration
94 | self.name = name
95 | self.verbose = verbose
96 | self.is_reset = False
97 |
98 | self.traded_symbols = traded_symbols
99 | self.static_features = [] # TODO there are computed once before being used in the environement
100 | self.dynamic_feature_functions = dynamic_feature_functions # are computed at each step of the environment
101 | self._nb_features = 79 + len(self.traded_symbols) * 4 + len(self.static_features) + len(self.dynamic_feature_functions)
102 |
103 | self.reward_function = reward_function
104 | self.trade_function = trade_function
105 | self.max_episode_duration = max_episode_duration
106 |
107 | self.action_space = spaces.Discrete(action_size)
108 | self.observation_space = spaces.Box(
109 | -np.inf,
110 | np.inf,
111 | shape = [self._nb_features]
112 | )
113 |
114 | self.log_metrics = []
115 | self._previous_portfolio_value = None
116 |
117 | async def get_obs(self, ctx):
118 | flatten_pf = np.concatenate([get_flatten_pf(get_current_portfolio(ctx), symbol) for symbol in self.traded_symbols])
119 | # TODO open orders
120 | dynamic_obs = []
121 | for dynamic_feature_function in self.dynamic_feature_functions:
122 | dynamic_obs.append(await dynamic_feature_function(ctx))
123 | return np.concatenate([dynamic_obs[0], flatten_pf])
124 |
125 | async def reset(self, seed = None, options = None):
126 | super().reset(seed = seed)
127 | self.is_reset = True
128 | self._step = 0
129 | self._idx = 0
130 | if self.max_episode_duration != 'max':
131 | self._idx = np.random.randint(
132 | low = self._idx,
133 | high = len(self.df) - self.max_episode_duration - self._idx
134 | )
135 |
136 | return await self.get_obs(options['ctx'])
137 |
138 | async def step(self, action):
139 | ctx = action['ctx']
140 | content = action['content']
141 |
142 | forced_reward = None
143 | # take content
144 | try:
145 | created_orders = await self.trade_function(ctx, content)
146 | except octobot_trading_errors.PortfolioNegativeValueError:
147 | forced_reward = -1
148 |
149 | self._idx += 1
150 | self._step += 1
151 |
152 | done, truncated = False, False
153 |
154 | if not done and forced_reward is None:
155 | current_pf_value = get_current_portfolio_value(ctx)
156 | profitabilities = get_profitabilities(ctx)
157 | current_profitability = profitabilities[1]
158 | market_profitability = profitabilities[3]
159 | reward = self.reward_function(current_pf_value,
160 | self._previous_portfolio_value,
161 | current_profitability,
162 | market_profitability,
163 | created_orders)
164 | self._previous_portfolio_value = current_pf_value
165 | else:
166 | reward = forced_reward
167 | # TODO save reward
168 |
169 | if done or truncated:
170 | # TODO ?
171 | None
172 | return await self.get_obs(ctx), reward, done, truncated
173 |
--------------------------------------------------------------------------------
/octobot_script/ai/models.py:
--------------------------------------------------------------------------------
1 | from keras.models import Sequential
2 | from keras.layers import Dense
3 | from keras.optimizers import Adam
4 |
5 |
6 | def mlp(n_action, n_hidden_layer=1, n_neuron_per_layer=32,
7 | activation='relu', loss='mse'):
8 | """ A multi-layer perceptron """
9 | print(n_action)
10 | model = Sequential()
11 |
12 | model.add(Dense(n_neuron_per_layer, input_dim=1, activation=activation))
13 | for _ in range(n_hidden_layer):
14 | model.add(Dense(n_neuron_per_layer, activation=activation))
15 | model.add(Dense(n_action, activation='relu'))
16 | model.compile(loss=loss, optimizer=Adam())
17 | print(model.summary())
18 | return model
19 |
--------------------------------------------------------------------------------
/octobot_script/api/__init__.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 |
18 | from octobot_script.api.data_fetching import *
19 | from octobot_script.api.execution import *
20 | from octobot_script.api.ploting import *
21 |
--------------------------------------------------------------------------------
/octobot_script/api/data_fetching.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | import octobot_backtesting.api as backtesting_api
18 | import octobot_commons.symbols as commons_symbols
19 | import octobot_commons.enums as commons_enums
20 | import octobot_trading.enums as trading_enums
21 | import octobot_script.internal.octobot_mocks as octobot_mocks
22 |
23 |
24 | def _ensure_ms_timestamp(timestamp):
25 | if timestamp is None:
26 | return timestamp
27 | if timestamp < 16737955050: # Friday 28 May 2500 07:57:30
28 | return timestamp * 1000
29 |
30 |
31 | async def historical_data(symbol, timeframe, exchange="binance", exchange_type=trading_enums.ExchangeTypes.SPOT.value,
32 | start_timestamp=None, end_timestamp=None):
33 | symbols = [symbol]
34 | time_frames = [commons_enums.TimeFrames(timeframe)]
35 | data_collector_instance = backtesting_api.exchange_historical_data_collector_factory(
36 | exchange,
37 | trading_enums.ExchangeTypes(exchange_type),
38 | octobot_mocks.get_tentacles_config(),
39 | [commons_symbols.parse_symbol(symbol) for symbol in symbols],
40 | time_frames=time_frames,
41 | start_timestamp=_ensure_ms_timestamp(start_timestamp),
42 | end_timestamp=_ensure_ms_timestamp(end_timestamp)
43 | )
44 | return await backtesting_api.initialize_and_run_data_collector(data_collector_instance)
45 |
46 |
47 | async def get_data(symbol, time_frame, exchange="binance", exchange_type=trading_enums.ExchangeTypes.SPOT.value,
48 | start_timestamp=None, end_timestamp=None, data_file=None):
49 | data = data_file or \
50 | await historical_data(symbol, timeframe=time_frame, exchange=exchange, exchange_type=exchange_type,
51 | start_timestamp=start_timestamp, end_timestamp=end_timestamp)
52 | return await backtesting_api.create_and_init_backtest_data(
53 | [data],
54 | octobot_mocks.get_config(),
55 | octobot_mocks.get_tentacles_config(),
56 | use_accurate_price_time_frame=True
57 | )
58 |
--------------------------------------------------------------------------------
/octobot_script/api/execution.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | import octobot_script.internal.logging_util as logging_util
18 | import octobot_script.internal.runners as runners
19 |
20 |
21 | async def run(backtesting_data, update_func, strategy_config,
22 | enable_logs=False, enable_storage=True):
23 | if enable_logs:
24 | logging_util.load_logging_config()
25 | return await runners.run(
26 | backtesting_data, update_func, strategy_config,
27 | enable_logs=enable_logs, enable_storage=enable_storage
28 | )
29 |
--------------------------------------------------------------------------------
/octobot_script/api/ploting.py:
--------------------------------------------------------------------------------
1 | # pylint: disable=E1101
2 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
3 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
4 | #
5 | # OctoBot is free software; you can redistribute it and/or
6 | # modify it under the terms of the GNU General Public License
7 | # as published by the Free Software Foundation; either
8 | # version 3.0 of the License, or (at your option) any later version.
9 | #
10 | # OctoBot 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 | # General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public
16 | # License along with OctoBot-Script. If not, see .
17 |
18 | async def plot_indicator(ctx, name, x, y, signals=None):
19 | # lazy import
20 | import octobot_script as obs
21 |
22 | await obs.plot(ctx, name, x=list(x), y=list(y))
23 | value_by_x = {
24 | x: y
25 | for x, y in zip(x, y)
26 | }
27 | if signals:
28 | await obs.plot(ctx, "signals", x=list(signals), y=[value_by_x[x] for x in signals], mode="markers")
29 |
30 |
--------------------------------------------------------------------------------
/octobot_script/cli.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 |
18 | import click
19 | import asyncio
20 | import aiohttp
21 | import sys
22 |
23 | import octobot_script
24 | import octobot_script.internal.octobot_mocks as octobot_mocks
25 | import octobot_script.internal.logging_util as octobot_script_logging
26 | import octobot_tentacles_manager.api as api
27 |
28 |
29 | async def install_all_tentacles(quite_mode) -> bool:
30 | octobot_script_logging.enable_base_logger()
31 | error_count = 0
32 | install_path = octobot_mocks.get_module_appdir_path()
33 | tentacles_path = octobot_mocks.get_tentacles_path()
34 | tentacles_urls = octobot_mocks.get_public_tentacles_urls()
35 | async with aiohttp.ClientSession() as aiohttp_session:
36 | for tentacles_url in tentacles_urls:
37 | error_count += await api.install_all_tentacles(tentacles_url,
38 | tentacle_path=tentacles_path,
39 | bot_path=install_path,
40 | aiohttp_session=aiohttp_session,
41 | quite_mode=quite_mode,
42 | bot_install_dir=install_path)
43 | return error_count == 0
44 |
45 |
46 | @click.group
47 | @click.version_option(version=octobot_script.VERSION, prog_name=octobot_script.PROJECT_NAME)
48 | def main():
49 | """
50 | OctoBot-Script command line interface.
51 | """
52 |
53 |
54 | @main.command("install_tentacles")
55 | @click.option('--quite', flag_value=True, help='Only display errors in logs.')
56 | def sync_install_tentacles(quite):
57 | """
58 | (Re)-install the available OctoBot tentacles.
59 | """
60 | sys.exit(0 if asyncio.run(install_all_tentacles(quite)) else -1)
61 |
62 |
63 | if __name__ == "__main__":
64 | main()
65 |
--------------------------------------------------------------------------------
/octobot_script/config/config_mock.json:
--------------------------------------------------------------------------------
1 | {
2 | "backtesting": {
3 | "files": []
4 | },
5 | "exchanges": {},
6 | "services": {
7 | "web": {
8 | "auto-open-in-web-browser": true
9 | }
10 | },
11 | "trader": {
12 | "enabled": false,
13 | "load-trade-history": false
14 | },
15 | "trader-simulator": {
16 | "enabled": true,
17 | "fees": {
18 | "maker": 0.01,
19 | "taker": 0.06
20 | },
21 | "starting-portfolio": {
22 | "BTC": 0,
23 | "ETH": 0,
24 | "USD": 0,
25 | "USDT": 10000
26 | }
27 | },
28 | "trading": {
29 | "reference-market": "USDT",
30 | "risk": 0.5
31 | },
32 | "notification":{
33 | "global-info": true,
34 | "price-alerts": true,
35 | "trades": true,
36 | "trading-script-alerts": true,
37 | "other": true,
38 | "notification-type": [
39 | "web"
40 | ]
41 | },
42 | "profile": "daily_trading",
43 | "accepted_terms": false
44 | }
45 |
--------------------------------------------------------------------------------
/octobot_script/config/logging_config.ini:
--------------------------------------------------------------------------------
1 | [loggers]
2 | keys=root
3 |
4 | [handlers]
5 | keys=consoleHandler,fileHandler
6 |
7 | [formatters]
8 | keys=consoleFormatter,fileFormatter
9 |
10 | [logger_root]
11 | level=DEBUG
12 | handlers=consoleHandler,fileHandler
13 |
14 | [handler_consoleHandler]
15 | class=StreamHandler
16 | level=DEBUG
17 | formatter=consoleFormatter
18 | args=(sys.stdout,)
19 |
20 | [handler_fileHandler]
21 | class=handlers.RotatingFileHandler
22 | level=DEBUG
23 | formatter=fileFormatter
24 | args=('logs/OctoBot.log', 'a', 24000000, 20)
25 |
26 | [formatter_consoleFormatter]
27 | class=colorlog.ColoredFormatter
28 | format=%(log_color)s %(asctime)s %(levelname)-8s %(name)-20s %(message)s
29 |
30 | [formatter_fileFormatter]
31 | format=%(asctime)-16s %(levelname)-6s %(name)-20s %(filename)-s:%(lineno)-8s %(message)s
32 | datefmt=%Y-%m-%d %H:%M:%S
33 |
--------------------------------------------------------------------------------
/octobot_script/constants.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 |
18 | ADDITIONAL_IMPORT_PATH = "imports"
19 | CONFIG_PATH = "config"
20 |
--------------------------------------------------------------------------------
/octobot_script/internal/__init__.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 |
--------------------------------------------------------------------------------
/octobot_script/internal/backtester_trading_mode.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | # import API first to avoid import issues
18 | import octobot_trading.api # pylint: disable=unused-import
19 | import octobot_trading.modes as modes
20 | import octobot_trading.enums as trading_enums
21 |
22 |
23 | class BacktesterTradingMode(modes.AbstractScriptedTradingMode):
24 |
25 | def __init__(self, config, exchange_manager):
26 | super().__init__(config, exchange_manager)
27 | self._import_scripts()
28 |
29 | def _import_scripts(self):
30 | pass
31 |
32 | @classmethod
33 | def get_supported_exchange_types(cls) -> list:
34 | """
35 | :return: The list of supported exchange types
36 | """
37 | return [
38 | trading_enums.ExchangeTypes.SPOT,
39 | trading_enums.ExchangeTypes.FUTURE,
40 | trading_enums.ExchangeTypes.MARGIN,
41 | ]
42 |
--------------------------------------------------------------------------------
/octobot_script/internal/logging_util.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | import logging
18 | import logging.config as config
19 | import os.path
20 |
21 | import octobot.logger
22 | import octobot_script.internal.octobot_mocks as octobot_mocks
23 |
24 |
25 | def load_logging_config(config_file="logging_config.ini"):
26 | if octobot.logger.BOT_CHANNEL_LOGGER is not None:
27 | # logs already initialized
28 | return
29 | logs_folder = "logs"
30 | if not os.path.exists(logs_folder):
31 | os.mkdir(logs_folder)
32 | try:
33 | config.fileConfig(config_file)
34 | except KeyError:
35 | logging_config = os.path.join(octobot_mocks.get_module_install_path(), "config", config_file)
36 | config.fileConfig(logging_config)
37 | octobot.logger.init_bot_channel_logger()
38 |
39 |
40 | def enable_base_logger():
41 | logging.basicConfig(
42 | level=logging.DEBUG,
43 | datefmt="%Y-%m-%d %H:%M:%S",
44 | format="%(asctime)s %(levelname)-8s %(name)-20s %(message)s"
45 | )
46 |
47 |
--------------------------------------------------------------------------------
/octobot_script/internal/octobot_mocks.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | import os
18 | import json
19 | import appdirs
20 |
21 | import octobot_script
22 | import octobot_script.constants as constants
23 | import octobot_script.internal.backtester_trading_mode
24 | import octobot_commons.constants as commons_constants
25 | import octobot_tentacles_manager.api as octobot_tentacles_manager_api
26 | import octobot_tentacles_manager.constants as octobot_tentacles_manager_constants
27 | import octobot.configuration_manager as octobot_configuration_manager
28 |
29 |
30 | def get_tentacles_config():
31 | # use tentacles config from user appdirs as it is kept up to date at each tentacle packages install
32 | ref_tentacles_config_path = os.path.join(
33 | get_module_appdir_path(),
34 | octobot_tentacles_manager_constants.USER_REFERENCE_TENTACLE_CONFIG_PATH,
35 | commons_constants.CONFIG_TENTACLES_FILE
36 | )
37 | tentacles_setup_config = octobot_tentacles_manager_api.get_tentacles_setup_config(ref_tentacles_config_path)
38 | # activate OctoBot-Script required tentacles
39 | _force_tentacles_config_activation(tentacles_setup_config)
40 | return tentacles_setup_config
41 |
42 |
43 | def get_config():
44 | with open(get_module_config_path("config_mock.json")) as f:
45 | return json.load(f)
46 |
47 |
48 | def get_module_install_path():
49 | return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
50 |
51 |
52 | def get_module_config_path(file_name):
53 | return os.path.join(get_module_install_path(), constants.CONFIG_PATH, file_name)
54 |
55 |
56 | def get_module_appdir_path():
57 | dirs = appdirs.AppDirs(octobot_script.PROJECT_NAME, octobot_script.AUTHOR, octobot_script.VERSION)
58 | return dirs.user_data_dir
59 |
60 |
61 | def get_internal_import_path():
62 | return os.path.join(get_module_appdir_path(), constants.ADDITIONAL_IMPORT_PATH)
63 |
64 |
65 | def get_tentacles_path():
66 | return os.path.join(get_internal_import_path(), octobot_tentacles_manager_constants.TENTACLES_PATH)
67 |
68 |
69 | def get_imported_tentacles_path():
70 | import tentacles
71 | return os.path.dirname(os.path.abspath(tentacles.__file__))
72 |
73 |
74 | def get_public_tentacles_urls():
75 | return [
76 | octobot_configuration_manager.get_default_tentacles_url()
77 | ]
78 |
79 |
80 | def _force_tentacles_config_activation(tentacles_setup_config):
81 | import tentacles.Evaluator
82 | forced_tentacles = {
83 | octobot_tentacles_manager_constants.TENTACLES_EVALUATOR_PATH: {
84 | tentacles.Evaluator.BlankStrategyEvaluator.get_name(): True
85 | },
86 | octobot_tentacles_manager_constants.TENTACLES_TRADING_PATH: {
87 | octobot_script.internal.backtester_trading_mode.BacktesterTradingMode.get_name(): True
88 | }
89 | }
90 | for topic, activations in forced_tentacles.items():
91 | for tentacle, activated in activations.items():
92 | tentacles_setup_config.tentacles_activation[topic][tentacle] = activated
93 |
--------------------------------------------------------------------------------
/octobot_script/internal/runners.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | import octobot.api as octobot_api
18 | import octobot_backtesting.api as backtesting_api
19 | import octobot_commons.constants as commons_constants
20 | import octobot_commons.enums as commons_enums
21 |
22 | import octobot_script.model as models
23 | import octobot_script.internal.backtester_trading_mode as backtester_trading_mode
24 |
25 |
26 | async def run(backtesting_data, update_func, strategy_config,
27 | enable_logs=False, enable_storage=False):
28 | backtest_result = models.BacktestResult(backtesting_data, strategy_config)
29 | _register_strategy(update_func, strategy_config)
30 | independent_backtesting = octobot_api.create_independent_backtesting(
31 | backtesting_data.config,
32 | backtesting_data.tentacles_config,
33 | backtesting_data.data_files,
34 | run_on_common_part_only=True,
35 | start_timestamp=None,
36 | end_timestamp=None,
37 | enable_logs=enable_logs,
38 | stop_when_finished=False,
39 | run_on_all_available_time_frames=True,
40 | enforce_total_databases_max_size_after_run=False,
41 | enable_storage=enable_storage,
42 | backtesting_data=backtesting_data,
43 | )
44 | await octobot_api.initialize_and_run_independent_backtesting(independent_backtesting)
45 | await independent_backtesting.join_backtesting_updater(None)
46 | await _gather_results(independent_backtesting, backtest_result)
47 | await octobot_api.stop_independent_backtesting(independent_backtesting)
48 | return backtest_result
49 |
50 |
51 | async def _gather_results(independent_backtesting, backtest_result):
52 | backtest_result.independent_backtesting = independent_backtesting
53 | backtest_result.duration = backtesting_api.get_backtesting_duration(
54 | independent_backtesting.octobot_backtesting.backtesting
55 | )
56 | backtest_result.candles_count = sum(
57 | candle_manager.get_preloaded_symbol_candles_count()
58 | for candle_manager in backtest_result.backtesting_data.preloaded_candle_managers.values()
59 | )
60 | backtest_result.report = await independent_backtesting.get_dict_formatted_report()
61 | backtest_result.bot_id = independent_backtesting.octobot_backtesting.bot_id
62 |
63 |
64 | def _register_strategy(update_func, strategy_config):
65 | def _local_import_scripts(self, *args):
66 | self._live_script = update_func
67 | original_reload_config = self.reload_config
68 |
69 | async def _local_reload_config(*args, **kwargs):
70 | await original_reload_config(*args, **kwargs)
71 | updated_config = {
72 | commons_constants.CONFIG_ACTIVATION_TOPICS.replace(" ", "_"):
73 | commons_enums.ActivationTopics.FULL_CANDLES.value
74 | }
75 | updated_config.update(strategy_config)
76 | self.trading_config.update(updated_config)
77 | self.reload_config = _local_reload_config
78 | backtester_trading_mode.BacktesterTradingMode._import_scripts = _local_import_scripts
79 |
--------------------------------------------------------------------------------
/octobot_script/model/__init__.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | from octobot_script.model.strategy import *
18 | from octobot_script.model.backtest_result import *
19 |
--------------------------------------------------------------------------------
/octobot_script/model/backtest_plot.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 | import time
17 | import webbrowser
18 | import jinja2
19 | import json
20 | import http.server
21 | import socketserver
22 |
23 | import octobot_commons.constants as commons_constants
24 | import octobot_commons.display as display
25 | import octobot_commons.logging as logging
26 | import octobot_commons.timestamp_util as timestamp_util
27 | import octobot.api as octobot_api
28 | import octobot_script.resources as resources
29 | import octobot_script.internal.backtester_trading_mode as backtester_trading_mode
30 |
31 |
32 | class BacktestPlot:
33 | DEFAULT_REPORT_NAME = "report.html"
34 | DEFAULT_TEMPLATE = "default_report_template.html"
35 | JINJA_ENVIRONMENT = jinja2.Environment(loader=jinja2.FileSystemLoader(
36 | resources.get_report_resource_path(None)
37 | ))
38 | GENERATED_TIME_FORMAT = "%Y-%m-%d at %H:%M:%S"
39 | SERVER_PORT = 5555
40 | SERVER_HOST = "localhost"
41 |
42 | def __init__(self, backtest_result, run_db_identifier, report_file=None):
43 | self.backtest_result = backtest_result
44 | self.report_file = report_file
45 | self.run_db_identifier = run_db_identifier
46 | self.backtesting_analysis_settings = self.default_backtesting_analysis_settings()
47 |
48 | async def fill(self, template_file=None):
49 | template = self.JINJA_ENVIRONMENT.get_template(template_file or self.DEFAULT_TEMPLATE)
50 | template_data = await self._get_template_data()
51 | with open(self.report_file, "w") as report:
52 | report.write(template.render(template_data))
53 |
54 | def show(self):
55 | backtest_plot_instance = self
56 | print(f"Report in {self.report_file}")
57 |
58 | class ReportRequestHandler(http.server.SimpleHTTPRequestHandler):
59 | def log_request(self, *_, **__):
60 | # do not log requests
61 | pass
62 |
63 | def do_GET(self):
64 | self.send_response(http.HTTPStatus.OK)
65 | self.send_header("Content-type", "text/html")
66 | self.end_headers()
67 |
68 | with open(backtest_plot_instance.report_file, "rb") as report:
69 | self.wfile.write(report.read())
70 |
71 | try:
72 | with socketserver.TCPServer(("", self.SERVER_PORT), ReportRequestHandler) as httpd:
73 | webbrowser.open(f"http://{self.SERVER_HOST}:{self.SERVER_PORT}")
74 | httpd.handle_request()
75 | except Exception:
76 | webbrowser.open(self.report_file)
77 |
78 | async def _get_template_data(self):
79 | full_data, symbols, time_frames, exchanges = await self._get_full_data()
80 | return {
81 | "FULL_DATA": full_data,
82 | "title": f"{', '.join(symbols)}",
83 | "top_title": f"{', '.join(symbols)} on {', '.join(time_frames)} from "
84 | f"{', '.join([e.capitalize() for e in exchanges])}",
85 | "creation_time": timestamp_util.convert_timestamp_to_datetime(time.time(), self.GENERATED_TIME_FORMAT),
86 | "middle_title": "Portfolio value",
87 | "bottom_title": "Details",
88 | "strategy_config": self.backtest_result.strategy_config
89 | }
90 |
91 | async def _get_full_data(self):
92 | # tentacles not available during first install
93 | import tentacles.Meta.Keywords.scripting_library as scripting_library
94 | elements = display.display_translator_factory()
95 | trading_mode = backtester_trading_mode.BacktesterTradingMode
96 | symbols = []
97 | time_frames = []
98 | exchanges = []
99 | for exchange, available_symbols in octobot_api.get_independent_backtesting_symbols_by_exchanges(
100 | self.backtest_result.independent_backtesting
101 | ).items():
102 | exchanges.append(exchange)
103 | for symbol in available_symbols:
104 | symbol = str(symbol)
105 | symbols.append(symbol)
106 | for time_frame in octobot_api.get_independent_backtesting_config(
107 | self.backtest_result.independent_backtesting)[commons_constants.CONFIG_TIME_FRAME]:
108 | time_frames.append(time_frame.value)
109 | await elements.fill_from_database(
110 | trading_mode, self.run_db_identifier, exchange, symbol, time_frame.value,
111 | None, with_inputs=False
112 | )
113 | ctx = scripting_library.Context.minimal(
114 | trading_mode, logging.get_logger(self.__class__.__name__), exchange, symbol,
115 | self.run_db_identifier.backtesting_id, self.run_db_identifier.optimizer_id,
116 | self.run_db_identifier.optimization_campaign_name, self.backtesting_analysis_settings)
117 | elements.add_parts_from_other(await scripting_library.default_backtesting_analysis_script(ctx))
118 | return json.dumps(elements.to_json()), symbols, time_frames, exchanges
119 |
120 | def default_backtesting_analysis_settings(self):
121 | return {
122 | "display_backtest_details": True,
123 | "display_trades_and_positions": True,
124 | "plot_best_case_growth_on_backtesting_chart": False,
125 | "plot_funding_fees_on_backtesting_chart": False,
126 | "plot_hist_portfolio_on_backtesting_chart": True,
127 | "plot_pnl_on_backtesting_chart": False,
128 | "plot_pnl_on_main_chart": False,
129 | "plot_trade_gains_on_backtesting_chart": False,
130 | "plot_trade_gains_on_main_chart": False,
131 | "plot_win_rate_on_backtesting_chart": False,
132 | "plot_wins_and_losses_count_on_backtesting_chart": False,
133 | "display_backtest_details_general": False,
134 | "display_backtest_details_performances": True,
135 | "display_backtest_details_details": False,
136 | "display_backtest_details_strategy_settings": False,
137 | }
138 |
--------------------------------------------------------------------------------
/octobot_script/model/backtest_result.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | import os
18 | import octobot_script.model.backtest_plot as backtest_plot
19 | import octobot_script.model.errors as errors
20 | import octobot_commons.databases as commons_databases
21 |
22 |
23 | class BacktestResult:
24 | def __init__(self, backtesting_data, strategy_config):
25 | self.backtesting_data = backtesting_data
26 | self.strategy_config = strategy_config
27 | self.independent_backtesting = None
28 | self.duration = None
29 | self.candles_count = None
30 | self.report = {}
31 | self.bot_id = None
32 |
33 | def describe(self):
34 | return f"[{round(self.duration, 3)}s / {self.candles_count} candles] profitability: {self.report['bot_report']['profitability']} " \
35 | f"market average: {self.report['bot_report']['market_average_profitability']} " \
36 | f"strategy_config: {self.strategy_config}"
37 |
38 | async def plot(self, report_file=None, show=False):
39 | if not commons_databases.RunDatabasesProvider.instance().is_storage_enabled(self.bot_id):
40 | raise errors.ParameterError("storage has to be enabled to plot backtesting data")
41 | plot_result = await self._get_plotted_result(report_file=report_file)
42 | if show:
43 | plot_result.show()
44 | return plot_result
45 |
46 | async def _get_plotted_result(self, report_file=None):
47 | run_db_id = commons_databases.RunDatabasesProvider.instance().get_run_databases_identifier(self.bot_id)
48 | plot = backtest_plot.BacktestPlot(
49 | self, run_db_id, report_file=report_file or self.get_default_plotted_report_file(run_db_id)
50 | )
51 | await plot.fill()
52 | return plot
53 |
54 | def get_default_plotted_report_file(self, run_db_id):
55 | return os.path.join(
56 | run_db_id.get_backtesting_run_folder(),
57 | backtest_plot.BacktestPlot.DEFAULT_REPORT_NAME
58 | )
59 |
--------------------------------------------------------------------------------
/octobot_script/model/errors.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | class ParameterError(Exception):
18 | pass
19 |
--------------------------------------------------------------------------------
/octobot_script/model/strategy.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | class Strategy:
18 | def __init__(self, config=None):
19 | self.config = config
20 |
--------------------------------------------------------------------------------
/octobot_script/resources/__init__.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | import os
18 |
19 |
20 | def get_report_resource_path(resource_name):
21 | base_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "reports")
22 | if resource_name:
23 | return os.path.join(base_path, resource_name)
24 | return base_path
25 |
--------------------------------------------------------------------------------
/octobot_script/resources/reports/css/style.css:
--------------------------------------------------------------------------------
1 | /** colors **/
2 | :root {
3 | --bg: #131722;
4 | --fnt: #b2b5be;
5 | --brdr: #2a2e39;
6 | --brdr-actv: #B71C1C;
7 | --fnt-hvr: #131722;
8 | --bg-hvr: #C62828;
9 | --fnt-actv: #B71C1C;
10 | --new-warning: #fb3;
11 | --new-success: #00c851;
12 | }
13 |
14 | .bg-dark, .form-control options, ul {
15 | background: var(--bg) !important;
16 | color: var(--fnt) !important;
17 | }
18 |
19 | .octobot-logo {
20 | height: 40px;
21 | }
22 |
23 | /** plotting **/
24 |
25 | .hoverlayer line:first-child, .hoverlayer line:nth-child(3) {
26 | /*hide selector borders*/
27 | stroke: unset;
28 | }
29 |
30 | .main-chart {
31 | height: 300px;
32 | }
33 |
34 | .sub-chart {
35 | height: 200px;
36 | }
37 |
38 | .sub-chart {
39 | height: 200px;
40 | }
41 |
42 | .backtesting-run-overview{
43 | height: 200px;
44 | }
45 |
--------------------------------------------------------------------------------
/octobot_script/resources/reports/css/w2ui_template.css:
--------------------------------------------------------------------------------
1 | /*global*/
2 | .w2ui-empty-record, .w2ui-empty-record:hover, .w2ui-grid-footer, .w2ui-grid-columns,
3 | w2ui-grid-columns *, .w2ui-grid-toolbar,.w2ui-grid-toolbar *, .w2ui-grid-header, .w2ui-record,
4 | .grid_RSI_footer, .w2ui-overlay *, .w2ui-message *{
5 | background-color: var(--brdr) !important;
6 | color: var(--fnt) !important;
7 | background-image: unset !important;
8 | }
9 |
10 | /*searches*/
11 | .w2ui-head, .w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-all,
12 | .w2ui-grid .w2ui-grid-toolbar .w2ui-grid-search-input .w2ui-search-all:focus {
13 | background-color: var(--bg) !important;
14 | color: var(--fnt) !important;
15 | background-image: unset !important;
16 | }
17 |
18 | /*rows*/
19 | .w2ui-even{
20 | background-color: var(--bg) !important;
21 | }
22 |
23 | .w2ui-odd{
24 | background-color: var(--brdr) !important;
25 | }
26 |
27 | /*hover*/
28 | .w2ui-record:hover {
29 | background-color: var(--bg-hvr) !important;
30 | }
31 |
32 | /*selected*/
33 | .w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-inactive,
34 | .w2ui-grid .w2ui-grid-body .w2ui-grid-records table tr.w2ui-selected {
35 | background-color: var(--brdr-actv) !important;
36 | color: var(--fnt) !important;
37 | }
38 |
--------------------------------------------------------------------------------
/octobot_script/resources/reports/default_report_template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {% include 'header.html' %}
4 |
5 |
`
16 | );
17 | }else{
18 | backtestingValuesGridDiv.append(element.html);
19 | }
20 | });
21 | }
22 |
23 | const createTexts = () => {
24 |
25 | FULL_DATA.forEach((maybeValue) => {
26 | if (maybeValue.type !== "value") {
27 | return;
28 | }
29 | const parentDiv = $(document.getElementById(maybeValue.name));
30 | if(!parentDiv.length){
31 | return
32 | }
33 | _add_labelled_backtesting_values(maybeValue, parentDiv)
34 | });
35 | }
36 |
37 | const init = () => {
38 | createTexts();
39 | }
40 |
41 | init();
42 | });
43 |
--------------------------------------------------------------------------------
/octobot_script/resources/reports/scripts.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
16 |
--------------------------------------------------------------------------------
/requirements-ai.txt:
--------------------------------------------------------------------------------
1 | numpy
2 | tensorflow==2.11.0
3 | gymnasium
4 | tqdm
5 | tensorboard
6 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # Drakkar-Software requirements
2 | OctoBot==2.0.11
3 |
4 | # manage tentacles install path
5 | appdirs==1.4.4
6 |
7 | # CLI
8 | click==8.1.3
9 |
10 | # versions managed by OctoBot's dependencies
11 | jinja2
12 | aiohttp
13 |
14 | # necessary for pip install
15 | wheel
16 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | from setuptools import find_packages
4 | from setuptools import setup
5 | from distutils.command.install import install
6 |
7 |
8 | # from octobot_script import PROJECT_NAME, VERSION
9 | # todo figure out how not to import octobot_script.__init__.py here
10 | PROJECT_NAME = "OctoBot-Script"
11 | VERSION = "0.0.25" # major.minor.revision
12 |
13 |
14 | def _post_install():
15 | import octobot_script.cli
16 | asyncio.run(octobot_script.cli.install_all_tentacles(True))
17 |
18 |
19 | class InstallWithPostInstallAction(install):
20 | def run(self):
21 | install.run(self)
22 | self.execute(_post_install, (), msg="Installing OctoBot-Script tentacles")
23 |
24 |
25 | PACKAGES = find_packages(
26 | exclude=[
27 | "tests",
28 | "octobot_script.imports*",
29 | "octobot_script.user*",
30 | ]
31 | )
32 |
33 | # long description from README file
34 | with open('README.md', encoding='utf-8') as f:
35 | DESCRIPTION = f.read()
36 |
37 | REQUIRED = open('requirements.txt').readlines()
38 | REQUIRES_PYTHON = '>=3.8'
39 |
40 | setup(
41 | name=PROJECT_NAME,
42 | version=VERSION,
43 | url='https://github.com/Drakkar-Software/OctoBot-Script',
44 | license='GPL-3.0',
45 | author='Drakkar-Software',
46 | author_email='contact@drakkar.software',
47 | description='Backtesting framework of the OctoBot Ecosystem',
48 | packages=PACKAGES,
49 | cmdclass={'install': InstallWithPostInstallAction},
50 | long_description=DESCRIPTION,
51 | long_description_content_type='text/markdown',
52 | tests_require=["pytest"],
53 | test_suite="tests",
54 | zip_safe=False,
55 | data_files=[],
56 | include_package_data=True, # copy non python files on install
57 | install_requires=REQUIRED,
58 | python_requires=REQUIRES_PYTHON,
59 | entry_points={
60 | 'console_scripts': [
61 | 'octobot_script = octobot_script.cli:main'
62 | ]
63 | },
64 | classifiers=[
65 | 'Development Status :: 5 - Production/Stable',
66 | 'Operating System :: OS Independent',
67 | 'Operating System :: MacOS :: MacOS X',
68 | 'Operating System :: Microsoft :: Windows',
69 | 'Operating System :: POSIX',
70 | 'Programming Language :: Python :: 3.10',
71 | ],
72 | )
73 |
--------------------------------------------------------------------------------
/standard.rc:
--------------------------------------------------------------------------------
1 | [MASTER]
2 |
3 | # A comma-separated list of package or module names from where C extensions may
4 | # be loaded. Extensions are loading into the active Python interpreter and may
5 | # run arbitrary code.
6 | extension-pkg-whitelist=
7 |
8 | # Specify a score threshold to be exceeded before program exits with error.
9 | fail-under=10.0
10 |
11 | # Add files or directories to the blacklist. They should be base names, not
12 | # paths.
13 | ignore=CVS
14 |
15 | # Add files or directories matching the regex patterns to the blacklist. The
16 | # regex matches against base names, not paths.
17 | ignore-patterns=
18 |
19 | # Python code to execute, usually for sys.path manipulation such as
20 | # pygtk.require().
21 | #init-hook=
22 |
23 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
24 | # number of processors available to use.
25 | jobs=1
26 |
27 | # Control the amount of potential inferred values when inferring a single
28 | # object. This can help the performance when dealing with large functions or
29 | # complex, nested conditions.
30 | limit-inference-results=100
31 |
32 | # List of plugins (as comma separated values of python module names) to load,
33 | # usually to register additional checkers.
34 | load-plugins=
35 |
36 | # Pickle collected data for later comparisons.
37 | persistent=yes
38 |
39 | # When enabled, pylint would attempt to guess common misconfiguration and emit
40 | # user-friendly hints instead of false-positive error messages.
41 | suggestion-mode=yes
42 |
43 | # Allow loading of arbitrary C extensions. Extensions are imported into the
44 | # active Python interpreter and may run arbitrary code.
45 | unsafe-load-any-extension=no
46 |
47 |
48 | [MESSAGES CONTROL]
49 |
50 | # Only show warnings with the listed confidence levels. Leave empty to show
51 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
52 | confidence=
53 |
54 | # Disable the message, report, category or checker with the given id(s). You
55 | # can either give multiple identifiers separated by comma (,) or put this
56 | # option multiple times (only on the command line, not in the configuration
57 | # file where it should appear only once). You can also use "--disable=all" to
58 | # disable everything first and then reenable specific checks. For example, if
59 | # you want to run only the similarities checker, you can use "--disable=all
60 | # --enable=similarities". If you want to run only the classes checker, but have
61 | # no Warning level messages displayed, use "--disable=all --enable=classes
62 | # --disable=W".
63 | disable=print-statement,
64 | parameter-unpacking,
65 | unpacking-in-except,
66 | old-raise-syntax,
67 | backtick,
68 | long-suffix,
69 | old-ne-operator,
70 | old-octal-literal,
71 | import-star-module-level,
72 | non-ascii-bytes-literal,
73 | raw-checker-failed,
74 | bad-inline-option,
75 | locally-disabled,
76 | file-ignored,
77 | suppressed-message,
78 | useless-suppression,
79 | deprecated-pragma,
80 | use-symbolic-message-instead,
81 | apply-builtin,
82 | basestring-builtin,
83 | buffer-builtin,
84 | cmp-builtin,
85 | coerce-builtin,
86 | execfile-builtin,
87 | file-builtin,
88 | long-builtin,
89 | raw_input-builtin,
90 | reduce-builtin,
91 | standarderror-builtin,
92 | unicode-builtin,
93 | xrange-builtin,
94 | coerce-method,
95 | delslice-method,
96 | getslice-method,
97 | setslice-method,
98 | no-absolute-import,
99 | old-division,
100 | dict-iter-method,
101 | dict-view-method,
102 | next-method-called,
103 | metaclass-assignment,
104 | indexing-exception,
105 | raising-string,
106 | reload-builtin,
107 | oct-method,
108 | hex-method,
109 | nonzero-method,
110 | cmp-method,
111 | input-builtin,
112 | round-builtin,
113 | intern-builtin,
114 | unichr-builtin,
115 | map-builtin-not-iterating,
116 | zip-builtin-not-iterating,
117 | range-builtin-not-iterating,
118 | filter-builtin-not-iterating,
119 | using-cmp-argument,
120 | eq-without-hash,
121 | div-method,
122 | idiv-method,
123 | rdiv-method,
124 | exception-message-attribute,
125 | invalid-str-codec,
126 | sys-max-int,
127 | bad-python3-import,
128 | deprecated-string-function,
129 | deprecated-str-translate-call,
130 | deprecated-itertools-function,
131 | deprecated-types-field,
132 | next-method-defined,
133 | dict-items-not-iterating,
134 | dict-keys-not-iterating,
135 | dict-values-not-iterating,
136 | deprecated-operator-function,
137 | deprecated-urllib-function,
138 | xreadlines-attribute,
139 | deprecated-sys-function,
140 | exception-escape,
141 | comprehension-escape,
142 | import-error,
143 | C, I, R, W # only errors TODO remove
144 |
145 | # Enable the message, report, category or checker with the given id(s). You can
146 | # either give multiple identifier separated by comma (,) or put this option
147 | # multiple time (only on the command line, not in the configuration file where
148 | # it should appear only once). See also the "--disable" option for examples.
149 | enable=
150 |
151 |
152 | [REPORTS]
153 |
154 | # Python expression which should return a score less than or equal to 10. You
155 | # have access to the variables 'error', 'warning', 'refactor', and 'convention'
156 | # which contain the number of messages in each category, as well as 'statement'
157 | # which is the total number of statements analyzed. This score is used by the
158 | # global evaluation report (RP0004).
159 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
160 |
161 | # Template used to display messages. This is a python new-style format string
162 | # used to format the message information. See doc for all details.
163 | #msg-template=
164 |
165 | # Set the output format. Available formats are text, parseable, colorized, json
166 | # and msvs (visual studio). You can also give a reporter class, e.g.
167 | # mypackage.mymodule.MyReporterClass.
168 | output-format=text
169 |
170 | # Tells whether to display a full report or only the messages.
171 | reports=no
172 |
173 | # Activate the evaluation score.
174 | score=yes
175 |
176 |
177 | [REFACTORING]
178 |
179 | # Maximum number of nested blocks for function / method body
180 | max-nested-blocks=5
181 |
182 | # Complete name of functions that never returns. When checking for
183 | # inconsistent-return-statements if a never returning function is called then
184 | # it will be considered as an explicit return statement and no message will be
185 | # printed.
186 | never-returning-functions=sys.exit
187 |
188 |
189 | [MISCELLANEOUS]
190 |
191 | # List of note tags to take in consideration, separated by a comma.
192 | notes=FIXME,
193 | XXX,
194 | TODO
195 |
196 | # Regular expression of note tags to take in consideration.
197 | #notes-rgx=
198 |
199 |
200 | [STRING]
201 |
202 | # This flag controls whether inconsistent-quotes generates a warning when the
203 | # character used as a quote delimiter is used inconsistently within a module.
204 | check-quote-consistency=no
205 |
206 | # This flag controls whether the implicit-str-concat should generate a warning
207 | # on implicit string concatenation in sequences defined over several lines.
208 | check-str-concat-over-line-jumps=no
209 |
210 |
211 | [BASIC]
212 |
213 | # Naming style matching correct argument names.
214 | argument-naming-style=snake_case
215 |
216 | # Regular expression matching correct argument names. Overrides argument-
217 | # naming-style.
218 | #argument-rgx=
219 |
220 | # Naming style matching correct attribute names.
221 | attr-naming-style=snake_case
222 |
223 | # Regular expression matching correct attribute names. Overrides attr-naming-
224 | # style.
225 | #attr-rgx=
226 |
227 | # Bad variable names which should always be refused, separated by a comma.
228 | bad-names=foo,
229 | bar,
230 | baz,
231 | toto,
232 | tutu,
233 | tata
234 |
235 | # Bad variable names regexes, separated by a comma. If names match any regex,
236 | # they will always be refused
237 | bad-names-rgxs=
238 |
239 | # Naming style matching correct class attribute names.
240 | class-attribute-naming-style=any
241 |
242 | # Regular expression matching correct class attribute names. Overrides class-
243 | # attribute-naming-style.
244 | #class-attribute-rgx=
245 |
246 | # Naming style matching correct class names.
247 | class-naming-style=PascalCase
248 |
249 | # Regular expression matching correct class names. Overrides class-naming-
250 | # style.
251 | #class-rgx=
252 |
253 | # Naming style matching correct constant names.
254 | const-naming-style=UPPER_CASE
255 |
256 | # Regular expression matching correct constant names. Overrides const-naming-
257 | # style.
258 | #const-rgx=
259 |
260 | # Minimum line length for functions/classes that require docstrings, shorter
261 | # ones are exempt.
262 | docstring-min-length=-1
263 |
264 | # Naming style matching correct function names.
265 | function-naming-style=snake_case
266 |
267 | # Regular expression matching correct function names. Overrides function-
268 | # naming-style.
269 | #function-rgx=
270 |
271 | # Good variable names which should always be accepted, separated by a comma.
272 | good-names=i,
273 | j,
274 | k,
275 | ex,
276 | Run,
277 | _
278 |
279 | # Good variable names regexes, separated by a comma. If names match any regex,
280 | # they will always be accepted
281 | good-names-rgxs=
282 |
283 | # Include a hint for the correct naming format with invalid-name.
284 | include-naming-hint=no
285 |
286 | # Naming style matching correct inline iteration names.
287 | inlinevar-naming-style=any
288 |
289 | # Regular expression matching correct inline iteration names. Overrides
290 | # inlinevar-naming-style.
291 | #inlinevar-rgx=
292 |
293 | # Naming style matching correct method names.
294 | method-naming-style=snake_case
295 |
296 | # Regular expression matching correct method names. Overrides method-naming-
297 | # style.
298 | #method-rgx=
299 |
300 | # Naming style matching correct module names.
301 | module-naming-style=snake_case
302 |
303 | # Regular expression matching correct module names. Overrides module-naming-
304 | # style.
305 | #module-rgx=
306 |
307 | # Colon-delimited sets of names that determine each other's naming style when
308 | # the name regexes allow several styles.
309 | name-group=
310 |
311 | # Regular expression which should only match function or class names that do
312 | # not require a docstring.
313 | no-docstring-rgx=^_
314 |
315 | # List of decorators that produce properties, such as abc.abstractproperty. Add
316 | # to this list to register other decorators that produce valid properties.
317 | # These decorators are taken in consideration only for invalid-name.
318 | property-classes=abc.abstractproperty
319 |
320 | # Naming style matching correct variable names.
321 | variable-naming-style=snake_case
322 |
323 | # Regular expression matching correct variable names. Overrides variable-
324 | # naming-style.
325 | #variable-rgx=
326 |
327 |
328 | [SPELLING]
329 |
330 | # Limits count of emitted suggestions for spelling mistakes.
331 | max-spelling-suggestions=4
332 |
333 | # Spelling dictionary name. Available dictionaries: none. To make it work,
334 | # install the python-enchant package.
335 | spelling-dict=
336 |
337 | # List of comma separated words that should not be checked.
338 | spelling-ignore-words=
339 |
340 | # A path to a file that contains the private dictionary; one word per line.
341 | spelling-private-dict-file=
342 |
343 | # Tells whether to store unknown words to the private dictionary (see the
344 | # --spelling-private-dict-file option) instead of raising a message.
345 | spelling-store-unknown-words=no
346 |
347 |
348 | [LOGGING]
349 |
350 | # The type of string formatting that logging methods do. `old` means using %
351 | # formatting, `new` is for `{}` formatting.
352 | logging-format-style=old
353 |
354 | # Logging modules to check that the string format arguments are in logging
355 | # function parameter format.
356 | logging-modules=logging
357 |
358 |
359 | [FORMAT]
360 |
361 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
362 | expected-line-ending-format=
363 |
364 | # Regexp for a line that is allowed to be longer than the limit.
365 | ignore-long-lines=^\s*(# )??$
366 |
367 | # Number of spaces of indent required inside a hanging or continued line.
368 | indent-after-paren=4
369 |
370 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
371 | # tab).
372 | indent-string=' '
373 |
374 | # Maximum number of characters on a single line.
375 | max-line-length=100
376 |
377 | # Maximum number of lines in a module.
378 | max-module-lines=1000
379 |
380 | # Allow the body of a class to be on the same line as the declaration if body
381 | # contains single statement.
382 | single-line-class-stmt=no
383 |
384 | # Allow the body of an if to be on the same line as the test if there is no
385 | # else.
386 | single-line-if-stmt=no
387 |
388 |
389 | [TYPECHECK]
390 |
391 | # List of decorators that produce context managers, such as
392 | # contextlib.contextmanager. Add to this list to register other decorators that
393 | # produce valid context managers.
394 | contextmanager-decorators=contextlib.contextmanager
395 |
396 | # List of members which are set dynamically and missed by pylint inference
397 | # system, and so shouldn't trigger E1101 when accessed. Python regular
398 | # expressions are accepted.
399 | generated-members=
400 |
401 | # Tells whether missing members accessed in mixin class should be ignored. A
402 | # mixin class is detected if its name ends with "mixin" (case insensitive).
403 | ignore-mixin-members=yes
404 |
405 | # Tells whether to warn about missing members when the owner of the attribute
406 | # is inferred to be None.
407 | ignore-none=yes
408 |
409 | # This flag controls whether pylint should warn about no-member and similar
410 | # checks whenever an opaque object is returned when inferring. The inference
411 | # can return multiple potential results while evaluating a Python object, but
412 | # some branches might not be evaluated, which results in partial inference. In
413 | # that case, it might be useful to still emit no-member and other checks for
414 | # the rest of the inferred objects.
415 | ignore-on-opaque-inference=yes
416 |
417 | # List of class names for which member attributes should not be checked (useful
418 | # for classes with dynamically set attributes). This supports the use of
419 | # qualified names.
420 | ignored-classes=optparse.Values,thread._local,_thread._local
421 |
422 | # List of module names for which member attributes should not be checked
423 | # (useful for modules/projects where namespaces are manipulated during runtime
424 | # and thus existing member attributes cannot be deduced by static analysis). It
425 | # supports qualified module names, as well as Unix pattern matching.
426 | ignored-modules=
427 |
428 | # Show a hint with possible names when a member name was not found. The aspect
429 | # of finding the hint is based on edit distance.
430 | missing-member-hint=yes
431 |
432 | # The minimum edit distance a name should have in order to be considered a
433 | # similar match for a missing member name.
434 | missing-member-hint-distance=1
435 |
436 | # The total number of similar names that should be taken in consideration when
437 | # showing a hint for a missing member.
438 | missing-member-max-choices=1
439 |
440 | # List of decorators that change the signature of a decorated function.
441 | signature-mutators=
442 |
443 |
444 | [SIMILARITIES]
445 |
446 | # Ignore comments when computing similarities.
447 | ignore-comments=yes
448 |
449 | # Ignore docstrings when computing similarities.
450 | ignore-docstrings=yes
451 |
452 | # Ignore imports when computing similarities.
453 | ignore-imports=no
454 |
455 | # Minimum lines number of a similarity.
456 | min-similarity-lines=4
457 |
458 |
459 | [VARIABLES]
460 |
461 | # List of additional names supposed to be defined in builtins. Remember that
462 | # you should avoid defining new builtins when possible.
463 | additional-builtins=
464 |
465 | # Tells whether unused global variables should be treated as a violation.
466 | allow-global-unused-variables=yes
467 |
468 | # List of strings which can identify a callback function by name. A callback
469 | # name must start or end with one of those strings.
470 | callbacks=cb_,
471 | _cb
472 |
473 | # A regular expression matching the name of dummy variables (i.e. expected to
474 | # not be used).
475 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
476 |
477 | # Argument names that match this expression will be ignored. Default to name
478 | # with leading underscore.
479 | ignored-argument-names=_.*|^ignored_|^unused_
480 |
481 | # Tells whether we should check for unused import in __init__ files.
482 | init-import=no
483 |
484 | # List of qualified module names which can have objects that can redefine
485 | # builtins.
486 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
487 |
488 |
489 | [DESIGN]
490 |
491 | # Maximum number of arguments for function / method.
492 | max-args=5
493 |
494 | # Maximum number of attributes for a class (see R0902).
495 | max-attributes=7
496 |
497 | # Maximum number of boolean expressions in an if statement (see R0916).
498 | max-bool-expr=5
499 |
500 | # Maximum number of branch for function / method body.
501 | max-branches=12
502 |
503 | # Maximum number of locals for function / method body.
504 | max-locals=15
505 |
506 | # Maximum number of parents for a class (see R0901).
507 | max-parents=7
508 |
509 | # Maximum number of public methods for a class (see R0904).
510 | max-public-methods=20
511 |
512 | # Maximum number of return / yield for function / method body.
513 | max-returns=6
514 |
515 | # Maximum number of statements in function / method body.
516 | max-statements=50
517 |
518 | # Minimum number of public methods for a class (see R0903).
519 | min-public-methods=2
520 |
521 |
522 | [CLASSES]
523 |
524 | # List of method names used to declare (i.e. assign) instance attributes.
525 | defining-attr-methods=__init__,
526 | __new__,
527 | setUp,
528 | __post_init__
529 |
530 | # List of member names, which should be excluded from the protected access
531 | # warning.
532 | exclude-protected=_asdict,
533 | _fields,
534 | _replace,
535 | _source,
536 | _make
537 |
538 | # List of valid names for the first argument in a class method.
539 | valid-classmethod-first-arg=cls
540 |
541 | # List of valid names for the first argument in a metaclass class method.
542 | valid-metaclass-classmethod-first-arg=cls
543 |
544 |
545 | [IMPORTS]
546 |
547 | # List of modules that can be imported at any level, not just the top level
548 | # one.
549 | allow-any-import-level=
550 |
551 | # Allow wildcard imports from modules that define __all__.
552 | allow-wildcard-with-all=no
553 |
554 | # Analyse import fallback blocks. This can be used to support both Python 2 and
555 | # 3 compatible code, which means that the block might have code that exists
556 | # only in one or another interpreter, leading to false positives when analysed.
557 | analyse-fallback-blocks=no
558 |
559 | # Deprecated modules which should not be used, separated by a comma.
560 | deprecated-modules=optparse,tkinter.tix
561 |
562 | # Create a graph of external dependencies in the given file (report RP0402 must
563 | # not be disabled).
564 | ext-import-graph=
565 |
566 | # Create a graph of every (i.e. internal and external) dependencies in the
567 | # given file (report RP0402 must not be disabled).
568 | import-graph=
569 |
570 | # Create a graph of internal dependencies in the given file (report RP0402 must
571 | # not be disabled).
572 | int-import-graph=
573 |
574 | # Force import order to recognize a module as part of the standard
575 | # compatibility libraries.
576 | known-standard-library=
577 |
578 | # Force import order to recognize a module as part of a third party library.
579 | known-third-party=enchant
580 |
581 | # Couples of modules and preferred modules, separated by a comma.
582 | preferred-modules=
583 |
584 |
585 | [EXCEPTIONS]
586 |
587 | # Exceptions that will emit a warning when being caught. Defaults to
588 | # "BaseException, Exception".
589 | overgeneral-exceptions=builtins.BaseException,
590 | builtins.Exception
591 |
--------------------------------------------------------------------------------
/start.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | import octobot_script.cli
18 |
19 | octobot_script.cli.main()
20 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | import pytest
18 | import mock
19 | import octobot_script.internal.octobot_mocks as octobot_mocks
20 |
21 |
22 | # only load config once
23 | TEST_CONFIG = octobot_mocks.get_config()
24 | TEST_TENTACLES_CONFIG = octobot_mocks.get_tentacles_config()
25 |
26 |
27 | @pytest.fixture
28 | def mocked_config():
29 | with mock.patch.object(octobot_mocks, "get_config", mock.Mock(return_value=TEST_CONFIG)), \
30 | mock.patch.object(octobot_mocks, "get_tentacles_config", mock.Mock(return_value=TEST_TENTACLES_CONFIG)):
31 | yield
32 |
--------------------------------------------------------------------------------
/tests/api/__init__.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 |
--------------------------------------------------------------------------------
/tests/api/test_data_fetching.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | import pytest
18 | import mock
19 |
20 | import octobot_commons.enums as commons_enums
21 | import octobot_trading.enums as trading_enums
22 | import octobot_backtesting.api as backtesting_api
23 | import octobot_script as obs
24 | import octobot_script.api.data_fetching as data_fetching
25 |
26 | from tests import mocked_config, TEST_CONFIG, TEST_TENTACLES_CONFIG
27 |
28 |
29 | # All test coroutines will be treated as marked.
30 | pytestmark = pytest.mark.asyncio
31 |
32 |
33 | async def test_historical_data():
34 | with mock.patch.object(backtesting_api, "initialize_and_run_data_collector", mock.AsyncMock(return_value="data")) \
35 | as initialize_and_run_data_collector_mock:
36 | assert await obs.historical_data("BTC/USDT", commons_enums.TimeFrames.ONE_DAY.value) == "data"
37 | initialize_and_run_data_collector_mock.assert_awaited_once()
38 |
39 |
40 | async def test_get_data(mocked_config):
41 | with mock.patch.object(data_fetching, "historical_data",
42 | mock.AsyncMock(return_value="data")) as historical_data_mock, \
43 | mock.patch.object(backtesting_api, "create_and_init_backtest_data",
44 | mock.AsyncMock(return_value="backtest_data")) as create_and_init_backtest_data_mock:
45 | assert await obs.get_data("BTC/USDT", commons_enums.TimeFrames.ONE_DAY.value) == "backtest_data"
46 | historical_data_mock.assert_awaited_once_with(
47 | "BTC/USDT",
48 | timeframe=commons_enums.TimeFrames.ONE_DAY.value,
49 | exchange="binance",
50 | exchange_type=trading_enums.ExchangeTypes.SPOT.value,
51 | start_timestamp=None,
52 | end_timestamp=None,
53 | )
54 | create_and_init_backtest_data_mock.assert_awaited_once_with(
55 | ["data"],
56 | TEST_CONFIG,
57 | TEST_TENTACLES_CONFIG,
58 | use_accurate_price_time_frame=True
59 | )
60 | historical_data_mock.reset_mock()
61 | create_and_init_backtest_data_mock.reset_mock()
62 | assert await obs.get_data("BTC/USDT", commons_enums.TimeFrames.ONE_DAY.value, data_file="existing_file") \
63 | == "backtest_data"
64 | historical_data_mock.assert_not_awaited()
65 | create_and_init_backtest_data_mock.assert_awaited_once_with(
66 | ["existing_file"],
67 | TEST_CONFIG,
68 | TEST_TENTACLES_CONFIG,
69 | use_accurate_price_time_frame=True
70 | )
71 |
72 |
--------------------------------------------------------------------------------
/tests/api/test_execution.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | import pytest
18 | import mock
19 |
20 | import octobot_script as obs
21 | import octobot_script.internal.logging_util as logging_util
22 | import octobot_script.internal.runners as runners
23 |
24 |
25 | # All test coroutines will be treated as marked.
26 | pytestmark = pytest.mark.asyncio
27 |
28 |
29 | async def test_run():
30 | with mock.patch.object(logging_util, "load_logging_config", mock.Mock()) as load_logging_config_mock, \
31 | mock.patch.object(runners, "run", mock.AsyncMock(return_value="ret")) as run_mock:
32 | def up_func():
33 | pass
34 | assert await obs.run("backtesting_data", up_func, "strat_config") == "ret"
35 | load_logging_config_mock.assert_not_called()
36 | run_mock.assert_awaited_once_with("backtesting_data", up_func, "strat_config",
37 | enable_logs=False, enable_storage=True)
38 | load_logging_config_mock.reset_mock()
39 | run_mock.reset_mock()
40 | assert await obs.run("backtesting_data", up_func, "strat_config", enable_logs=True, enable_storage=False) \
41 | == "ret"
42 | load_logging_config_mock.assert_called_once()
43 | run_mock.assert_awaited_once_with("backtesting_data", up_func, "strat_config",
44 | enable_logs=True, enable_storage=False)
45 |
--------------------------------------------------------------------------------
/tests/functionnal/__init__.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | import pytest_asyncio
18 | import os
19 | import octobot_script as obs
20 |
21 |
22 | # only load config once
23 | BACKTESTING_FILES_DIR = os.path.join("tests", "test_util")
24 | ONE_DAY_BTC_USDT_DATA = os.path.join(BACKTESTING_FILES_DIR, "ExchangeHistoryDataCollector_1673796151.325921.data")
25 |
26 |
27 | @pytest_asyncio.fixture
28 | async def one_day_btc_usdt_data():
29 | data = None
30 | try:
31 | data = await obs.get_data("BTC/USDT", "1d", start_timestamp=1505606400, data_file=ONE_DAY_BTC_USDT_DATA)
32 | yield data
33 | finally:
34 | if data is not None:
35 | await data.stop()
36 |
--------------------------------------------------------------------------------
/tests/functionnal/example_scripts/__init__.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 |
--------------------------------------------------------------------------------
/tests/functionnal/example_scripts/test_precomputed_vs_iteration_rsi.py:
--------------------------------------------------------------------------------
1 | # This file is part of OctoBot-Script (https://github.com/Drakkar-Software/OctoBot-Script)
2 | # Copyright (c) 2023 Drakkar-Software, All rights reserved.
3 | #
4 | # OctoBot is free software; you can redistribute it and/or
5 | # modify it under the terms of the GNU General Public License
6 | # as published by the Free Software Foundation; either
7 | # version 3.0 of the License, or (at your option) any later version.
8 | #
9 | # OctoBot 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 | # General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public
15 | # License along with OctoBot-Script. If not, see .
16 |
17 | import pytest
18 | import os
19 | import tulipy
20 |
21 | import octobot_script as obs
22 | from tests.functionnal import one_day_btc_usdt_data
23 |
24 |
25 | # All test coroutines will be treated as marked.
26 | pytestmark = pytest.mark.asyncio
27 |
28 |
29 | async def test_precomputed_vs_iteration_rsi(one_day_btc_usdt_data):
30 | # 1. pre-compute entries at first iteration only
31 | async def _pre_compute_update(ctx):
32 | if run_data["entries"] is None:
33 | closes = await obs.Close(ctx, max_history=True)
34 | times = await obs.Time(ctx, max_history=True, use_close_time=True)
35 | rsi_v = tulipy.rsi(closes, period=ctx.tentacle.trading_config["period"])
36 | delta = len(closes) - len(rsi_v)
37 | run_data["entries"] = {
38 | times[index + delta]
39 | for index, rsi_val in enumerate(rsi_v)
40 | if rsi_val < ctx.tentacle.trading_config["rsi_value_buy_threshold"]
41 | }
42 | await obs.plot_indicator(ctx, "RSI", times[delta:], rsi_v, run_data["entries"])
43 | if obs.current_live_time(ctx) in run_data["entries"]:
44 | await obs.market(ctx, "buy", amount="10%", stop_loss_offset="-15%", take_profit_offset="25%")
45 | run_data = {
46 | "entries": None,
47 | }
48 | config = {
49 | "period": 10,
50 | "rsi_value_buy_threshold": 28,
51 | }
52 | res = await obs.run(
53 | one_day_btc_usdt_data, _pre_compute_update, config,
54 | enable_logs=False, enable_storage=True
55 | )
56 | # ensure run happened
57 | assert res.backtesting_data is not None
58 | assert res.strategy_config is not None
59 | assert res.independent_backtesting is not None
60 | assert res.bot_id is not None
61 | assert res.report['bot_report']['profitability']['binance'] != 0
62 | assert res.report["bot_report"]['end_portfolio']['binance'] != \
63 | res.report["bot_report"]['starting_portfolio']['binance']
64 | assert res.duration < 10
65 | assert res.candles_count == 1947
66 | await _check_report(res)
67 |
68 | # ensure second run gives the same result
69 | run_data = {
70 | "entries": None,
71 | }
72 | res_2 = await obs.run(
73 | one_day_btc_usdt_data, _pre_compute_update, config,
74 | enable_logs=True, enable_storage=False
75 | )
76 | assert res_2.bot_id != res.bot_id
77 | assert res_2.report['bot_report']['profitability'] == res.report['bot_report']['profitability']
78 | assert res_2.report["bot_report"]['end_portfolio']['binance'] != \
79 | res_2.report["bot_report"]['starting_portfolio']['binance']
80 |
81 | # try with different config
82 | run_data = {
83 | "entries": None,
84 | }
85 | config = {
86 | "period": 10,
87 | "rsi_value_buy_threshold": 10,
88 | }
89 | res_3 = await obs.run(
90 | one_day_btc_usdt_data, _pre_compute_update, config,
91 | enable_logs=False, enable_storage=False
92 | )
93 | assert res_3.bot_id is not None
94 | assert res_3.bot_id != res.bot_id
95 | assert res_3.report['bot_report']['profitability'] != res.report['bot_report']['profitability']
96 | assert res_3.report["bot_report"]['end_portfolio']['binance'] != \
97 | res_3.report["bot_report"]['starting_portfolio']['binance']
98 |
99 | # 2. iteration computed entries at each iteration
100 | async def _iterations_update(ctx):
101 | if obs.current_live_time(ctx) != await obs.current_candle_time(
102 | ctx, use_close_time=True):
103 | return
104 | close = await obs.Close(ctx)
105 | if len(close) <= ctx.tentacle.trading_config["period"]:
106 | return
107 | rsi_v = tulipy.rsi(close, period=ctx.tentacle.trading_config["period"])
108 | if rsi_v[-1] < ctx.tentacle.trading_config["rsi_value_buy_threshold"]:
109 | await obs.market(ctx, "buy", amount="10%", stop_loss_offset="-15%", take_profit_offset="25%")
110 |
111 | res_iteration = await obs.run(
112 | one_day_btc_usdt_data, _iterations_update, config,
113 | enable_logs=False, enable_storage=False
114 | )
115 | # same result as pre_computed with the same config
116 | assert res_iteration.report['bot_report']['profitability'] == res_3.report['bot_report']['profitability']
117 |
118 |
119 | async def _check_report(res):
120 | description = res.describe()
121 | assert str(res.strategy_config) in description
122 | report = "report.html"
123 | await res.plot(report_file=report, show=False)
124 | with open(report) as rep:
125 | report_content = rep.read()
126 | for key, val in res.strategy_config.items():
127 | assert str(key) in report_content
128 | assert str(val) in report_content
129 | assert "BTC/USDT" in report_content
130 | assert "1d" in report_content
131 | assert "Binance" in report_content
132 | os.remove(report)
133 |
--------------------------------------------------------------------------------
/tests/test_util/ExchangeHistoryDataCollector_1673796151.325921.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drakkar-Software/OctoBot-Script/8dbab5bf05abc21b78f3a962319d3f67fa232048/tests/test_util/ExchangeHistoryDataCollector_1673796151.325921.data
--------------------------------------------------------------------------------