17 Commits

Author SHA1 Message Date
Markus
f718249a49 Use pathos. 2021-01-02 17:36:58 +01:00
Markus
f5eec79a33 Merge remote-tracking branch 'origin/master' into stock 2021-01-02 17:16:19 +01:00
Markus
23207abe49 Fixed bug in test. 2020-12-30 13:14:28 +01:00
Markus
95b123b605 Merge remote-tracking branch 'origin/master' into stock 2020-12-27 20:25:02 +01:00
Markus
dd1dab1c5c Individual ratio normalization. 2020-12-07 20:03:47 +01:00
Markus
8cf1e57d50 Merge remote-tracking branch 'origin/master' into stock 2020-12-07 19:58:52 +01:00
Markus
004cd79738 Handle nan in Candle Model and timeframe from one minute generation. 2020-12-04 22:15:56 +01:00
Markus
028c297f72 Helper for nan index array and reinsert. 2020-12-04 20:56:33 +01:00
Markus
dc79f49ef4 Minor fixes. 2020-12-04 16:21:49 +01:00
Markus
5b156fe0e6 Minor fixes. 2020-12-04 16:19:36 +01:00
Markus
d2f33bef7c Added fill absent with np.nan function. 2020-12-04 15:58:45 +01:00
Markus
c369064fd7 Added polygon package. 2020-12-04 15:41:35 +01:00
Markus
df6d49d7de Added polygon driver. 2020-12-04 15:40:00 +01:00
Markus
371b3aeaca Added polygon driver. 2020-12-04 15:30:57 +01:00
Markus
c0ef623887 Added polygon driver. 2020-12-04 15:25:44 +01:00
Markus
3187e8104e Added polygon driver. 2020-12-04 15:22:42 +01:00
Markus
82a323791c Added polygon driver + api_key config + stock_mode setting for drivers. 2020-12-04 14:52:22 +01:00
413 changed files with 9310 additions and 17592 deletions

View File

@@ -1,25 +0,0 @@
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env
pip-log.txt
pip-delete-this-directory.txt
.tox
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
*.log
.git
*.md
!README*.md
README-secret.md
.travis.yml
Dockerfile
docker-compose.yml
.idea
venv

5
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,5 @@
<!--
IMPORTANT: Please open an issue ONLY if you find something wrong with the source code. For questions and feedback use Jesse Forum:
https://forum.jesse.trade/
-->

View File

@@ -1,34 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
<!--
IMPORTANT: Please open an issue ONLY if you find something wrong with the source code. For questions and feedback use Discord (https://jesse.trade/discord). Also make sure to give the documentation (https://docs.jesse.trade/) and FAQ (https://jesse.trade/help) a good read to eliminate the possibility of causing the problem due to wrong usage. Make sure you are using the most recent version `pip show jesse` and updated all requirements `pip install -r https://raw.githubusercontent.com/jesse-ai/jesse/master/requirements.txt`.
-->
**Describe the bug**
A clear and concise description of what the bug is. Include the whole error message / traceback.
**To Reproduce**
Steps to reproduce the behavior:
1. Include your routes.py, config.py (make sure to remove personal information) and if possible your strategy code (if you want to keep it private - contact us on Discord directly).
2. Explain the steps you do that lead to the error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Enviroment (please complete the following information):**
- OS: [e.g. iOS, Windows, Ubuntu]
- Version [use `pip show jesse`]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,24 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
<!---
Make sure to check the roadmap (https://docs.jesse.trade/docs/roadmap.html) and the Trello boards linked there whether your idea is already listed and give Jesse's documentation a good read to make sure you don't request something that's already possible. If possible use Discord (https://jesse.trade/discord) to discuss your ideas with the community.
-->
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

17
.github/stale.yml vendored
View File

@@ -1,17 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View File

@@ -1,98 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '0 0 1 * *'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Cache pip
uses: actions/cache@v2
with:
path: ${{ matrix.path }}
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install ta-lib
run: |
if ([ "$RUNNER_OS" = "macOS" ]); then
brew install ta-lib
fi
if ([ "$RUNNER_OS" = "Linux" ]); then
if [ ! -f "$GITHUB_WORKSPACE/ta-lib/src" ]; then wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz -q && tar -xzf ta-lib-0.4.0-src.tar.gz; fi
cd ta-lib/
./configure --prefix=/usr
if [ ! -f "$HOME/ta-lib/src" ]; then make; fi
sudo make install
cd
fi
if ([ "$RUNNER_OS" = "Windows" ]); then
curl -sL http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-msvc.zip -o $GITHUB_WORKSPACE/ta-lib.zip --create-dirs && 7z x $GITHUB_WORKSPACE/ta-lib.zip -o/c/ta-lib && mv /c/ta-lib/ta-lib/* /c/ta-lib/ && rm -rf /c/ta-lib/ta-lib && cd /c/ta-lib/c/make/cdr/win32/msvc && nmake
fi
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
pip install numba
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -1,97 +0,0 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Python application
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ${{matrix.os}}
strategy:
matrix:
# os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest]
include:
- os: ubuntu-latest
path: ~/.cache/pip
#- os: macos-latest
# path: ~/Library/Caches/pip
#- os: windows-latest
# path: ~\AppData\Local\pip\Cache
python-version: [3.8, 3.9, '3.10']
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Cache pip
uses: actions/cache@v2
id: cache
with:
path: ${{ matrix.path }}
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Set up ta-lib dir
if: ${{ runner.os == 'Linux' }}
run: |
mkdir -p $HOME/.local/ta-lib
echo "LD_LIBRARY_PATH=$HOME/.local/ta-lib/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV
echo "TA_INCLUDE_PATH=$HOME/.local/ta-lib/include" >> $GITHUB_ENV
echo "TA_LIBRARY_PATH=$HOME/.local/ta-lib/lib" >> $GITHUB_ENV
- name: Set up ta-lib cache
if: ${{ runner.os == 'Linux' }}
uses: actions/cache@v2
id: talib-cache
with:
path: |
~/.local/ta-lib/lib
~/.local/ta-lib/include
key: talib-cache-v0.4.0
- name: Install ta-lib mac / windows
run: |
if ([ "$RUNNER_OS" = "macOS" ]); then
brew install ta-lib
fi
if ([ "$RUNNER_OS" = "Windows" ]); then
curl -sL http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-msvc.zip -o $GITHUB_WORKSPACE/ta-lib.zip --create-dirs && 7z x $GITHUB_WORKSPACE/ta-lib.zip -o/c/ta-lib && mv /c/ta-lib/ta-lib/* /c/ta-lib/ && rm -rf /c/ta-lib/ta-lib && cd /c/ta-lib/c/make/cdr/win32/msvc && nmake
fi
- name: Install ta-lib Linux
if: steps.talib-cache.outputs.cache-hit != 'true'
run: |
wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz -q && tar -xzf ta-lib-0.4.0-src.tar.gz
cd ta-lib/
./configure --prefix=$HOME/.local/ta-lib
make
sudo make install
cd
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ ! ${{ matrix.python-version }} = "3.10"]; then pip install numba; fi
pip install -e . -U
# - name: Lint with flake8
# run: |
# stop the build if there are Python syntax errors or undefined names
# flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
# flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pip install pytest
pytest

157
.gitignore vendored
View File

@@ -1,151 +1,20 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# IDE
/_pycache_
/.pytest_cache
/.vscode
/jesse.egg-info
/.idea
.DS_Store
/storage/*.key
/storage/*.sqlite
/storage/*.gz
.DS_Store
/.vagrant
*.pyc
/__pycache__
/venv
__pycache__
.vscode
/dist
/build
/*.egg-info
/*.egg
testing-*.py
/storage/full-reports/*.html
/storage/logs/*

View File

@@ -1,5 +1,5 @@
language: python
dist: focal
dist: bionic
cache:
directories:
- $HOME/.cache/pip

View File

@@ -1,28 +0,0 @@
FROM python:3.9-slim
ENV PYTHONUNBUFFERED 1
RUN apt-get update \
&& apt-get -y install git build-essential libssl-dev \
&& apt-get clean \
&& pip install --upgrade pip
RUN pip3 install Cython numpy
# Prepare environment
RUN mkdir /jesse-docker
WORKDIR /jesse-docker
# Install TA-lib
COPY docker_build_helpers/* /tmp/
RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib*
ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
# Install dependencies
COPY requirements.txt /jesse-docker
RUN pip3 install -r requirements.txt
# Build
COPY . /jesse-docker
RUN pip3 install -e .
WORKDIR /home

View File

@@ -1,28 +0,0 @@
FROM python:3.9-slim
ENV PYTHONUNBUFFERED 1
RUN apt-get update \
&& apt-get -y install build-essential libssl-dev \
&& apt-get clean \
&& pip install --upgrade pip
RUN pip3 install Cython numpy codecov pytest-cov
# Prepare environment
RUN mkdir /jesse-docker
WORKDIR /jesse-docker
# Install TA-lib
COPY docker_build_helpers/* /tmp/
RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib*
ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
# Install dependencies
COPY requirements.txt /jesse-docker
RUN pip3 install -r requirements.txt
# Build
COPY . /jesse-docker
RUN pip3 install -e .
ENTRYPOINT pytest --cov=./ # && codecov

View File

@@ -1,2 +0,0 @@
include jesse/static/*
include jesse/static/**/*

View File

@@ -8,8 +8,8 @@
[![Website](https://img.shields.io/badge/Website-Start%20here!-9cf)](https://jesse.trade)
[![Docs](https://img.shields.io/badge/Docs-Learn%20how!-red)](https://docs.jesse.trade)
[![FAQ](https://img.shields.io/badge/FAQ-Find%20answers!-yellow)](https://jesse.trade/help)
[![Chat](https://img.shields.io/discord/771690508413829141)](https://jesse.trade/discord)
[![Docs](https://img.shields.io/discord/771690508413829141)](https://jesse.trade/discord)
[![Forum](https://img.shields.io/badge/Forum-Join%20us!-brightgreen)](https://forum.jesse.trade)
[![Blog](https://img.shields.io/badge/Blog-Get%20the%20news!-blueviolet)](https://jesse.trade/blog)
---
Jesse is an advanced crypto trading framework which aims to simplify researching and defining trading strategies.
@@ -20,11 +20,6 @@ In fact, it is so simple that in case you already know Python, you can get start
[Here](https://docs.jesse.trade/docs/) you can read more about why Jesse's features.
## Sponsored Promotion
[![TokenBot](https://raw.githubusercontent.com/jesse-ai/jesse/master/assets/TokenBot-Jesse-banner.png)](https://tokenbot.com/?utm_source=github&utm_medium=jesse&utm_campaign=algodevs)
## Getting Started
Head over to the "getting started" section of the [documentation](https://docs.jesse.trade/docs/getting-started). The
documentation is short yet very informative.
@@ -87,7 +82,9 @@ And here are generated charts:
This is the very initial release. There's way more. Subscribe to our mailing list at [jesse.trade](https://jesse.trade) to get the good stuff as soon they're released. Don't worry, We won't send you spam. Pinky promise.
## Community
I created a [discord server](https://jesse.trade/discord) for Jesse users to discuss algo-trading. It's a warm place to share ideas, and help each other out.
We've created a [community](http://forum.jesse.trade/) for Jesse users to discuss algo-trading. It's a warm place to share ideas, and help each other out.
**[update]:** We now have a [discord server](https://discord.gg/nztUFbMnF5) to discuss Jesse and trading in general. Make sure to join.
## How to contribute
Thank you for your interest in contributing to the project. Before starting to work on a PR, please make sure that it isn't under the "in progress" column in our [Github project page](https://github.com/jesse-ai/jesse/projects/2). In case you want to help but don't know what tasks we need help for, checkout the "todo" column.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

View File

@@ -1,17 +0,0 @@
if [ -z "$1" ]; then
INSTALL_LOC=/usr/local
else
INSTALL_LOC=${1}
fi
echo "Installing to ${INSTALL_LOC}"
if [ ! -f "${INSTALL_LOC}/lib/libta_lib.a" ]; then
tar zxvf ta-lib-0.4.0-src.tar.gz
cd ta-lib \
&& sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h \
&& ./configure --prefix=${INSTALL_LOC}/ \
&& make \
&& which sudo && sudo make install || make install \
&& echo "export LD_LIBRARY_PATH=/usr/local/lib" >> /root/.bashrc
else
echo "TA-lib already installed, skipping installation"
fi

View File

@@ -1,495 +1,500 @@
import asyncio
import json
import os
import warnings
from typing import Optional
import sys
from pydoc import locate
import click
import pkg_resources
from fastapi import BackgroundTasks, Query, Header
from starlette.websockets import WebSocket, WebSocketDisconnect
from fastapi.responses import JSONResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from jesse.services import auth as authenticator
from jesse.services.redis import async_redis, async_publish, sync_publish
from jesse.services.web import fastapi_app, BacktestRequestJson, ImportCandlesRequestJson, CancelRequestJson, \
LoginRequestJson, ConfigRequestJson, LoginJesseTradeRequestJson, NewStrategyRequestJson, FeedbackRequestJson, \
ReportExceptionRequestJson, OptimizationRequestJson
import uvicorn
from asyncio import Queue
import jesse.helpers as jh
import time
# to silent stupid pandas warnings
import jesse.helpers as jh
# Hide the "FutureWarning: pandas.util.testing is deprecated." caused by empyrical
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
# Python version validation.
if jh.python_version() < 3.7:
print(
jh.color(
'Jesse requires Python version above 3.7. Yours is {}'.format(jh.python_version()),
'red'
)
)
# variable to know if the live trade plugin is installed
HAS_LIVE_TRADE_PLUGIN = True
try:
import jesse_live
except ModuleNotFoundError:
HAS_LIVE_TRADE_PLUGIN = False
# fix directory issue
sys.path.insert(0, os.getcwd())
ls = os.listdir('.')
is_jesse_project = 'strategies' in ls and 'config.py' in ls and 'storage' in ls and 'routes.py' in ls
def validate_cwd() -> None:
def validate_cwd():
"""
make sure we're in a Jesse project
"""
if not jh.is_jesse_project():
if not is_jesse_project:
print(
jh.color(
'Current directory is not a Jesse project. You must run commands from the root of a Jesse project. Read this page for more info: https://docs.jesse.trade/docs/getting-started/#create-a-new-jesse-project',
'Current directory is not a Jesse project. You must run commands from the root of a Jesse project.',
'red'
)
)
os._exit(1)
# print(os.path.dirname(jesse))
JESSE_DIR = os.path.dirname(os.path.realpath(__file__))
def inject_local_config():
"""
injects config from local config file
"""
local_config = locate('config.config')
from jesse.config import set_config
set_config(local_config)
# load homepage
@fastapi_app.get("/")
async def index():
return FileResponse(f"{JESSE_DIR}/static/index.html")
def inject_local_routes():
"""
injects routes from local routes folder
"""
local_router = locate('routes')
from jesse.routes import router
router.set_routes(local_router.routes)
router.set_extra_candles(local_router.extra_candles)
@fastapi_app.post("/terminate-all")
async def terminate_all(authorization: Optional[str] = Header(None)):
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
from jesse.services.multiprocessing import process_manager
process_manager.flush()
return JSONResponse({'message': 'terminating all tasks...'})
# inject local files
if is_jesse_project:
inject_local_config()
inject_local_routes()
@fastapi_app.post("/shutdown")
async def shutdown(background_tasks: BackgroundTasks, authorization: Optional[str] = Header(None)):
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
def register_custom_exception_handler():
"""
background_tasks.add_task(jh.terminate_app)
return JSONResponse({'message': 'Shutting down...'})
:return:
"""
import sys
import threading
import traceback
import logging
from jesse.services import logger as jesse_logger
import click
from jesse import exceptions
log_format = "%(message)s"
os.makedirs('storage/logs', exist_ok=True)
@fastapi_app.post("/auth")
def auth(json_request: LoginRequestJson):
return authenticator.password_to_token(json_request.password)
if jh.is_livetrading():
logging.basicConfig(filename='storage/logs/live-trade.txt', level=logging.INFO, filemode='w', format=log_format)
elif jh.is_paper_trading():
logging.basicConfig(filename='storage/logs/paper-trade.txt', level=logging.INFO, filemode='w',
format=log_format)
elif jh.is_collecting_data():
logging.basicConfig(filename='storage/logs/collect.txt', level=logging.INFO, filemode='w',
format=log_format)
elif jh.is_optimizing():
logging.basicConfig(filename='storage/logs/optimize.txt', level=logging.INFO, filemode='w',
format=log_format)
else:
logging.basicConfig(level=logging.INFO)
# main thread
def handle_exception(exc_type, exc_value, exc_traceback):
"""
@fastapi_app.post("/make-strategy")
def make_strategy(json_request: NewStrategyRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
:param exc_type:
:param exc_value:
:param exc_traceback:
:return:
"""
if issubclass(exc_type, KeyboardInterrupt):
sys.excepthook(exc_type, exc_value, exc_traceback)
return
from jesse.services import strategy_maker
return strategy_maker.generate(json_request.name)
# handle Breaking exceptions
if exc_type in [
exceptions.ConfigException, exceptions.RouteNotFound, exceptions.InvalidRoutes,
exceptions.CandleNotFoundInDatabase
]:
click.clear()
print('=' * 30 + ' EXCEPTION TRACEBACK:')
traceback.print_tb(exc_traceback, file=sys.stdout)
print("=" * 73)
print(
'\n',
jh.color('Uncaught Exception:', 'red'),
jh.color('{}: {}'.format(exc_type.__name__, exc_value), 'yellow')
)
return
@fastapi_app.post("/feedback")
def feedback(json_request: FeedbackRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
from jesse.services import jesse_trade
return jesse_trade.feedback(json_request.description, json_request.email)
@fastapi_app.post("/report-exception")
def report_exception(json_request: ReportExceptionRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
from jesse.services import jesse_trade
return jesse_trade.report_exception(
json_request.description,
json_request.traceback,
json_request.mode,
json_request.attach_logs,
json_request.session_id,
json_request.email,
has_live=HAS_LIVE_TRADE_PLUGIN
)
@fastapi_app.post("/get-config")
def get_config(json_request: ConfigRequestJson, authorization: Optional[str] = Header(None)):
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
from jesse.modes.data_provider import get_config as gc
return JSONResponse({
'data': gc(json_request.current_config, has_live=HAS_LIVE_TRADE_PLUGIN)
}, status_code=200)
@fastapi_app.post("/update-config")
def update_config(json_request: ConfigRequestJson, authorization: Optional[str] = Header(None)):
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
from jesse.modes.data_provider import update_config as uc
uc(json_request.current_config)
return JSONResponse({'message': 'Updated configurations successfully'}, status_code=200)
@fastapi_app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket, token: str = Query(...)):
from jesse.services.multiprocessing import process_manager
from jesse.services.env import ENV_VALUES
if not authenticator.is_valid_token(token):
return
await websocket.accept()
queue = Queue()
ch, = await async_redis.psubscribe(f"{ENV_VALUES['APP_PORT']}:channel:*")
async def echo(q):
while True:
msg = await q.get()
msg = json.loads(msg)
msg['id'] = process_manager.get_client_id(msg['id'])
await websocket.send_json(
msg
# send notifications if it's a live session
if jh.is_live():
jesse_logger.error(
'{}: {}'.format(exc_type.__name__, exc_value)
)
async def reader(channel, q):
async for ch, message in channel.iter():
# modify id and set the one that the font-end knows
await q.put(message)
if jh.is_live() or jh.is_collecting_data():
logging.error("Uncaught Exception:", exc_info=(exc_type, exc_value, exc_traceback))
else:
print('=' * 30 + ' EXCEPTION TRACEBACK:')
traceback.print_tb(exc_traceback, file=sys.stdout)
print("=" * 73)
print(
'\n',
jh.color('Uncaught Exception:', 'red'),
jh.color('{}: {}'.format(exc_type.__name__, exc_value), 'yellow')
)
asyncio.get_running_loop().create_task(reader(ch, queue))
asyncio.get_running_loop().create_task(echo(queue))
if jh.is_paper_trading():
print(
jh.color(
'An uncaught exception was raised. Check the log file at:\n{}'.format(
'storage/logs/paper-trade.txt'
),
'red'
)
)
elif jh.is_livetrading():
print(
jh.color(
'An uncaught exception was raised. Check the log file at:\n{}'.format(
'storage/logs/live-trade.txt'
),
'red'
)
)
elif jh.is_collecting_data():
print(
jh.color(
'An uncaught exception was raised. Check the log file at:\n{}'.format(
'storage/logs/collect.txt'
),
'red'
)
)
try:
while True:
# just so WebSocketDisconnect would be raised on connection close
await websocket.receive_text()
except WebSocketDisconnect:
await async_redis.punsubscribe(f"{ENV_VALUES['APP_PORT']}:channel:*")
print('Websocket disconnected')
sys.excepthook = handle_exception
# other threads
if jh.python_version() >= 3.8:
def handle_thread_exception(args):
"""
:param args:
:return:
"""
if args.exc_type == SystemExit:
return
# handle Breaking exceptions
if args.exc_type in [
exceptions.ConfigException, exceptions.RouteNotFound, exceptions.InvalidRoutes,
exceptions.CandleNotFoundInDatabase
]:
click.clear()
print('=' * 30 + ' EXCEPTION TRACEBACK:')
traceback.print_tb(args.exc_traceback, file=sys.stdout)
print("=" * 73)
print(
'\n',
jh.color('Uncaught Exception:', 'red'),
jh.color('{}: {}'.format(args.exc_type.__name__, args.exc_value), 'yellow')
)
return
# send notifications if it's a live session
if jh.is_live():
jesse_logger.error(
'{}: {}'.format(args.exc_type.__name__, args.exc_value)
)
if jh.is_live() or jh.is_collecting_data():
logging.error("Uncaught Exception:", exc_info=(args.exc_type, args.exc_value, args.exc_traceback))
else:
print('=' * 30 + ' EXCEPTION TRACEBACK:')
traceback.print_tb(args.exc_traceback, file=sys.stdout)
print("=" * 73)
print(
'\n',
jh.color('Uncaught Exception:', 'red'),
jh.color('{}: {}'.format(args.exc_type.__name__, args.exc_value), 'yellow')
)
if jh.is_paper_trading():
print(
jh.color(
'An uncaught exception was raised. Check the log file at:\n{}'.format(
'storage/logs/paper-trade.txt'
),
'red'
)
)
elif jh.is_livetrading():
print(
jh.color(
'An uncaught exception was raised. Check the log file at:\n{}'.format(
'storage/logs/live-trade.txt'
),
'red'
)
)
elif jh.is_collecting_data():
print(
jh.color(
'An uncaught exception was raised. Check the log file at:\n{}'.format(
'storage/logs/collect.txt'
),
'red'
)
)
threading.excepthook = handle_thread_exception
# create a Click group
@click.group()
@click.version_option(pkg_resources.get_distribution("jesse").version)
def cli() -> None:
def cli():
pass
@cli.command()
@click.option(
'--strict/--no-strict', default=True,
help='Default is the strict mode which will raise an exception if the values for license is not set.'
)
def install_live(strict: bool) -> None:
from jesse.services.installer import install
install(HAS_LIVE_TRADE_PLUGIN, strict)
@cli.command()
def run() -> None:
@click.argument('exchange', required=True, type=str)
@click.argument('symbol', required=True, type=str)
@click.argument('start_date', required=True, type=str)
def import_candles(exchange, symbol, start_date):
"""
imports historical candles from exchange
"""
validate_cwd()
from jesse.config import config
config['app']['trading_mode'] = 'import-candles'
# run all the db migrations
from jesse.services.migrator import run as run_migrations
import peewee
try:
run_migrations()
except peewee.OperationalError:
sleep_seconds = 10
print(f"Database wasn't ready. Sleep for {sleep_seconds} seconds and try again.")
time.sleep(sleep_seconds)
run_migrations()
register_custom_exception_handler()
# read port from .env file, if not found, use default
from jesse.services.env import ENV_VALUES
if 'APP_PORT' in ENV_VALUES:
port = int(ENV_VALUES['APP_PORT'])
else:
port = 9000
# run the main application
uvicorn.run(fastapi_app, host="0.0.0.0", port=port, log_level="info")
@fastapi_app.post('/general-info')
def general_info(authorization: Optional[str] = Header(None)) -> JSONResponse:
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
from jesse.modes import data_provider
try:
data = data_provider.get_general_info(has_live=HAS_LIVE_TRADE_PLUGIN)
except Exception as e:
return JSONResponse({
'error': str(e)
}, status_code=500)
return JSONResponse(
data,
status_code=200
)
@fastapi_app.post('/import-candles')
def import_candles(request_json: ImportCandlesRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
from jesse.services.multiprocessing import process_manager
validate_cwd()
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
from jesse.services import db
from jesse.modes import import_candles_mode
process_manager.add_task(
import_candles_mode.run, 'candles-' + str(request_json.id), request_json.exchange, request_json.symbol,
request_json.start_date, True
)
import_candles_mode.run(exchange, symbol, start_date)
return JSONResponse({'message': 'Started importing candles...'}, status_code=202)
db.close_connection()
@fastapi_app.delete("/import-candles")
def cancel_import_candles(request_json: CancelRequestJson, authorization: Optional[str] = Header(None)):
from jesse.services.multiprocessing import process_manager
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
process_manager.cancel_process('candles-' + request_json.id)
return JSONResponse({'message': f'Candles process with ID of {request_json.id} was requested for termination'}, status_code=202)
@fastapi_app.post("/backtest")
def backtest(request_json: BacktestRequestJson, authorization: Optional[str] = Header(None)):
from jesse.services.multiprocessing import process_manager
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
@cli.command()
@click.argument('start_date', required=True, type=str)
@click.argument('finish_date', required=True, type=str)
@click.option('--debug/--no-debug', default=False,
help='Displays logging messages instead of the progressbar. Used for debugging your strategy.')
@click.option('--csv/--no-csv', default=False,
help='Outputs a CSV file of all executed trades on completion.')
@click.option('--json/--no-json', default=False,
help='Outputs a JSON file of all executed trades on completion.')
@click.option('--fee/--no-fee', default=True, help='You can use "--no-fee" as a quick way to set trading fee to zero.')
@click.option('--chart/--no-chart', default=False,
help='Generates charts of daily portfolio balance and assets price change. Useful for a visual comparision of your portfolio against the market.')
@click.option('--tradingview/--no-tradingview', default=False,
help="Generates an output that can be copy-and-pasted into tradingview.com's pine-editor too see the trades in their charts.")
def backtest(start_date, finish_date, debug, csv, json, fee, chart, tradingview):
"""
backtest mode. Enter in "YYYY-MM-DD" "YYYY-MM-DD"
"""
validate_cwd()
from jesse.modes.backtest_mode import run as run_backtest
from jesse.config import config
config['app']['trading_mode'] = 'backtest'
process_manager.add_task(
run_backtest,
'backtest-' + str(request_json.id),
request_json.debug_mode,
request_json.config,
request_json.routes,
request_json.extra_routes,
request_json.start_date,
request_json.finish_date,
None,
request_json.export_chart,
request_json.export_tradingview,
request_json.export_full_reports,
request_json.export_csv,
request_json.export_json
)
register_custom_exception_handler()
return JSONResponse({'message': 'Started backtesting...'}, status_code=202)
from jesse.services import db
from jesse.modes import backtest_mode
from jesse.services.selectors import get_exchange
# debug flag
config['app']['debug_mode'] = debug
# fee flag
if not fee:
for e in config['app']['trading_exchanges']:
config['env']['exchanges'][e]['fee'] = 0
get_exchange(e).fee = 0
backtest_mode.run(start_date, finish_date, chart=chart, tradingview=tradingview, csv=csv, json=json)
db.close_connection()
@fastapi_app.post("/optimization")
async def optimization(request_json: OptimizationRequestJson, authorization: Optional[str] = Header(None)):
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
from jesse.services.multiprocessing import process_manager
@cli.command()
@click.argument('start_date', required=True, type=str)
@click.argument('finish_date', required=True, type=str)
@click.argument('optimal_total', required=True, type=int)
@click.option(
'--cpu', default=0, show_default=True,
help='The number of CPU cores that Jesse is allowed to use. If set to 0, it will use as many as is available on your machine.')
@click.option(
'--debug/--no-debug', default=False,
help='Displays detailed logs about the genetics algorithm. Use it if you are interested int he genetics algorithm.'
)
def optimize(start_date, finish_date, optimal_total, cpu, debug):
"""
tunes the hyper-parameters of your strategy
"""
validate_cwd()
from jesse.config import config
config['app']['trading_mode'] = 'optimize'
from jesse.modes.optimize_mode import run as run_optimization
register_custom_exception_handler()
process_manager.add_task(
run_optimization,
'optimize-' + str(request_json.id),
request_json.debug_mode,
request_json.config,
request_json.routes,
request_json.extra_routes,
request_json.start_date,
request_json.finish_date,
request_json.optimal_total,
request_json.export_csv,
request_json.export_json
)
# debug flag
config['app']['debug_mode'] = debug
# optimize_mode(start_date, finish_date, optimal_total, cpu, csv, json)
from jesse.modes.optimize_mode import optimize_mode
return JSONResponse({'message': 'Started optimization...'}, status_code=202)
optimize_mode(start_date, finish_date, optimal_total, cpu)
@fastapi_app.delete("/optimization")
def cancel_optimization(request_json: CancelRequestJson, authorization: Optional[str] = Header(None)):
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
from jesse.services.multiprocessing import process_manager
process_manager.cancel_process('optimize-' + request_json.id)
return JSONResponse({'message': f'Optimization process with ID of {request_json.id} was requested for termination'}, status_code=202)
@fastapi_app.get("/download/{mode}/{file_type}/{session_id}")
def download(mode: str, file_type: str, session_id: str, token: str = Query(...)):
@cli.command()
@click.argument('name', required=True, type=str)
def make_strategy(name):
"""
Log files require session_id because there is one log per each session. Except for the optimize mode
generates a new strategy folder from jesse/strategies/ExampleStrategy
"""
if not authenticator.is_valid_token(token):
return authenticator.unauthorized_response()
validate_cwd()
from jesse.config import config
from jesse.modes import data_provider
config['app']['trading_mode'] = 'make-strategy'
return data_provider.download_file(mode, file_type, session_id)
register_custom_exception_handler()
from jesse.services import strategy_maker
strategy_maker.generate(name)
@fastapi_app.get("/download/optimize/log")
def download_optimization_log(token: str = Query(...)):
@cli.command()
@click.argument('name', required=True, type=str)
def make_project(name):
"""
Optimization logs don't have have session ID
generates a new strategy folder from jesse/strategies/ExampleStrategy
"""
if not authenticator.is_valid_token(token):
return authenticator.unauthorized_response()
from jesse.config import config
from jesse.modes import data_provider
config['app']['trading_mode'] = 'make-project'
return data_provider.download_file('optimize', 'log')
register_custom_exception_handler()
from jesse.services import project_maker
project_maker.generate(name)
@fastapi_app.delete("/backtest")
def cancel_backtest(request_json: CancelRequestJson, authorization: Optional[str] = Header(None)):
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
@cli.command()
@click.option('--dna/--no-dna', default=False, help='Translates DNA into parameters. Used in optimize mode only')
def routes(dna):
"""
lists all routes
"""
validate_cwd()
from jesse.config import config
from jesse.services.multiprocessing import process_manager
config['app']['trading_mode'] = 'routes'
process_manager.cancel_process('backtest-' + request_json.id)
register_custom_exception_handler()
return JSONResponse({'message': f'Backtest process with ID of {request_json.id} was requested for termination'}, status_code=202)
from jesse.modes import routes_mode
routes_mode.run(dna)
@fastapi_app.on_event("shutdown")
def shutdown_event():
from jesse.services.db import database
database.close_connection()
if HAS_LIVE_TRADE_PLUGIN:
from jesse.services.web import fastapi_app, LiveRequestJson, LiveCancelRequestJson, GetCandlesRequestJson, \
GetLogsRequestJson, GetOrdersRequestJson
from jesse.services import auth as authenticator
@fastapi_app.post("/live")
def live(request_json: LiveRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
from jesse import validate_cwd
# dev_mode is used only by developers so it doesn't have to be a supported parameter
dev_mode: bool = False
if 'plugins' in ls:
@cli.command()
def collect():
"""
fetches streamed market data such as tickers, trades, and orderbook from
the WS connection and stores them into the database for later research.
"""
validate_cwd()
# set trading mode
from jesse.config import config
config['app']['trading_mode'] = 'collect'
register_custom_exception_handler()
from plugins.live.collect_mode import run
run()
@cli.command()
@click.option('--testdrive/--no-testdrive', default=False)
@click.option('--debug/--no-debug', default=False)
@click.option('--dev/--no-dev', default=False)
@click.option('--fee/--no-fee', default=True)
def live(testdrive, debug, dev, fee):
"""
trades in real-time on exchange with REAL money
"""
validate_cwd()
# set trading mode
from jesse.config import config
config['app']['trading_mode'] = 'livetrade'
config['app']['is_test_driving'] = testdrive
register_custom_exception_handler()
# debug flag
config['app']['debug_mode'] = debug
from plugins.live import init
from jesse.services.selectors import get_exchange
# fee flag
if not fee:
for e in config['app']['trading_exchanges']:
config['env']['exchanges'][e]['fee'] = 0
get_exchange(e).fee = 0
# inject live config
init(config)
# execute live session
from jesse_live import live_mode
from jesse.services.multiprocessing import process_manager
trading_mode = 'livetrade' if request_json.paper_mode is False else 'papertrade'
process_manager.add_task(
live_mode.run,
f'{trading_mode}-' + str(request_json.id),
request_json.debug_mode,
dev_mode,
request_json.config,
request_json.routes,
request_json.extra_routes,
trading_mode,
)
mode = 'live' if request_json.paper_mode is False else 'paper'
return JSONResponse({'message': f"Started {mode} trading..."}, status_code=202)
from plugins.live.live_mode import run
run(dev)
@fastapi_app.delete("/live")
def cancel_backtest(request_json: LiveCancelRequestJson, authorization: Optional[str] = Header(None)):
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
from jesse.services.multiprocessing import process_manager
trading_mode = 'livetrade' if request_json.paper_mode is False else 'papertrade'
process_manager.cancel_process(f'{trading_mode}-' + request_json.id)
return JSONResponse({'message': f'Live process with ID of {request_json.id} terminated.'}, status_code=200)
@fastapi_app.post('/get-candles')
def get_candles(json_request: GetCandlesRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
from jesse import validate_cwd
@cli.command()
@click.option('--debug/--no-debug', default=False)
@click.option('--dev/--no-dev', default=False)
@click.option('--fee/--no-fee', default=True)
def paper(debug, dev, fee):
"""
trades in real-time on exchange with PAPER money
"""
validate_cwd()
from jesse.modes.data_provider import get_candles as gc
# set trading mode
from jesse.config import config
config['app']['trading_mode'] = 'papertrade'
arr = gc(json_request.exchange, json_request.symbol, json_request.timeframe)
register_custom_exception_handler()
return JSONResponse({
'id': json_request.id,
'data': arr
}, status_code=200)
# debug flag
config['app']['debug_mode'] = debug
from plugins.live import init
from jesse.services.selectors import get_exchange
@fastapi_app.post('/get-logs')
def get_logs(json_request: GetLogsRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
# fee flag
if not fee:
for e in config['app']['trading_exchanges']:
config['env']['exchanges'][e]['fee'] = 0
get_exchange(e).fee = 0
from jesse_live.services.data_provider import get_logs as gl
# inject live config
init(config)
arr = gl(json_request.session_id, json_request.type)
return JSONResponse({
'id': json_request.id,
'data': arr
}, status_code=200)
@fastapi_app.post('/get-orders')
def get_orders(json_request: GetOrdersRequestJson, authorization: Optional[str] = Header(None)) -> JSONResponse:
if not authenticator.is_valid_token(authorization):
return authenticator.unauthorized_response()
from jesse_live.services.data_provider import get_orders as go
arr = go(json_request.session_id)
return JSONResponse({
'id': json_request.id,
'data': arr
}, status_code=200)
# Mount static files.Must be loaded at the end to prevent overlapping with API endpoints
fastapi_app.mount("/", StaticFiles(directory=f"{JESSE_DIR}/static"), name="static")
# execute live session
from plugins.live.live_mode import run
run(dev)

View File

@@ -1,11 +1,16 @@
import jesse.helpers as jh
config = {
# these values are related to the user's environment
'env': {
'databases': {
'postgres_host': '127.0.0.1',
'postgres_name': 'jesse_db',
'postgres_port': 5432,
'postgres_username': 'jesse_user',
'postgres_password': 'password',
},
'caching': {
'driver': 'pickle'
'driver': None
},
'logging': {
@@ -25,175 +30,89 @@ config = {
'Sandbox': {
'fee': 0,
'type': 'spot',
# used only in futures trading
# used only in margin trading
'settlement_currency': 'USDT',
# accepted values are: 'cross' and 'isolated'
'futures_leverage_mode': 'cross',
# 1x, 2x, 10x, 50x, etc. Enter as integers
'futures_leverage': 1,
'assets': [
{'asset': 'USDT', 'balance': 10_000},
{'asset': 'USDT', 'balance': 10000},
{'asset': 'BTC', 'balance': 0},
],
},
'Bybit Perpetual': {
'fee': 0.00075,
# backtest mode only: accepted are 'spot' and 'futures'
# 'spot' support is currently very limited - you can use 'futures' with leverage 1 for now
'type': 'futures',
# futures mode only
'settlement_currency': 'USDT',
# accepted values are: 'cross' and 'isolated'
'futures_leverage_mode': 'cross',
# 1x, 2x, 10x, 50x, etc. Enter as integers
'futures_leverage': 1,
'assets': [
{'asset': 'USDT', 'balance': 10_000},
],
},
'Testnet Bybit Perpetual': {
'fee': 0.00075,
# backtest mode only: accepted are 'spot' and 'futures'
# 'spot' support is currently very limited - you can use 'futures' with leverage 1 for now
'type': 'futures',
# futures mode only
'settlement_currency': 'USDT',
# accepted values are: 'cross' and 'isolated'
'futures_leverage_mode': 'cross',
# 1x, 2x, 10x, 50x, etc. Enter as integers
'futures_leverage': 1,
'assets': [
{'asset': 'USDT', 'balance': 10_000},
],
},
# https://ftx.com/markets/future
'FTX Futures': {
'fee': 0.0006,
# backtest mode only: accepted are 'spot' and 'futures'
# 'spot' support is currently very limited - you can use 'futures' with leverage 1 for now
'type': 'futures',
# futures mode only
'settlement_currency': 'USD',
# accepted values are: 'cross' and 'isolated'
'futures_leverage_mode': 'cross',
# 1x, 2x, 10x, 20x, etc. Enter as integers
'futures_leverage': 1,
'assets': [
{'asset': 'USD', 'balance': 10_000},
],
},
# https://www.bitfinex.com
'Bitfinex': {
'fee': 0.002,
# backtest mode only: accepted are 'spot' and 'futures'
'type': 'futures',
# futures mode only
'type': 'margin',
# used only in margin trading
'settlement_currency': 'USD',
# accepted values are: 'cross' and 'isolated'
'futures_leverage_mode': 'cross',
# 1x, 2x, 10x, 50x, etc. Enter as integers
'futures_leverage': 1,
'fee': 0.002,
'assets': [
{'asset': 'USDT', 'balance': 10_000},
{'asset': 'USD', 'balance': 10_000},
{'asset': 'USDT', 'balance': 10000},
{'asset': 'USD', 'balance': 10000},
{'asset': 'BTC', 'balance': 0},
],
},
# https://www.binance.com
'Binance': {
'fee': 0.001,
# backtest mode only: accepted are 'spot' and 'futures'
'type': 'futures',
# futures mode only
'type': 'spot',
# used only in margin trading
'settlement_currency': 'USDT',
# accepted values are: 'cross' and 'isolated'
'futures_leverage_mode': 'cross',
# 1x, 2x, 10x, 50x, etc. Enter as integers
'futures_leverage': 1,
'fee': 0.001,
'assets': [
{'asset': 'USDT', 'balance': 10_000},
{'asset': 'USDT', 'balance': 10000},
{'asset': 'BTC', 'balance': 0},
],
},
# https://www.binance.com
'Binance Futures': {
'fee': 0.0004,
# backtest mode only: accepted are 'spot' and 'futures'
'type': 'futures',
# futures mode only
'type': 'margin',
# used only in margin trading
'settlement_currency': 'USDT',
# accepted values are: 'cross' and 'isolated'
'futures_leverage_mode': 'cross',
# 1x, 2x, 10x, 50x, etc. Enter as integers
'futures_leverage': 1,
'fee': 0.0002,
'assets': [
{'asset': 'USDT', 'balance': 10_000},
{'asset': 'USDT', 'balance': 10000},
],
},
# https://testnet.binancefuture.com
'Testnet Binance Futures': {
'fee': 0.0004,
# backtest mode only: accepted are 'spot' and 'futures'
'type': 'futures',
# futures mode only
'type': 'margin',
# used only in margin trading
'settlement_currency': 'USDT',
# accepted values are: 'cross' and 'isolated'
'futures_leverage_mode': 'cross',
# 1x, 2x, 10x, 50x, etc. Enter as integers
'futures_leverage': 1,
'fee': 0.0002,
'assets': [
{'asset': 'USDT', 'balance': 10_000},
{'asset': 'USDT', 'balance': 10000},
],
},
# https://pro.coinbase.com
'Coinbase': {
'type': 'spot',
# used only in margin trading
'settlement_currency': 'USDT',
'fee': 0.005,
# backtest mode only: accepted are 'spot' and 'futures'
'type': 'futures',
# futures mode only
'settlement_currency': 'USD',
# accepted values are: 'cross' and 'isolated'
'futures_leverage_mode': 'cross',
# 1x, 2x, 10x, 50x, etc. Enter as integers
'futures_leverage': 1,
'assets': [
{'asset': 'USDT', 'balance': 10_000},
{'asset': 'USD', 'balance': 10_000},
{'asset': 'USDT', 'balance': 10000},
{'asset': 'BTC', 'balance': 0},
],
},
'Polygon': {
'api_key': '',
},
},
# changes the metrics output of the backtest
'metrics': {
'sharpe_ratio': True,
'calmar_ratio': False,
'sortino_ratio': False,
'omega_ratio': False,
'winning_streak': False,
'losing_streak': False,
'largest_losing_trade': False,
'largest_winning_trade': False,
'total_winning_trades': False,
'total_losing_trades': False,
},
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
@@ -203,7 +122,7 @@ config = {
# Below configurations are related to the optimize mode
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
'optimization': {
# sharpe, calmar, sortino, omega, serenity, smart sharpe, smart sortino
# sharpe, calmar, sortino, omega
'ratio': 'sharpe',
},
@@ -215,7 +134,7 @@ config = {
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
'data': {
# The minimum number of warmup candles that is loaded before each session.
'warmup_candles_num': 240,
'warmup_candles_num': 210,
}
},
@@ -249,73 +168,28 @@ config = {
# this would enable many console.log()s in the code, which are helpful for debugging.
'debug_mode': False,
# this is only used for the live unit tests
'is_unit_testing': False,
},
}
backup_config = config.copy()
def set_config(conf: dict) -> None:
def set_config(c):
global config
# optimization mode only
if jh.is_optimizing():
# ratio
config['env']['optimization']['ratio'] = conf['ratio']
# exchange info (only one because the optimize mode supports only one trading route at the moment)
config['env']['optimization']['exchange'] = conf['exchange']
# warm_up_candles
config['env']['optimization']['warmup_candles_num'] = int(conf['warm_up_candles'])
# backtest and live
if jh.is_backtesting() or jh.is_live():
# warm_up_candles
config['env']['data']['warmup_candles_num'] = int(conf['warm_up_candles'])
# logs
config['env']['logging'] = conf['logging']
# exchanges
for key, e in conf['exchanges'].items():
config['env']['exchanges'][e['name']] = {
'fee': float(e['fee']),
'type': 'futures',
# used only in futures trading
# 'settlement_currency': 'USDT',
'settlement_currency': jh.get_settlement_currency_from_exchange(e['name']),
# accepted values are: 'cross' and 'isolated'
'futures_leverage_mode': e['futures_leverage_mode'],
# 1x, 2x, 10x, 50x, etc. Enter as integers
'futures_leverage': int(e['futures_leverage']),
'assets': [
{'asset': 'USDT', 'balance': float(e['balance'])},
],
}
# live mode only
if jh.is_live():
config['env']['notifications'] = conf['notifications']
# TODO: must become a config value later when we go after multi account support?
config['env']['identifier'] = 'main'
# # add sandbox because it isn't in the local config file but it is needed since we might have replaced it
# config['env']['exchanges']['Sandbox'] = {
# 'type': 'spot',
# # used only in futures trading
# 'settlement_currency': 'USDT',
# 'fee': 0,
# 'futures_leverage_mode': 'cross',
# 'futures_leverage': 1,
# 'assets': [
# {'asset': 'USDT', 'balance': 10_000},
# {'asset': 'BTC', 'balance': 0},
# ],
# }
config['env'] = c
# add sandbox because it isn't in the local config file
config['env']['exchanges']['Sandbox'] = {
'type': 'spot',
# used only in margin trading
'settlement_currency': 'USDT',
'fee': 0,
'assets': [
{'asset': 'USDT', 'balance': 10000},
{'asset': 'BTC', 'balance': 0},
],
}
def reset_config() -> None:
def reset_config():
global config
config = backup_config.copy()
backup_config = config.copy()

View File

@@ -12,9 +12,7 @@ class order_statuses:
ACTIVE = 'ACTIVE'
CANCELED = 'CANCELED'
EXECUTED = 'EXECUTED'
PARTIALLY_EXECUTED = 'PARTIALLY EXECUTED'
QUEUED = 'QUEUED'
LIQUIDATED = 'LIQUIDATED'
class timeframes:
@@ -23,14 +21,12 @@ class timeframes:
MINUTE_5 = '5m'
MINUTE_15 = '15m'
MINUTE_30 = '30m'
MINUTE_45 = '45m'
HOUR_1 = '1h'
HOUR_2 = '2h'
HOUR_3 = '3h'
HOUR_4 = '4h'
HOUR_6 = '6h'
HOUR_8 = '8h'
HOUR_12 = '12h'
DAY_1 = '1D'

View File

@@ -18,6 +18,10 @@ class InvalidStrategy(Exception):
pass
class Breaker(Exception):
pass
class CandleNotFoundInDatabase(Exception):
pass
@@ -30,6 +34,10 @@ class SymbolNotFound(Exception):
pass
class MaximumDecimal(Exception):
pass
class RouteNotFound(Exception):
pass
@@ -46,29 +54,16 @@ class ExchangeNotResponding(Exception):
pass
class ExchangeRejectedOrder(Exception):
pass
class InvalidShape(Exception):
pass
class InvalidConfig(Exception):
class ConfigException(Exception):
pass
class InvalidTimeframe(Exception):
pass
class NegativeBalance(Exception):
pass
class InsufficientMargin(Exception):
pass
class Termination(Exception):
pass

View File

@@ -1,6 +1,5 @@
from abc import ABC, abstractmethod
from typing import Union
from jesse.models import Order
class Exchange(ABC):
"""
@@ -8,29 +7,65 @@ class Exchange(ABC):
"""
@abstractmethod
def market_order(self, symbol: str, qty: float, current_price: float, side: str, role: str, flags: list) -> Order:
def market_order(self, symbol, qty, current_price, side, role, flags):
"""
:param symbol:
:param qty:
:param current_price:
:param side:
:param role:
:param flags:
"""
pass
@abstractmethod
def limit_order(self, symbol: str, qty: float, price: float, side: str, role: str, flags: list) -> Order:
def limit_order(self, symbol, qty, price, side, role, flags):
"""
:param symbol:
:param qty:
:param price:
:param side:
:param role:
:param flags:
"""
pass
@abstractmethod
def stop_order(self, symbol: str, qty: float, price: float, side: str, role: str, flags: list) -> Order:
def stop_order(self, symbol, qty, price, side, role, flags):
"""
:param symbol:
:param qty:
:param price:
:param side:
:param role:
:param flags:
"""
pass
@abstractmethod
def cancel_all_orders(self, symbol: str) -> None:
def cancel_all_orders(self, symbol):
"""
:param symbol:
"""
pass
@abstractmethod
def cancel_order(self, symbol: str, order_id: str) -> None:
def cancel_order(self, symbol, order_id):
"""
:param symbol:
:param order_id:
"""
pass
@abstractmethod
def get_exec_inst(self, flags: list) -> Union[str, None]:
pass
def get_exec_inst(self, flags):
"""
@abstractmethod
def _fetch_precisions(self) -> None:
:param flags:
"""
pass

View File

@@ -3,14 +3,27 @@ from jesse.enums import order_types
from jesse.exchanges.exchange import Exchange
from jesse.models import Order
from jesse.store import store
from typing import Union
class Sandbox(Exchange):
"""
"""
def __init__(self, name='Sandbox'):
super().__init__()
self.name = name
def market_order(self, symbol: str, qty: float, current_price: float, side: str, role: str, flags: list) -> Order:
def market_order(self, symbol, qty, current_price, side, role, flags):
"""
:param symbol:
:param qty:
:param current_price:
:param side:
:param role:
:param flags:
:return:
"""
order = Order({
'id': jh.generate_unique_id(),
'symbol': symbol,
@@ -29,7 +42,17 @@ class Sandbox(Exchange):
return order
def limit_order(self, symbol: str, qty: float, price: float, side: str, role: str, flags: list) -> Order:
def limit_order(self, symbol, qty, price, side, role, flags):
"""
:param symbol:
:param qty:
:param price:
:param side:
:param role:
:param flags:
:return:
"""
order = Order({
'id': jh.generate_unique_id(),
'symbol': symbol,
@@ -46,7 +69,17 @@ class Sandbox(Exchange):
return order
def stop_order(self, symbol: str, qty: float, price: float, side: str, role: str, flags: list) -> Order:
def stop_order(self, symbol, qty, price, side, role, flags):
"""
:param symbol:
:param qty:
:param price:
:param side:
:param role:
:param flags:
:return:
"""
order = Order({
'id': jh.generate_unique_id(),
'symbol': symbol,
@@ -63,7 +96,11 @@ class Sandbox(Exchange):
return order
def cancel_all_orders(self, symbol: str) -> None:
def cancel_all_orders(self, symbol):
"""
:param symbol:
"""
orders = filter(lambda o: o.is_new,
store.orders.get_orders(self.name, symbol))
@@ -71,15 +108,22 @@ class Sandbox(Exchange):
o.cancel()
if not jh.is_unit_testing():
store.orders.storage[f'{self.name}-{symbol}'].clear()
store.orders.storage['{}-{}'.format(self.name, symbol)].clear()
def cancel_order(self, symbol: str, order_id: str) -> None:
def cancel_order(self, symbol, order_id):
"""
:param symbol:
:param order_id:
"""
store.orders.get_order_by_id(self.name, symbol, order_id).cancel()
def get_exec_inst(self, flags: list) -> Union[str, None]:
def get_exec_inst(self, flags):
"""
:param flags:
:return:
"""
if flags:
return flags[0]
return None
def _fetch_precisions(self) -> None:
pass

View File

@@ -1,4 +1,4 @@
from .candle_factory import fake_candle
from .candle_factory import range_candles
from .candle_factory import candles_from_close_prices
from .candle_factory import fake_range_candle
from .candle_factory import fake_range_candle_from_range_prices
from .order_factory import fake_order

View File

@@ -1,10 +1,8 @@
from random import randint
from typing import Union
import numpy as np
# 2021-01-01T00:00:00+00:00
first_timestamp = 1609459080000
first_timestamp = 1552309186171
open_price = randint(40, 100)
close_price = randint(open_price, 110) if randint(0, 1) else randint(
30, open_price)
@@ -14,9 +12,11 @@ min_price = min(open_price, close_price)
low_price = min_price if randint(0, 1) else randint(min_price, min_price + 10)
def range_candles(count: int) -> np.ndarray:
def fake_range_candle(count) -> np.ndarray:
"""
Generates a range of candles with random values.
:param count:
:return:
"""
fake_candle(reset=True)
arr = np.zeros((count, 6))
@@ -25,10 +25,11 @@ def range_candles(count: int) -> np.ndarray:
return arr
def candles_from_close_prices(prices: Union[list, range]) -> np.ndarray:
def fake_range_candle_from_range_prices(prices) -> np.ndarray:
"""
Generates a range of candles from a list of close prices.
The first candle has the timestamp of "2021-01-01T00:00:00+00:00"
:param prices:
:return:
"""
fake_candle(reset=True)
global first_timestamp
@@ -54,7 +55,13 @@ def candles_from_close_prices(prices: Union[list, range]) -> np.ndarray:
return np.array(arr)
def fake_candle(attributes: dict = None, reset: bool = False) -> np.ndarray:
def fake_candle(attributes=None, reset=False):
"""
:param attributes:
:param reset:
:return:
"""
global first_timestamp
global open_price
global close_price
@@ -64,7 +71,7 @@ def fake_candle(attributes: dict = None, reset: bool = False) -> np.ndarray:
global low_price
if reset:
first_timestamp = 1609459080000
first_timestamp = 1552309186171
open_price = randint(40, 100)
close_price = randint(open_price, 110)
high_price = max(open_price, close_price)

View File

@@ -7,7 +7,7 @@ from jesse.models import Order
first_timestamp = 1552309186171
def fake_order(attributes: dict = None) -> Order:
def fake_order(attributes=None):
"""
:param attributes:

View File

@@ -5,8 +5,7 @@ import random
import string
import sys
import uuid
from typing import List, Tuple, Union, Any
from pprint import pprint
import arrow
import click
import numpy as np
@@ -14,21 +13,21 @@ import numpy as np
CACHED_CONFIG = dict()
def app_currency() -> str:
def app_currency():
from jesse.routes import router
return quote_asset(router.routes[0].symbol)
def app_mode() -> str:
def app_mode():
from jesse.config import config
return config['app']['trading_mode']
def arrow_to_timestamp(arrow_time: arrow.arrow.Arrow) -> int:
def arrow_to_timestamp(arrow_time):
return arrow_time.int_timestamp * 1000
def base_asset(symbol: str) -> str:
def base_asset(symbol: str):
return symbol.split('-')[0]
@@ -50,16 +49,11 @@ def binary_search(arr: list, item) -> int:
return -1
def class_iter(Class):
return (value for variable, value in vars(Class).items() if
not callable(getattr(Class, variable)) and not variable.startswith("__"))
def clean_orderbook_list(arr) -> List[List[float]]:
def clean_orderbook_list(arr):
return [[float(i[0]), float(i[1])] for i in arr]
def color(msg_text: str, msg_color: str) -> str:
def color(msg_text: str, msg_color: str):
if not msg_text:
return ''
@@ -77,52 +71,36 @@ def color(msg_text: str, msg_color: str) -> str:
return click.style(msg_text, fg='magenta')
if msg_color == 'cyan':
return click.style(msg_text, fg='cyan')
if msg_color in {'white', 'gray'}:
if msg_color in ['white', 'gray']:
return click.style(msg_text, fg='white')
raise ValueError('unsupported color')
def convert_number(old_max: float, old_min: float, new_max: float, new_min: float, old_value: float) -> float:
def convert_number(old_max, old_min, new_max, new_min, old_value):
"""
convert a number from one range (ex 40-119) to another
range (ex 0-30) while keeping the ratio.
"""
# validation
if old_value > old_max or old_value < old_min:
raise ValueError(f'old_value:{old_value} must be within the range. {old_min}-{old_max}')
raise ValueError('old_value:{} must be within the range. {}-{}'.format(old_value, old_min, old_max))
old_range = (old_max - old_min)
new_range = (new_max - new_min)
return (((old_value - old_min) * new_range) / old_range) + new_min
new_value = (((old_value - old_min) * new_range) / old_range) + new_min
return new_value
def dashless_symbol(symbol: str) -> str:
def dashless_symbol(symbol):
return symbol.replace("-", "")
def dashy_symbol(symbol: str) -> str:
# if already has '-' in symbol, return symbol
if '-' in symbol:
return symbol
from jesse.config import config
for s in config['app']['considering_symbols']:
compare_symbol = dashless_symbol(s)
if compare_symbol == symbol:
return s
return f"{symbol[0:3]}-{symbol[3:]}"
def date_diff_in_days(date1: arrow.arrow.Arrow, date2: arrow.arrow.Arrow) -> int:
def date_diff_in_days(date1, date2):
if type(date1) is not arrow.arrow.Arrow or type(
date2) is not arrow.arrow.Arrow:
raise TypeError('dates must be Arrow instances')
dif = date2 - date1
return abs(dif.days)
@@ -152,10 +130,11 @@ def dna_to_hp(strategy_hp, dna: str):
raise TypeError('Only int and float types are implemented')
hp[h['name']] = decoded_gene
return hp
def dump_exception() -> None:
def dump_exception():
"""
a useful debugging helper
"""
@@ -164,8 +143,7 @@ def dump_exception() -> None:
terminate_app()
def estimate_average_price(order_qty: float, order_price: float, current_qty: float,
current_entry_price: float) -> float:
def estimate_average_price(order_qty, order_price, current_qty, current_entry_price):
"""Estimates the new entry price for the position.
This is used after having a new order and updating the currently holding position.
@@ -182,7 +160,7 @@ def estimate_average_price(order_qty: float, order_price: float, current_qty: fl
current_entry_price) / (abs(order_qty) + abs(current_qty))
def estimate_PNL(qty: float, entry_price: float, exit_price: float, trade_type: str, trading_fee: float = 0) -> float:
def estimate_PNL(qty, entry_price, exit_price, trade_type, trading_fee=0):
qty = abs(qty)
profit = qty * (exit_price - entry_price)
@@ -194,7 +172,7 @@ def estimate_PNL(qty: float, entry_price: float, exit_price: float, trade_type:
return profit - fee
def estimate_PNL_percentage(qty: float, entry_price: float, exit_price: float, trade_type: str) -> float:
def estimate_PNL_percentage(qty, entry_price, exit_price, trade_type):
qty = abs(qty)
profit = qty * (exit_price - entry_price)
@@ -208,34 +186,20 @@ def file_exists(path: str) -> bool:
return os.path.isfile(path)
def clear_file(path: str) -> None:
with open(path, 'w') as f:
f.write('')
def make_directory(path: str) -> None:
if not os.path.exists(path):
os.makedirs(path)
def floor_with_precision(num: float, precision: int = 0) -> float:
def floor_with_precision(num, precision=0):
temp = 10 ** precision
return math.floor(num * temp) / temp
def format_currency(num: float) -> str:
def format_currency(num):
return f'{num:,}'
def generate_unique_id() -> str:
def generate_unique_id():
return str(uuid.uuid4())
def get_arrow(timestamp: int) -> arrow.arrow.Arrow:
return timestamp_to_arrow(timestamp)
def get_candle_source(candles: np.ndarray, source_type: str = "close") -> np.ndarray:
def get_candle_source(candles: np.ndarray, source_type="close") -> np.ndarray:
"""
Returns the candles corresponding the selected type.
@@ -264,7 +228,7 @@ def get_candle_source(candles: np.ndarray, source_type: str = "close") -> np.nda
raise ValueError('type string not recognised')
def get_config(keys: str, default: Any = None) -> Any:
def get_config(keys: str, default=None):
"""
Gets keys as a single string separated with "." and returns value.
Also accepts a default value so that the app would work even if
@@ -278,39 +242,33 @@ def get_config(keys: str, default: Any = None) -> Any:
if not str:
raise ValueError('keys string cannot be empty')
if is_unit_testing() or keys not in CACHED_CONFIG:
if os.environ.get(keys.upper().replace(".", "_").replace(" ", "_")) is not None:
CACHED_CONFIG[keys] = os.environ.get(keys.upper().replace(".", "_").replace(" ", "_"))
else:
from functools import reduce
from jesse.config import config
CACHED_CONFIG[keys] = reduce(lambda d, k: d.get(k, default) if isinstance(d, dict) else default,
keys.split("."), config)
if not keys in CACHED_CONFIG:
from functools import reduce
from jesse.config import config
CACHED_CONFIG[keys] = reduce(lambda d, k: d.get(k, default) if isinstance(d, dict) else default,
keys.split("."), config)
return CACHED_CONFIG[keys]
def get_strategy_class(strategy_name: str):
def get_nan_indices(array):
return np.where(np.isnan(array))[0]
def get_strategy_class(strategy_name):
from pydoc import locate
if not is_unit_testing():
return locate(f'strategies.{strategy_name}.{strategy_name}')
path = sys.path[0]
# live plugin
if path.endswith('jesse-live'):
strategy_dir = f'tests.strategies.{strategy_name}.{strategy_name}'
# main framework
if is_unit_testing():
return locate('jesse.strategies.{}.{}'.format(strategy_name, strategy_name))
else:
strategy_dir = f'jesse.strategies.{strategy_name}.{strategy_name}'
return locate(strategy_dir)
return locate('strategies.{}.{}'.format(strategy_name, strategy_name))
def insecure_hash(msg: str) -> str:
return hashlib.md5(msg.encode()).hexdigest()
def insert_list(index: int, item, arr: list) -> list:
def insert_list(index: int, item, arr: list):
"""
helper to insert an item in a Python List without removing the item
"""
@@ -320,83 +278,71 @@ def insert_list(index: int, item, arr: list) -> list:
return arr[:index] + [item] + arr[index:]
def is_backtesting() -> bool:
def is_backtesting():
from jesse.config import config
return config['app']['trading_mode'] == 'backtest'
def is_collecting_data() -> bool:
def is_collecting_data():
from jesse.config import config
return config['app']['trading_mode'] == 'collect'
def is_debuggable(debug_item) -> bool:
def is_debuggable(debug_item):
from jesse.config import config
return is_debugging() and config['env']['logging'][debug_item]
def is_debugging() -> bool:
def is_debugging():
from jesse.config import config
return config['app']['debug_mode']
def is_importing_candles() -> bool:
def is_importing_candles():
from jesse.config import config
return config['app']['trading_mode'] == 'candles'
return config['app']['trading_mode'] == 'import-candles'
def is_live() -> bool:
def is_live():
return is_livetrading() or is_paper_trading()
def is_livetrading() -> bool:
def is_livetrading():
from jesse.config import config
return config['app']['trading_mode'] == 'livetrade'
def is_optimizing() -> bool:
def is_optimizing():
from jesse.config import config
return config['app']['trading_mode'] == 'optimize'
def is_paper_trading() -> bool:
def is_paper_trading():
from jesse.config import config
return config['app']['trading_mode'] == 'papertrade'
def is_test_driving() -> bool:
def is_test_driving():
from jesse.config import config
return config['app']['is_test_driving']
def is_unit_testing() -> bool:
from jesse.config import config
# config['app']['is_unit_testing'] is only set in the live plugin unit tests
return "pytest" in sys.modules or config['app']['is_unit_testing']
def is_unit_testing():
return "pytest" in sys.modules
def is_valid_uuid(uuid_to_test:str, version: int = 4) -> bool:
try:
uuid_obj = uuid.UUID(uuid_to_test, version=version)
except ValueError:
return False
return str(uuid_obj) == uuid_to_test
def key(exchange: str, symbol: str, timeframe: str = None):
def key(exchange, symbol, timeframe=None):
if timeframe is None:
return f'{exchange}-{symbol}'
return '{}-{}'.format(exchange, symbol)
return f'{exchange}-{symbol}-{timeframe}'
return '{}-{}-{}'.format(exchange, symbol, timeframe)
def max_timeframe(timeframes_list: list) -> str:
def max_timeframe(timeframes_list):
from jesse.enums import timeframes
if timeframes.DAY_1 in timeframes_list:
return timeframes.DAY_1
if timeframes.HOUR_12 in timeframes_list:
return timeframes.HOUR_12
if timeframes.HOUR_8 in timeframes_list:
return timeframes.HOUR_8
if timeframes.HOUR_6 in timeframes_list:
@@ -409,8 +355,6 @@ def max_timeframe(timeframes_list: list) -> str:
return timeframes.HOUR_2
if timeframes.HOUR_1 in timeframes_list:
return timeframes.HOUR_1
if timeframes.MINUTE_45 in timeframes_list:
return timeframes.MINUTE_45
if timeframes.MINUTE_30 in timeframes_list:
return timeframes.MINUTE_30
if timeframes.MINUTE_15 in timeframes_list:
@@ -423,51 +367,27 @@ def max_timeframe(timeframes_list: list) -> str:
return timeframes.MINUTE_1
def normalize(x: float, x_min: float, x_max: float) -> float:
def normalize(x, x_min, x_max):
"""
Rescaling data to have values between 0 and 1
"""
return (x - x_min) / (x_max - x_min)
x_new = (x - x_min) / (x_max - x_min)
return x_new
def now(force_fresh=False) -> int:
"""
Always returns the current time in milliseconds but rounds time in matter of seconds
"""
return now_to_timestamp(force_fresh)
def now_to_timestamp(force_fresh=False) -> int:
if not force_fresh and (not (is_live() or is_collecting_data() or is_importing_candles())):
def now_to_timestamp():
if not (is_live() or is_collecting_data() or is_importing_candles()):
from jesse.store import store
return store.app.time
return arrow.utcnow().int_timestamp * 1000
def current_1m_candle_timestamp():
return arrow.utcnow().floor('minute').int_timestamp * 1000
def now():
return now_to_timestamp()
def np_ffill(arr: np.ndarray, axis: int = 0) -> np.ndarray:
idx_shape = tuple([slice(None)] + [np.newaxis] * (len(arr.shape) - axis - 1))
idx = np.where(~np.isnan(arr), np.arange(arr.shape[axis])[idx_shape], 0)
np.maximum.accumulate(idx, axis=axis, out=idx)
slc = [
np.arange(k)[
tuple(
slice(None) if dim == i else np.newaxis
for dim in range(len(arr.shape))
)
]
for i, k in enumerate(arr.shape)
]
slc[axis] = idx
return arr[tuple(slc)]
def np_shift(arr: np.ndarray, num: int, fill_value=0) -> np.ndarray:
def np_shift(arr: np.ndarray, num: int, fill_value=0):
result = np.empty_like(arr)
if num > 0:
@@ -482,18 +402,17 @@ def np_shift(arr: np.ndarray, num: int, fill_value=0) -> np.ndarray:
return result
def opposite_side(s: str) -> str:
def opposite_side(s):
from jesse.enums import sides
if s == sides.BUY:
return sides.SELL
elif s == sides.SELL:
if s == sides.SELL:
return sides.BUY
else:
raise ValueError(f'{s} is not a valid input for side')
raise ValueError('unsupported side')
def opposite_type(t: str) -> str:
def opposite_type(t):
from jesse.enums import trade_types
if t == trade_types.LONG:
@@ -503,15 +422,15 @@ def opposite_type(t: str) -> str:
raise ValueError('unsupported type')
def orderbook_insertion_index_search(arr, target: int, ascending: bool = True) -> Tuple[bool, int]:
def orderbook_insertion_index_search(arr, target, ascending=True):
target = target[0]
lower = 0
upper = len(arr)
while lower < upper:
x = lower + (upper - lower) // 2
val = arr[x][0]
if ascending:
if ascending:
while lower < upper:
x = lower + (upper - lower) // 2
val = arr[x][0]
if target == val:
return True, x
elif target > val:
@@ -522,19 +441,30 @@ def orderbook_insertion_index_search(arr, target: int, ascending: bool = True) -
if lower == x:
return False, lower
upper = x
elif target == val:
return True, x
elif target < val:
if lower == x:
return False, lower + 1
lower = x
elif target > val:
if lower == x:
return False, lower
upper = x
else:
while lower < upper:
x = lower + (upper - lower) // 2
val = arr[x][0]
if target == val:
return True, x
elif target < val:
if lower == x:
return False, lower + 1
lower = x
elif target > val:
if lower == x:
return False, lower
upper = x
def orderbook_trim_price(p: float, ascending: bool, unit: float) -> float:
def orderbook_trim_price(p: float, ascending: bool, unit: float):
"""
:param p:
:param ascending:
:param unit:
:return:
"""
if ascending:
trimmed = np.ceil(p / unit) * unit
if math.log10(unit) < 0:
@@ -547,32 +477,33 @@ def orderbook_trim_price(p: float, ascending: bool, unit: float) -> float:
return p if trimmed == p - unit else trimmed
def prepare_qty(qty: float, side: str) -> float:
def prepare_qty(qty, side):
if side.lower() in ('sell', 'short'):
return -abs(qty)
elif side.lower() in ('buy', 'long'):
if side.lower() in ('buy', 'long'):
return abs(qty)
else:
raise ValueError(f'{side} is not a valid input')
raise TypeError()
def python_version() -> tuple:
return sys.version_info[:2]
def python_version() -> float:
return float('{}.{}'.format(sys.version_info[0], sys.version_info[1]))
def quote_asset(symbol: str) -> str:
def quote_asset(symbol: str):
try:
return symbol.split('-')[1]
except IndexError:
from jesse.exceptions import InvalidRoutes
raise InvalidRoutes(f"The symbol format is incorrect. Correct example: 'BTC-USDT'. Yours is '{symbol}'")
raise InvalidRoutes("The symbol format is incorrect. Correct example: 'BTC-USDT'. Yours is '{}'".format(symbol))
def random_str(num_characters: int = 8) -> str:
return ''.join(random.choice(string.ascii_letters) for _ in range(num_characters))
def random_str(num_characters=8):
return ''.join(random.choice(string.ascii_letters) for i in range(num_characters))
def readable_duration(seconds: int, granularity: int = 2) -> str:
def readable_duration(seconds, granularity=2):
intervals = (
('weeks', 604800), # 60 * 60 * 24 * 7
('days', 86400), # 60 * 60 * 24
@@ -582,7 +513,6 @@ def readable_duration(seconds: int, granularity: int = 2) -> str:
)
result = []
seconds = int(seconds)
for name, count in intervals:
value = seconds // count
@@ -590,60 +520,58 @@ def readable_duration(seconds: int, granularity: int = 2) -> str:
seconds -= value * count
if value == 1:
name = name.rstrip('s')
result.append(f"{value} {name}")
result.append("{} {}".format(value, name))
return ', '.join(result[:granularity])
def reinsert_nan(array, nan_indices):
for i in range(nan_indices.shape[0]):
array = np.concatenate((array[:nan_indices[i]], [np.nan], array[nan_indices[i]:]))
return array
def relative_to_absolute(path: str) -> str:
return os.path.abspath(path)
def round_price_for_live_mode(price, precision: int) -> Union[float, np.ndarray]:
def round_price_for_live_mode(price, roundable_price):
"""
Rounds price(s) based on exchange requirements
:param price: float
:param precision: int
:param roundable_price: float | nd.array
:return: float | nd.array
"""
return np.round(price, precision)
n = int(math.log10(price))
if price < 1:
price_round_precision = abs(n - 4)
else:
price_round_precision = 3 - n
if price_round_precision < 0:
price_round_precision = 0
return np.round(roundable_price, price_round_precision)
def round_qty_for_live_mode(roundable_qty: float, precision: int) -> Union[float, np.ndarray]:
def round_qty_for_live_mode(price, roundable_qty):
"""
Rounds qty(s) based on exchange requirements
:param price: float
:param roundable_qty: float | nd.array
:param precision: int
:return: float | nd.array
"""
# for qty rounding down is important to prevent InsufficenMargin
rounded = round_decimals_down(roundable_qty, precision)
n = int(math.log10(price))
for index, q in enumerate(rounded):
if q == 0.0:
rounded[index] = 1 / 10 ** precision
if price < 1:
qty_round_precision = 0
else:
qty_round_precision = n + 1
if qty_round_precision > 3:
qty_round_precision = 3
return rounded
def round_decimals_down(number: np.ndarray, decimals: int = 2) -> float:
"""
Returns a value rounded down to a specific number of decimal places.
"""
if not isinstance(decimals, int):
raise TypeError("decimal places must be an integer")
elif decimals < 0:
raise ValueError("decimal places has to be 0 or more")
elif decimals == 0:
return np.floor(number)
factor = 10 ** decimals
return np.floor(number * factor) / factor
def same_length(bigger: np.ndarray, shorter: np.ndarray) -> np.ndarray:
return np.concatenate((np.full((bigger.shape[0] - shorter.shape[0]), np.nan), shorter))
return np.round(roundable_qty, qty_round_precision)
def secure_hash(msg: str) -> str:
@@ -654,12 +582,9 @@ def should_execute_silently() -> bool:
return is_optimizing() or is_unit_testing()
def side_to_type(s: str) -> str:
def side_to_type(s):
from jesse.enums import trade_types, sides
# make sure string is lowercase
s = s.lower()
if s == sides.BUY:
return trade_types.LONG
if s == sides.SELL:
@@ -667,21 +592,14 @@ def side_to_type(s: str) -> str:
raise ValueError
def string_after_character(s: str, character: str) -> str:
def string_after_character(string: str, character: str):
try:
return s.split(character, 1)[1]
return string.split(character, 1)[1]
except IndexError:
return None
def slice_candles(candles: np.ndarray, sequential: bool) -> np.ndarray:
warmup_candles_num = get_config('env.data.warmup_candles_num', 240)
if not sequential and candles.shape[0] > warmup_candles_num:
candles = candles[-warmup_candles_num:]
return candles
def style(msg_text: str, msg_style: str) -> str:
def style(msg_text: str, msg_style: str):
if msg_style is None:
return msg_text
@@ -694,32 +612,15 @@ def style(msg_text: str, msg_style: str) -> str:
raise ValueError('unsupported style')
def terminate_app() -> None:
def terminate_app():
# close the database
from jesse.services.db import database
database.close_connection()
from jesse.services.db import close_connection
close_connection()
# disconnect python from the OS
os._exit(1)
def error(msg: str, force_print: bool = False) -> None:
# send notifications if it's a live session
if is_live():
from jesse.services import logger
logger.error(msg)
if force_print:
_print_error(msg)
else:
_print_error(msg)
def _print_error(msg: str) -> None:
print('\n')
print(color('========== critical error =========='.upper(), 'red'))
print(color(msg, 'red'))
def timeframe_to_one_minutes(timeframe: str) -> int:
def timeframe_to_one_minutes(timeframe):
from jesse.enums import timeframes
from jesse.exceptions import InvalidTimeframe
@@ -729,34 +630,36 @@ def timeframe_to_one_minutes(timeframe: str) -> int:
timeframes.MINUTE_5: 5,
timeframes.MINUTE_15: 15,
timeframes.MINUTE_30: 30,
timeframes.MINUTE_45: 45,
timeframes.HOUR_1: 60,
timeframes.HOUR_2: 60 * 2,
timeframes.HOUR_3: 60 * 3,
timeframes.HOUR_4: 60 * 4,
timeframes.HOUR_6: 60 * 6,
timeframes.HOUR_8: 60 * 8,
timeframes.HOUR_12: 60 * 12,
timeframes.DAY_1: 60 * 24,
}
try:
return dic[timeframe]
except KeyError:
all_timeframes = [timeframe for timeframe in class_iter(timeframes)]
raise InvalidTimeframe(
f'Timeframe "{timeframe}" is invalid. Supported timeframes are {", ".join(all_timeframes)}.')
'Timeframe "{}" is invalid. Supported timeframes are 1m, 3m, 5m, 15m, 30m, 1h, 2h, 3h, 4h, 6h, 8h, 1D'.format(
timeframe))
def timestamp_to_arrow(timestamp: int) -> arrow.arrow.Arrow:
def timestamp_to_arrow(timestamp):
return arrow.get(timestamp / 1000)
def get_arrow(timestamp):
return timestamp_to_arrow(timestamp)
def timestamp_to_date(timestamp: int) -> str:
return str(arrow.get(timestamp / 1000))[:10]
def timestamp_to_time(timestamp: int) -> str:
def timestamp_to_time(timestamp):
return str(arrow.get(timestamp / 1000))
@@ -769,7 +672,7 @@ def today_to_timestamp() -> int:
return arrow.utcnow().floor('day').int_timestamp * 1000
def type_to_side(t: str) -> str:
def type_to_side(t):
from jesse.enums import trade_types, sides
if t == trade_types.LONG:
@@ -788,165 +691,3 @@ def unique_list(arr) -> list:
seen = set()
seen_add = seen.add
return [x for x in arr if not (x in seen or seen_add(x))]
def closing_side(position_type: str) -> str:
if position_type.lower() == 'long':
return 'sell'
elif position_type.lower() == 'short':
return 'buy'
else:
raise ValueError(f'Value entered for position_type ({position_type}) is not valid')
def merge_dicts(d1: dict, d2: dict) -> dict:
"""
Merges nested dictionaries
:param d1: dict
:param d2: dict
:return: dict
"""
def inner(dict1, dict2):
for k in set(dict1.keys()).union(dict2.keys()):
if k in dict1 and k in dict2:
if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
yield k, dict(merge_dicts(dict1[k], dict2[k]))
else:
yield k, dict2[k]
elif k in dict1:
yield k, dict1[k]
else:
yield k, dict2[k]
return dict(inner(d1, d2))
def computer_name():
import platform
return platform.node()
def validate_response(response):
if response.status_code != 200:
err_msg = f"[{response.status_code}]: {response.json()['message']}\nPlease contact us at support@jesse.trade if this is unexpected."
if response.status_code not in [401, 403]:
raise ConnectionError(err_msg)
error(err_msg, force_print=True)
terminate_app()
def get_session_id():
from jesse.store import store
return store.app.session_id
def get_pid():
return os.getpid()
def is_jesse_project():
ls = os.listdir('.')
return 'strategies' in ls and 'storage' in ls
def dd(item, pretty=True):
"""
Dump and Die but pretty: used for debugging when developing Jesse
"""
dump(item, pretty)
terminate_app()
def dump(item, pretty=True):
"""
Dump object in pretty format: used for debugging when developing Jesse
"""
print(
color('\n========= Debugging Value =========='.upper(), 'yellow')
)
if pretty:
pprint(item)
else:
print(item)
print(
color('====================================\n', 'yellow')
)
def float_or_none(item):
"""
Return the float of the value if it's not None
"""
if item is None:
return None
else:
return float(item)
def str_or_none(item, encoding='utf-8'):
"""
Return the str of the value if it's not None
"""
if item is None:
return None
else:
# return item if it's str, if not, decode it using encoding
if isinstance(item, str):
return item
return str(item, encoding)
def get_settlement_currency_from_exchange(exchange: str):
if exchange in {'FTX Futures', 'Bitfinex', 'Coinbase'}:
return 'USD'
else:
return 'USDT'
def cpu_cores_count():
from multiprocessing import cpu_count
return cpu_count()
# a function that converts name to env_name. Example: 'Testnet Binance Futures' into 'TESTNET_BINANCE_FUTURES'
def convert_to_env_name(name: str) -> str:
return name.replace(' ', '_').upper()
def is_notebook():
try:
shell = get_ipython().__class__.__name__
# Jupyter notebook or qtconsole
if shell == 'ZMQInteractiveShell':
return True
elif shell == 'TerminalInteractiveShell':
# Terminal running IPython
return False
else:
# Other type (?)
return False
except NameError:
# Probably standard Python interpreter
return False
def get_os() -> str:
import platform
if platform.system() == 'Darwin':
return 'mac'
elif platform.system() == 'Linux':
return 'linux'
elif platform.system() == 'Windows':
return 'windows'
else:
raise NotImplementedError(f'Unsupported OS: "{platform.system()}"')
# a function that returns boolean whether or not the code is being executed inside a docker container
def is_docker() -> bool:
import os
return os.path.exists('/.dockerenv')

View File

@@ -4,95 +4,68 @@ from .adosc import adosc
from .adx import adx
from .adxr import adxr
from .alligator import alligator
from .alma import alma
from .ao import ao
from .apo import apo
from .aroon import aroon
from .aroonosc import aroonosc
from .atr import atr
from .avgprice import avgprice
from .bandpass import bandpass
from .beta import beta
from .bollinger_bands import bollinger_bands
from .bollinger_bands_width import bollinger_bands_width
from .bop import bop
from .cc import cc
from .cci import cci
from .cfo import cfo
from .cg import cg
from .chande import chande
from .chop import chop
from .cksp import cksp
from .cmo import cmo
from .correl import correl
from .correlation_cycle import correlation_cycle
from .correl import correl
from .cvi import cvi
from .cwma import cwma
from .damiani_volatmeter import damiani_volatmeter
from .dec_osc import dec_osc
from .decycler import decycler
from .dema import dema
from .devstop import devstop
from .di import di
from .dm import dm
from .dx import dx
from .donchian import donchian
from .dpo import dpo
from .dti import dti
from .dx import dx
from .edcf import edcf
from .efi import efi
from .ema import ema
from .emd import emd
from .emv import emv
from .epma import epma
from .er import er
from .eri import eri
from .fisher import fisher
from .fosc import fosc
from .frama import frama
from .fwma import fwma
from .gatorosc import gatorosc
from .gauss import gauss
from .high_pass import high_pass
from .high_pass_2_pole import high_pass_2_pole
from .hma import hma
from .high_pass import high_pass
from .ht_dcperiod import ht_dcperiod
from .ht_dcphase import ht_dcphase
from .ht_phasor import ht_phasor
from .ht_sine import ht_sine
from .ht_trendline import ht_trendline
from .ht_trendmode import ht_trendmode
from .hurst_exponent import hurst_exponent
from .hwma import hwma
from .ichimoku_cloud import ichimoku_cloud
from .ichimoku_cloud_seq import ichimoku_cloud_seq
from .ift_rsi import ift_rsi
from .itrend import itrend
from .jma import jma
from .jsa import jsa
from .kama import kama
from .kaufmanstop import kaufmanstop
from .kdj import kdj
from .keltner import keltner
from .kst import kst
from .kurtosis import kurtosis
from .kvo import kvo
from .linearreg import linearreg
from .linearreg_angle import linearreg_angle
from .linearreg_intercept import linearreg_intercept
from .linearreg_slope import linearreg_slope
from .lrsi import lrsi
from .ma import ma
from .maaq import maaq
from .mab import mab
from .macd import macd
from .macdext import macdext
from .mama import mama
from .marketfi import marketfi
from .mass import mass
from .mcginley_dynamic import mcginley_dynamic
from .mean_ad import mean_ad
from .median_ad import median_ad
from .marketfi import marketfi
from .medprice import medprice
from .mfi import mfi
from .midpoint import midpoint
@@ -100,48 +73,31 @@ from .midprice import midprice
from .minmax import minmax
from .mom import mom
from .msw import msw
from .mwdx import mwdx
from .natr import natr
from .nma import nma
from .nvi import nvi
from .obv import obv
from .pattern_recognition import pattern_recognition
from .pfe import pfe
from .pivot import pivot
from .pma import pma
from .ppo import ppo
from .pvi import pvi
from .pwma import pwma
from .qstick import qstick
from .reflex import reflex
from .rma import rma
from .roc import roc
from .roofing import roofing
from .rocp import rocp
from .rocr import rocr
from .rocr100 import rocr100
from .roofing import roofing
from .rsi import rsi
from .rsmk import rsmk
from .rsx import rsx
from .rvi import rvi
from .safezonestop import safezonestop
from .sar import sar
from .sarext import sarext
from .sinwma import sinwma
from .skew import skew
from .sma import sma
from .smma import smma
from .sqwma import sqwma
from .srsi import srsi
from .srwma import srwma
from .stc import stc
from .stddev import stddev
from .stochastic import stoch
from .stochf import stochf
from .supersmoother import supersmoother
from .supersmoother_3_pole import supersmoother_3_pole
from .supertrend import supertrend
from .swma import swma
from .t3 import t3
from .tema import tema
from .trange import trange
@@ -150,27 +106,21 @@ from .trima import trima
from .trix import trix
from .tsf import tsf
from .tsi import tsi
from .ttm_trend import ttm_trend
from .typprice import typprice
from .ui import ui
from .ultosc import ultosc
from .var import var
from .vi import vi
from .vidya import vidya
from .vlma import vlma
from .vosc import vosc
from .voss import voss
from .vpci import vpci
from .vpt import vpt
from .vpwma import vpwma
from .vwap import vwap
from .vwma import vwma
from .vwmacd import vwmacd
from .vosc import vosc
from .voss import voss
from .wad import wad
from .wclprice import wclprice
from .wilders import wilders
from .willr import willr
from .wma import wma
from .wt import wt
from .zlema import zlema
from .zscore import zscore

View File

@@ -3,21 +3,20 @@ from collections import namedtuple
import numpy as np
import talib
from jesse.helpers import slice_candles
AC = namedtuple('AC', ['osc', 'change'])
def acosc(candles: np.ndarray, sequential: bool = False) -> AC:
def acosc(candles: np.ndarray, sequential=False) -> AC:
"""
Acceleration / Deceleration Oscillator (AC)
:param candles: np.ndarray
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: AC(osc, change)
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
med = talib.MEDPRICE(candles[:, 3], candles[:, 4])
ao = talib.SMA(med, 5) - talib.SMA(med, 34)

View File

@@ -3,20 +3,22 @@ from typing import Union
import numpy as np
import talib
from jesse.helpers import slice_candles
def ad(candles: np.ndarray, sequential: bool = False) -> Union[float, np.ndarray]:
def ad(candles: np.ndarray, sequential=False) -> Union[float, np.ndarray]:
"""
AD - Chaikin A/D Line
:param candles: np.ndarray
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
res = talib.AD(candles[:, 3], candles[:, 4], candles[:, 2], candles[:, 5])
return res if sequential else res[-1]
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -3,24 +3,25 @@ from typing import Union
import numpy as np
import talib
from jesse.helpers import slice_candles
def adosc(candles: np.ndarray, fast_period: int = 3, slow_period: int = 10, sequential: bool = False) -> Union[
float, np.ndarray]:
def adosc(candles: np.ndarray, fastperiod=3, slowperiod=10, sequential=False) -> Union[float, np.ndarray]:
"""
ADOSC - Chaikin A/D Oscillator
:param candles: np.ndarray
:param fast_period: int - default: 3
:param slow_period: int - default: 10
:param sequential: bool - default: False
:param fastperiod: int - default: 3
:param slowperiod: int - default: 10
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
res = talib.ADOSC(candles[:, 3], candles[:, 4], candles[:, 2], candles[:, 5], fastperiod=fast_period,
slowperiod=slow_period)
res = talib.ADOSC(candles[:, 3], candles[:, 4], candles[:, 2], candles[:, 5], fastperiod=fastperiod,
slowperiod=slowperiod)
return res if sequential else res[-1]
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -3,21 +3,23 @@ from typing import Union
import numpy as np
import talib
from jesse.helpers import slice_candles
def adx(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
def adx(candles: np.ndarray, period=14, sequential=False) -> Union[float, np.ndarray]:
"""
ADX - Average Directional Movement Index
:param candles: np.ndarray
:param period: int - default: 14
:param sequential: bool - default: False
:param period: int - default=14
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
res = talib.ADX(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)
return res if sequential else res[-1]
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -3,21 +3,23 @@ from typing import Union
import numpy as np
import talib
from jesse.helpers import slice_candles
def adxr(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
def adxr(candles: np.ndarray, period=14, sequential=False) -> Union[float, np.ndarray]:
"""
ADXR - Average Directional Movement Index Rating
:param candles: np.ndarray
:param period: int - default: 14
:param sequential: bool - default: False
:param period: int - default=14
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
res = talib.ADXR(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)
return res if sequential else res[-1]
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -2,22 +2,23 @@ from collections import namedtuple
import numpy as np
from jesse.helpers import get_candle_source, np_shift, slice_candles
from jesse.helpers import get_candle_source, np_shift
AG = namedtuple('AG', ['jaw', 'teeth', 'lips'])
def alligator(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> AG:
def alligator(candles: np.ndarray, source_type="close", sequential=False) -> AG:
"""
Alligator
:param candles: np.ndarray
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: AG(jaw, teeth, lips)
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
@@ -31,7 +32,7 @@ def alligator(candles: np.ndarray, source_type: str = "close", sequential: bool
return AG(jaw[-1], teeth[-1], lips[-1])
def numpy_ewma(data: np.ndarray, window: int):
def numpy_ewma(data, window):
"""
:param data:
@@ -39,11 +40,13 @@ def numpy_ewma(data: np.ndarray, window: int):
:return:
"""
alpha = 1 / window
# scale = 1 / (1 - alpha)
scale = 1 / (1 - alpha)
n = data.shape[0]
scale_arr = (1 - alpha) ** (-1 * np.arange(n))
weights = (1 - alpha) ** np.arange(n)
pw0 = (1 - alpha) ** (n - 1)
mult = data * pw0 * scale_arr
cumsums = mult.cumsum()
return cumsums * scale_arr[::-1] / weights.cumsum()
out = cumsums * scale_arr[::-1] / weights.cumsum()
return out

View File

@@ -1,54 +0,0 @@
from typing import Union
import numpy as np
from jesse.helpers import get_candle_source, slice_candles
def alma(candles: np.ndarray, period: int = 9, sigma: float = 6.0, distribution_offset: float = 0.85,
source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
"""
ALMA - Arnaud Legoux Moving Average
:param candles: np.ndarray
:param period: int - default: 9
:param sigma: float - default: 6.0
:param distribution_offset: float - default: 0.85
:param source_type: str - default: "close"
:param sequential: bool - default: False
:return: float | np.ndarray
"""
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
asize = period - 1
m = distribution_offset * asize
s = period / sigma
dss = 2 * s * s
wtds = np.exp(-(np.arange(period) - m) ** 2 / dss)
pnp_array3D = strided_axis0(source, len(wtds))
res = np.zeros(source.shape)
res[period - 1:] = np.tensordot(pnp_array3D, wtds, axes=(1, 0))[:]
res /= wtds.sum()
res[res == 0] = np.nan
return res if sequential else res[-1]
def strided_axis0(a, L):
# Store the shape and strides info
shp = a.shape
s = a.strides
# Compute length of output array along the first axis
nd0 = shp[0] - L + 1
# Setup shape and strides for use with np.lib.stride_tricks.as_strided
# and get (n+1) dim output array
shp_in = (nd0, L) + shp[1:]
strd_in = (s[0],) + s
return np.lib.stride_tricks.as_strided(a, shape=shp_in, strides=strd_in)

View File

@@ -3,21 +3,20 @@ from collections import namedtuple
import numpy as np
import talib
from jesse.helpers import slice_candles
AO = namedtuple('AO', ['osc', 'change'])
def ao(candles: np.ndarray, sequential: bool = False) -> AO:
def ao(candles: np.ndarray, sequential=False) -> AO:
"""
Awesome Oscillator
:param candles: np.ndarray
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: AO(osc, change)
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
med = talib.MEDPRICE(candles[:, 3], candles[:, 4])
res = talib.SMA(med, 5) - talib.SMA(med, 34)

View File

@@ -1,29 +1,30 @@
from typing import Union
import numpy as np
from jesse.indicators.ma import ma
import talib
from jesse.helpers import get_candle_source, slice_candles
from jesse.helpers import get_candle_source
def apo(candles: np.ndarray, fast_period: int = 12, slow_period: int = 26, matype: int = 0, source_type: str = "close",
sequential: bool = False) -> Union[float, np.ndarray]:
def apo(candles: np.ndarray, fastperiod=12, slowperiod=26, matype=0, source_type="close", sequential=False) -> Union[
float, np.ndarray]:
"""
APO - Absolute Price Oscillator
:param candles: np.ndarray
:param fast_period: int - default: 12
:param slow_period: int - default: 26
:param fastperiod: int - default: 12
:param slowperiod: int - default: 26
:param matype: int - default: 0
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
res = ma(source, period=fast_period, matype=matype, sequential=True) - ma(source, period=slow_period, matype=matype, sequential=True)
res = talib.APO(source, fastperiod=fastperiod, slowperiod=slowperiod, matype=matype)
return res if sequential else res[-1]

View File

@@ -3,22 +3,21 @@ from collections import namedtuple
import numpy as np
import talib
from jesse.helpers import slice_candles
AROON = namedtuple('AROON', ['down', 'up'])
def aroon(candles: np.ndarray, period: int = 14, sequential: bool = False) -> AROON:
def aroon(candles: np.ndarray, period=14, sequential=False) -> AROON:
"""
AROON - Aroon
:param candles: np.ndarray
:param period: int - default: 14
:param sequential: bool - default: False
:param period: int - default=14
:param sequential: bool - default=False
:return: AROON(down, up)
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
aroondown, aroonup = talib.AROON(candles[:, 3], candles[:, 4], timeperiod=period)

View File

@@ -3,21 +3,23 @@ from typing import Union
import numpy as np
import talib
from jesse.helpers import slice_candles
def aroonosc(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
def aroonosc(candles: np.ndarray, period=14, sequential=False) -> Union[float, np.ndarray]:
"""
AROONOSC - Aroon Oscillator
:param candles: np.ndarray
:param period: int - default: 14
:param sequential: bool - default: False
:param period: int - default=14
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
res = talib.AROONOSC(candles[:, 3], candles[:, 4], timeperiod=period)
return res if sequential else res[-1]
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -3,21 +3,23 @@ from typing import Union
import numpy as np
import talib
from jesse.helpers import slice_candles
def atr(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
def atr(candles: np.ndarray, period=14, sequential=False) -> Union[float, np.ndarray]:
"""
ATR - Average True Range
:param candles: np.ndarray
:param period: int - default: 14
:param sequential: bool - default: False
:param period: int - default=14
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
res = talib.ATR(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)
return res if sequential else res[-1]
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -3,20 +3,22 @@ from typing import Union
import numpy as np
import talib
from jesse.helpers import slice_candles
def avgprice(candles: np.ndarray, sequential: bool = False) -> Union[float, np.ndarray]:
def avgprice(candles: np.ndarray, sequential=False) -> Union[float, np.ndarray]:
"""
AVGPRICE - Average Price
:param candles: np.ndarray
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
res = talib.AVGPRICE(candles[:, 1], candles[:, 3], candles[:, 4], candles[:, 2])
return res if sequential else res[-1]
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -1,68 +0,0 @@
from collections import namedtuple
import numpy as np
try:
from numba import njit
except ImportError:
njit = lambda a : a
from .high_pass import high_pass_fast
from jesse.helpers import get_candle_source, slice_candles
BandPass = namedtuple('BandPass', ['bp', 'bp_normalized', 'signal', 'trigger'])
def bandpass(candles: np.ndarray, period: int = 20, bandwidth: float = 0.3, source_type: str = "close", sequential: bool = False) -> BandPass:
"""
BandPass Filter
:param candles: np.ndarray
:param period: int - default: 20
:param bandwidth: float - default: 0.3
:param source_type: str - default: "close"
:param sequential: bool - default: False
:return: BandPass(bp, bp_normalized, signal, trigger)
"""
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
hp = high_pass_fast(source, 4 * period / bandwidth)
beta = np.cos(2 * np.pi / period)
gamma = np.cos(2 * np.pi * bandwidth / period)
alpha = 1 / gamma - np.sqrt(1 / gamma ** 2 - 1)
bp, peak = bp_fast(source, hp, alpha, beta)
bp_normalized = bp / peak
trigger = high_pass_fast(bp_normalized, period / bandwidth / 1.5)
signal = (bp_normalized < trigger) * 1 - (trigger < bp_normalized) * 1
if sequential:
return BandPass(bp, bp_normalized, signal, trigger)
else:
return BandPass(bp[-1], bp_normalized[-1], signal[-1], trigger[-1])
@njit
def bp_fast(source, hp, alpha, beta): # Function is compiled to machine code when called the first time
bp = np.copy(hp)
for i in range(2, source.shape[0]):
bp[i] = 0.5 * (1 - alpha) * hp[i] - (1 - alpha) * 0.5 * hp[i - 2] + beta * (1 + alpha) * bp[i - 1] - alpha * bp[i - 2]
# fast attack-slow decay AGC
K = 0.991
peak = np.copy(bp)
for i in range(source.shape[0]):
if i > 0:
peak[i] = peak[i - 1] * K
if np.abs(bp[i]) > peak[i]:
peak[i] = np.abs(bp[i])
return bp, peak

View File

@@ -3,21 +3,23 @@ from typing import Union
import numpy as np
import talib
from jesse.helpers import slice_candles
def beta(candles: np.ndarray, period: int = 5, sequential: bool = False) -> Union[float, np.ndarray]:
def beta(candles: np.ndarray, period=5, sequential=False) -> Union[float, np.ndarray]:
"""
BETA - Beta
:param candles: np.ndarray
:param period: int - default: 5
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
res = talib.BETA(candles[:, 3], candles[:, 4], timeperiod=period)
return res if sequential else res[-1]
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -2,18 +2,14 @@ from collections import namedtuple
import numpy as np
import talib
from jesse.indicators.ma import ma
from jesse.indicators.mean_ad import mean_ad
from jesse.indicators.median_ad import median_ad
from jesse.helpers import get_candle_source, slice_candles
from jesse.helpers import get_candle_source
BollingerBands = namedtuple('BollingerBands', ['upperband', 'middleband', 'lowerband'])
def bollinger_bands(candles: np.ndarray, period: int = 20, devup: float = 2, devdn: float = 2, matype: int = 0, devtype: int = 0,
source_type: str = "close",
sequential: bool = False) -> BollingerBands:
def bollinger_bands(candles: np.ndarray, period=20, devup=2, devdn=2, matype=0, source_type="close",
sequential=False) -> BollingerBands:
"""
BBANDS - Bollinger Bands
@@ -22,27 +18,17 @@ def bollinger_bands(candles: np.ndarray, period: int = 20, devup: float = 2, dev
:param devup: float - default: 2
:param devdn: float - default: 2
:param matype: int - default: 0
:param devtype: int - default: 0
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: BollingerBands(upperband, middleband, lowerband)
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
if devtype == 0:
dev = talib.STDDEV(source, period)
elif devtype == 1:
dev = mean_ad(source, period, sequential=True)
elif devtype == 2:
dev = median_ad(source, period, sequential=True)
middlebands = ma(source, period=period, matype=matype, sequential=True)
upperbands = middlebands + devup * dev
lowerbands = middlebands - devdn * dev
upperbands, middlebands, lowerbands = talib.BBANDS(source, timeperiod=period, nbdevup=devup, nbdevdn=devdn,
matype=matype)
if sequential:
return BollingerBands(upperbands, middlebands, lowerbands)

View File

@@ -2,17 +2,12 @@ from typing import Union
import numpy as np
import talib
from jesse.indicators.ma import ma
from jesse.indicators.mean_ad import mean_ad
from jesse.indicators.median_ad import median_ad
from jesse.helpers import get_candle_source, slice_candles
from jesse.helpers import get_candle_source
def bollinger_bands_width(candles: np.ndarray, period: int = 20, devup: float = 2, devdn: float = 2, matype: int = 0,
devtype: int = 0,
source_type: str = "close",
sequential: bool = False) -> Union[float, np.ndarray]:
def bollinger_bands_width(candles: np.ndarray, period=20, devup=2, devdn=2, matype=0, source_type="close",
sequential=False) -> Union[float, np.ndarray]:
"""
BBW - Bollinger Bands Width - Bollinger Bands Bandwidth
@@ -21,31 +16,19 @@ def bollinger_bands_width(candles: np.ndarray, period: int = 20, devup: float =
:param devup: float - default: 2
:param devdn: float - default: 2
:param matype: int - default: 0
:param devtype: int - default: 0
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: BollingerBands(upperband, middleband, lowerband)
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
if devtype == 0:
dev = talib.STDDEV(source, period)
elif devtype == 1:
dev = mean_ad(source, period, sequential=True)
elif devtype == 2:
dev = median_ad(source, period, sequential=True)
middlebands = ma(source, period=period, matype=matype, sequential=True)
upperbands = middlebands + devup * dev
lowerbands = middlebands - devdn * dev
upperbands, middlebands, lowerbands = talib.BBANDS(source, timeperiod=period, nbdevup=devup, nbdevdn=devdn,
matype=matype)
if sequential:
return (upperbands - lowerbands) / middlebands
else:
return (upperbands[-1] - lowerbands[-1]) / middlebands[-1]

View File

@@ -3,20 +3,22 @@ from typing import Union
import numpy as np
import talib
from jesse.helpers import slice_candles
def bop(candles: np.ndarray, sequential: bool = False) -> Union[float, np.ndarray]:
def bop(candles: np.ndarray, sequential=False) -> Union[float, np.ndarray]:
"""
BOP - Balance Of Power
:param candles: np.ndarray
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
res = talib.BOP(candles[:, 1], candles[:, 3], candles[:, 4], candles[:, 2])
return res if sequential else res[-1]
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -3,12 +3,10 @@ from typing import Union
import numpy as np
import talib
from jesse.helpers import get_candle_source, slice_candles
from jesse.helpers import get_candle_source
def cc(candles: np.ndarray, wma_period: int = 10, roc_short_period: int = 11, roc_long_period: int = 14,
source_type: str = "close",
sequential: bool = False) -> Union[float, np.ndarray]:
def cc(candles: np.ndarray, wma_period=10, roc_short_period=11, roc_long_period=14, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
CC - Coppock Curve
@@ -17,14 +15,14 @@ def cc(candles: np.ndarray, wma_period: int = 10, roc_short_period: int = 11, ro
:param roc_short_period: int - default: 11
:param roc_long_period: int - default: 14
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
res = talib.WMA(talib.ROC(source, timeperiod=roc_long_period) + talib.ROC(source, timeperiod=roc_short_period),
timeperiod=wma_period)
res = talib.WMA(talib.ROC(source, timeperiod=roc_long_period) + talib.ROC(source, timeperiod=roc_short_period), timeperiod=wma_period)
return res if sequential else res[-1]

View File

@@ -3,21 +3,23 @@ from typing import Union
import numpy as np
import talib
from jesse.helpers import slice_candles
def cci(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
def cci(candles: np.ndarray, period=14, sequential=False) -> Union[float, np.ndarray]:
"""
CCI - Commodity Channel Index
:param candles: np.ndarray
:param period: int - default: 14
:param sequential: bool - default: False
:param period: int - default=14
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
res = talib.CCI(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)
return res if sequential else res[-1]
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -1,34 +0,0 @@
from typing import Union
import numpy as np
import talib
from jesse.helpers import get_candle_source
from jesse.helpers import slice_candles
def cfo(candles: np.ndarray, period: int = 14, scalar: float = 100, source_type: str = "close",
sequential: bool = False) -> Union[
float, np.ndarray]:
"""
CFO - Chande Forcast Oscillator
:param candles: np.ndarray
:param period: int - default: 14
:param scalar: float - default: 100
:param source_type: str - default: "close"
:param sequential: bool - default: False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
res = scalar * (source - talib.LINEARREG(source, timeperiod=period))
res /= source
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -1,46 +0,0 @@
from typing import Union
import numpy as np
try:
from numba import njit
except ImportError:
njit = lambda a : a
from jesse.helpers import get_candle_source, same_length, slice_candles
def cg(candles: np.ndarray, period: int = 10, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
"""
Center of Gravity (CG)
:param candles: np.ndarray
:param period: int - default: 10
:param source_type: str - default: "close"
:param sequential: bool - default: False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
res = go_fast(source, period)
return same_length(candles, res) if sequential else res[-1]
@njit
def go_fast(source, period): # Function is compiled to machine code when called the first time
res = np.full_like(source, fill_value=np.nan)
for i in range(source.size):
if i > period:
num = 0
denom = 0
for count in range(period - 1):
close = source[i - count]
if not np.isnan(close):
num += (1 + count) * close
denom += close
result = -num / denom if denom != 0 else 0
res[i] = result
return res

View File

@@ -4,23 +4,21 @@ import numpy as np
import talib
from scipy.ndimage.filters import maximum_filter1d, minimum_filter1d
from jesse.helpers import slice_candles
def chande(candles: np.ndarray, period: int = 22, mult: float = 3.0, direction: str = "long",
sequential: bool = False) -> Union[float, np.ndarray]:
def chande(candles: np.ndarray, period=22, mult=3.0, direction="long", sequential=False) -> Union[float, np.ndarray]:
"""
Chandelier Exits
:param candles: np.ndarray
:param period: int - default: 22
:param mult: float - default: 3.0
:param period: float - default: 3.0
:param direction: str - default: "long"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
candles_close = candles[:, 2]
candles_high = candles[:, 3]
@@ -40,10 +38,10 @@ def chande(candles: np.ndarray, period: int = 22, mult: float = 3.0, direction:
return result if sequential else result[-1]
def filter1d_same(a: np.ndarray, W: int, max_or_min: str, fillna=np.nan):
def filter1d_same(a, W, type, fillna=np.nan):
out_dtype = np.full(0, fillna).dtype
hW = (W - 1) // 2 # Half window size
if max_or_min == 'max':
if type == 'max':
out = maximum_filter1d(a, size=W, origin=hW)
else:
out = minimum_filter1d(a, size=W, origin=hW)

View File

@@ -1,36 +0,0 @@
from typing import Union
import numpy as np
import talib
from jesse.helpers import slice_candles
def chop(candles: np.ndarray, period: int = 14, scalar: float = 100, drift: int = 1, sequential: bool = False) -> Union[
float, np.ndarray]:
"""
Choppiness Index (CHOP)
:param candles: np.ndarray
:param period: int - default: 30
:param scalar: float - default: 100
:param drift: int - default: 1
:param sequential: bool - default: False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
candles_close = candles[:, 2]
candles_high = candles[:, 3]
candles_low = candles[:, 4]
atr_sum = talib.SUM(talib.ATR(candles_high, candles_low, candles_close, timeperiod=drift), period)
hh = talib.MAX(candles_high, period)
ll = talib.MIN(candles_low, period)
res = (scalar * (np.log10(atr_sum) - np.log10(hh - ll))) / np.log10(period)
return res if sequential else res[-1]

View File

@@ -1,41 +0,0 @@
from collections import namedtuple
import numpy as np
import talib
from jesse.helpers import slice_candles
CKSP = namedtuple('CKSP', ['long', 'short'])
def cksp(candles: np.ndarray, p: int = 10, x: float = 1.0, q: int = 9, sequential: bool = False) -> CKSP:
"""
Chande Kroll Stop (CKSP)
:param candles: np.ndarray
:param p: int - default: 10
:param x: float - default: 1.0
:param q: int - default: 9
:param sequential: bool - default: False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
candles_close = candles[:, 2]
candles_high = candles[:, 3]
candles_low = candles[:, 4]
atr = talib.ATR(candles_high, candles_low, candles_close, timeperiod=p)
LS0 = talib.MAX(candles_high, q) - x * atr
LS = talib.MAX(LS0, q)
SS0 = talib.MIN(candles_low, q) + x * atr
SS = talib.MIN(SS0, q)
if sequential:
return CKSP(LS, SS)
else:
return CKSP(LS[-1], SS[-1])

View File

@@ -4,24 +4,26 @@ import numpy as np
import talib
from jesse.helpers import get_candle_source
from jesse.helpers import slice_candles
def cmo(candles: np.ndarray, period: int = 14, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
def cmo(candles: np.ndarray, period=14, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
CMO - Chande Momentum Oscillator
:param candles: np.ndarray
:param period: int - default: 14
:param period: int - default=14
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
res = talib.CMO(source, timeperiod=period)
return res if sequential else res[-1]
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -3,21 +3,23 @@ from typing import Union
import numpy as np
import talib
from jesse.helpers import slice_candles
def correl(candles: np.ndarray, period: int = 5, sequential: bool = False) -> Union[float, np.ndarray]:
def correl(candles: np.ndarray, period=5, sequential=False) -> Union[float, np.ndarray]:
"""
CORREL - Pearson's Correlation Coefficient (r)
:param candles: np.ndarray
:param period: int - default: 5
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
res = talib.CORREL(candles[:, 3], candles[:, 4], timeperiod=period)
return res if sequential else res[-1]
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -1,18 +1,14 @@
import math
from collections import namedtuple
import numpy as np
try:
from numba import njit
except ImportError:
njit = lambda a : a
from jesse.helpers import get_candle_source, np_shift, slice_candles
from jesse.helpers import get_candle_source, np_shift
CC = namedtuple('CC', ['real', 'imag', 'angle', 'state'])
def correlation_cycle(candles: np.ndarray, period: int = 20, threshold: int = 9, source_type: str = "close",
sequential: bool = False) -> CC:
def correlation_cycle(candles: np.ndarray, period=20, threshold=9, source_type="close", sequential=False) -> CC:
"""
"Correlation Cycle, Correlation Angle, Market State - John Ehlers
@@ -20,32 +16,17 @@ def correlation_cycle(candles: np.ndarray, period: int = 20, threshold: int = 9,
:param period: int - default: 20
:param threshold: int - default: 9
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: CC(real, imag)
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
realPart, imagPart, angle = go_fast(source, period)
priorAngle = np_shift(angle, 1, fill_value=np.nan)
angle = np.where(np.logical_and(priorAngle > angle, priorAngle - angle < 270.0), priorAngle, angle)
# Market State Function
state = np.where(np.abs(angle - priorAngle) < threshold, np.where(angle >= 0.0, 1, np.where(angle < 0.0, -1, 0)), 0)
if sequential:
return CC(realPart, imagPart, angle, state)
else:
return CC(realPart[-1], imagPart[-1], angle[-1], state[-1])
@njit
def go_fast(source, period): # Function is compiled to machine code when called the first time
# Correlation Cycle Function
PIx2 = 4.0 * np.arcsin(1.0)
PIx2 = 4.0 * math.asin(1.0)
period = max(2, period)
realPart = np.full_like(source, np.nan)
@@ -65,34 +46,45 @@ def go_fast(source, period): # Function is compiled to machine code when called
for j in range(period):
jMinusOne = j + 1
X = 0 if np.isnan(source[i - jMinusOne]) else source[i - jMinusOne]
if np.isnan(source[i - jMinusOne]):
X = 0
else:
X = source[i - jMinusOne]
temp = PIx2 * jMinusOne / period
Yc = np.cos(temp)
Ys = -np.sin(temp)
Rx += X
Ix += X
Rxx += X * X
Ixx += X * X
Rxy += X * Yc
Ixy += X * Ys
Ryy += Yc * Yc
Iyy += Ys * Ys
Ry += Yc
Iy += Ys
Rx = Rx + X
Ix = Ix + X
Rxx = Rxx + X * X
Ixx = Ixx + X * X
Rxy = Rxy + X * Yc
Ixy = Ixy + X * Ys
Ryy = Ryy + Yc * Yc
Iyy = Iyy + Ys * Ys
Ry = Ry + Yc
Iy = Iy + Ys
temp_1 = period * Rxx - Rx**2
temp_2 = period * Ryy - Ry**2
if temp_1 > 0.0 and temp_2 > 0.0:
temp_1 = period * Rxx - Rx * Rx
temp_2 = period * Ryy - Ry * Ry
if (temp_1 > 0.0 and temp_2 > 0.0):
realPart[i] = (period * Rxy - Rx * Ry) / np.sqrt(temp_1 * temp_2)
temp_1 = period * Ixx - Ix**2
temp_2 = period * Iyy - Iy**2
if temp_1 > 0.0 and temp_2 > 0.0:
temp_1 = period * Ixx - Ix * Ix
temp_2 = period * Iyy - Iy * Iy
if (temp_1 > 0.0 and temp_2 > 0.0):
imagPart[i] = (period * Ixy - Ix * Iy) / np.sqrt(temp_1 * temp_2)
# Correlation Angle Phasor
HALF_OF_PI = np.arcsin(1.0)
HALF_OF_PI = math.asin(1.0)
angle = np.where(imagPart == 0, 0.0, np.degrees(np.arctan(realPart / imagPart) + HALF_OF_PI))
angle = np.where(imagPart > 0.0, angle - 180.0, angle)
priorAngle = np_shift(angle, 1, fill_value=np.nan)
angle = np.where(np.logical_and(priorAngle > angle, priorAngle - angle < 270.0), priorAngle, angle)
return realPart, imagPart, angle
# Market State Function
state = np.where(np.abs(angle - priorAngle) < threshold, np.where(angle >= 0.0, 1, np.where(angle < 0.0, -1, 0)), 0)
if sequential:
return CC(realPart, imagPart, angle, state)
else:
return CC(realPart[-1], imagPart[-1], angle[-1], state[-1])

View File

@@ -3,21 +3,20 @@ from typing import Union
import numpy as np
import tulipy as ti
from jesse.helpers import slice_candles, same_length
def cvi(candles: np.ndarray, period: int = 5, sequential: bool = False) -> Union[float, np.ndarray]:
def cvi(candles: np.ndarray, period=5, sequential=False) -> Union[float, np.ndarray]:
"""
CVI - Chaikins Volatility
:param candles: np.ndarray
:param period: int - default: 5
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
res = ti.cvi(np.ascontiguousarray(candles[:, 3]), np.ascontiguousarray(candles[:, 4]), period=period)
return same_length(candles, res) if sequential else res[-1]
return np.concatenate((np.full((candles.shape[0] - res.shape[0]), np.nan), res), axis=0) if sequential else res[-1]

View File

@@ -1,49 +0,0 @@
from typing import Union
import numpy as np
try:
from numba import njit
except ImportError:
njit = lambda a : a
from jesse.helpers import get_candle_source, slice_candles
def cwma(candles: np.ndarray, period: int = 14, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
"""
Cubed Weighted Moving Average
:param candles: np.ndarray
:param period: int - default: 14
:param source_type: str - default: "close"
:param sequential: bool - default: False
:return: float | np.ndarray
"""
# Accept normal array too.
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
res = vpwma_fast(source, period)
return res if sequential else res[-1]
@njit
def vpwma_fast(source, period):
newseries = np.copy(source)
for j in range(period + 1, source.shape[0]):
my_sum = 0.0
weightSum = 0.0
for i in range(period - 1):
weight = np.power(period - i, 3)
my_sum += (source[j - i] * weight)
weightSum += weight
newseries[j] = my_sum / weightSum
return newseries

View File

@@ -1,61 +1,49 @@
import talib
from collections import namedtuple
import numpy as np
import talib
try:
from numba import njit
except ImportError:
njit = lambda a : a
from jesse.helpers import get_candle_source
from jesse.helpers import slice_candles
DamianiVolatmeter = namedtuple('DamianiVolatmeter', ['vol', 'anti'])
DamianiVolatmeter = namedtuple('DamianiVolatmeter', ['vol', 'anti' ])
def damiani_volatmeter(candles: np.ndarray, vis_atr: int = 13, vis_std: int = 20, sed_atr: int = 40, sed_std: int = 100,
threshold: float = 1.4, source_type: str = "close",
sequential: bool = False) -> DamianiVolatmeter:
def damiani_volatmeter(candles: np.ndarray, vis_atr=13, vis_std=20, sed_atr=40, sed_std=100, threshold=1.4, source_type="close", sequential=False) -> DamianiVolatmeter:
"""
Damiani Volatmeter
:param candles: np.ndarray
:param vis_atr: int - default: 13
:param vis_std: int - default: 20
:param sed_atr: int - default: 40
:param sed_std: int - default: 100
:param threshold: float - default: 1.4
:param vis_atr: int - default=13
:param vis_std: int - default=20
:param sed_atr: int - default=40
:param sed_std: int - default=100
:param threshold: float - default=1.4
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
lag_s = 0.5
vol = np.full_like(source, 0)
t = np.full_like(source, 0)
atrvis = talib.ATR(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=vis_atr)
atrsed = talib.ATR(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=sed_atr)
vol, t = damiani_volatmeter_fast(source, sed_std, atrvis, atrsed, vis_std, threshold)
for i in range(source.shape[0]):
if not (i < sed_std):
vol[i]=atrvis[i] / atrsed[i] + lag_s * (vol[i - 1] - vol[i - 3])
anti_thres = np.std(source[i - vis_std:i]) / np.std(source[i - sed_std:i])
t[i] = threshold - anti_thres
if sequential:
return DamianiVolatmeter(vol, t)
else:
return DamianiVolatmeter(vol[-1], t[-1])
@njit
def damiani_volatmeter_fast(source, sed_std, atrvis, atrsed, vis_std,
threshold): # Function is compiled to machine code when called the first time
lag_s = 0.5
vol = np.full_like(source, 0)
t = np.full_like(source, 0)
for i in range(source.shape[0]):
if i >= sed_std:
vol[i] = atrvis[i] / atrsed[i] + lag_s * (vol[i - 1] - vol[i - 3])
anti_thres = np.std(source[i - vis_std:i]) / np.std(source[i - sed_std:i])
t[i] = threshold - anti_thres
return vol, t

View File

@@ -2,31 +2,46 @@ from typing import Union
import numpy as np
from jesse.helpers import get_candle_source, slice_candles
from .high_pass_2_pole import high_pass_2_pole_fast
from jesse.helpers import get_candle_source
def dec_osc(candles: np.ndarray, hp_period: int = 125, k: float = 1, source_type: str = "close",
sequential: bool = False) -> Union[float, np.ndarray]:
def dec_osc(candles: np.ndarray, hp_period=125, k=1, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
Ehlers Decycler Oscillator
:param candles: np.ndarray
:param hp_period: int - default: 125
:param k: float - default: 1
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param hp_period: int - default=125
:param k: float - default=1
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
alphaArg1 = 2 * np.pi * 0.707 / hp_period
alpha1 = (np.cos(alphaArg1) + np.sin(alphaArg1) - 1) / np.cos(alphaArg1)
coeff1 = np.array([(1 - alpha1 / 2) ** 2, 2 * (1 - alpha1), -(1 - alpha1) ** 2])
hp1 = np.copy(source)
hp = high_pass_2_pole_fast(source, hp_period)
dec = source - hp
decosc = high_pass_2_pole_fast(dec, 0.5 * hp_period)
alphaArg2 = 2 * np.pi * 0.707 / (0.5 * hp_period)
alpha2 = (np.cos(alphaArg2) + np.sin(alphaArg2) - 1) / np.cos(alphaArg2)
coeff2 = np.array([(1 - alpha2 / 2) ** 2, 2 * (1 - alpha2), -(1 - alpha2) ** 2])
hp2 = np.copy(source)
res = 100 * k * decosc / source
for i in range(source.shape[0]):
val1 = np.array([source[i] - 2 * source[i - 1] + source[i - 2], hp1[i - 1], hp1[i - 2]])
hp1[i] = np.matmul(coeff1, val1)
return res if sequential else res[-1]
val2 = np.array(
[(source[i] - hp1[i]) - 2 * (source[i - 1] - hp1[i - 1]) + (source[i - 2] - hp1[i - 2]), hp2[i - 1],
hp2[i - 2]])
hp2[i] = np.matmul(coeff2, val2)
res = 100 * k * hp2 / source
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -2,26 +2,35 @@ from typing import Union
import numpy as np
from jesse.helpers import get_candle_source, slice_candles
from .high_pass_2_pole import high_pass_2_pole_fast
from jesse.helpers import get_candle_source
def decycler(candles: np.ndarray, hp_period: int = 125, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
def decycler(candles: np.ndarray, hp_period=125, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
Ehlers Simple Decycler
:param candles: np.ndarray
:param hp_period: int - default: 125
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param hp_period: int - default=125
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
hp = high_pass_2_pole_fast(source, hp_period)
res = source - hp
alphaArg1 = 2 * np.pi * 0.707 / hp_period
alpha1 = (np.cos(alphaArg1) + np.sin(alphaArg1) - 1) / np.cos(alphaArg1)
coeff1 = np.array([(1 - alpha1 / 2) ** 2, 2 * (1 - alpha1), -(1 - alpha1) ** 2])
hp1 = np.copy(source)
return res if sequential else res[-1]
for i in range(source.shape[0]):
val1 = np.array([source[i] - 2 * source[i - 1] + source[i - 2], hp1[i - 1], hp1[i - 2]])
hp1[i] = np.matmul(coeff1, val1)
res = source - hp1
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -3,27 +3,24 @@ from typing import Union
import numpy as np
import talib
from jesse.helpers import get_candle_source, slice_candles
from jesse.helpers import get_candle_source
def dema(candles: np.ndarray, period: int = 30, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
def dema(candles: np.ndarray, period=30, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
DEMA - Double Exponential Moving Average
:param candles: np.ndarray
:param period: int - default: 30
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
res = talib.DEMA(source, timeperiod=period)
return res if sequential else res[-1]

View File

@@ -1,45 +0,0 @@
from typing import Union
import numpy as np
import talib
from jesse.indicators.mean_ad import mean_ad
from jesse.indicators.median_ad import median_ad
from jesse.helpers import slice_candles
def devstop(candles: np.ndarray, period: int = 20, mult: float = 0, devtype: int = 0, direction: str = "long",
sequential: bool = False) -> Union[
float, np.ndarray]:
"""
Kase Dev Stops
:param candles: np.ndarray
:param period: int - default: 20
:param mult: float - default: 0
:param devtype: int - default: 0
:param direction: str - default: long
:param sequential: bool - default: False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
high = candles[:, 3]
low = candles[:, 4]
AVTR = talib.SMA(talib.MAX(high, 2) - talib.MIN(low, 2), period)
if devtype == 0:
SD = talib.STDDEV(talib.MAX(high, 2) - talib.MIN(low, 2), period)
elif devtype == 1:
SD = mean_ad(talib.MAX(high, 2) - talib.MIN(low, 2), period, sequential=True)
elif devtype == 2:
SD = median_ad(talib.MAX(high, 2) - talib.MIN(low, 2), period, sequential=True)
if direction == "long":
res = talib.MAX(high - AVTR - mult * SD, period)
else:
res = talib.MIN(low + AVTR + mult * SD, period)
return res if sequential else res[-1]

View File

@@ -3,22 +3,21 @@ from collections import namedtuple
import numpy as np
import talib
from jesse.helpers import slice_candles
DI = namedtuple('DI', ['plus', 'minus'])
def di(candles: np.ndarray, period: int = 14, sequential: bool = False) -> DI:
def di(candles: np.ndarray, period=14, sequential=False) -> DI:
"""
DI - Directional Indicator
:param candles: np.ndarray
:param period: int - default: 14
:param sequential: bool - default: False
:param period: int - default=14
:param sequential: bool - default=False
:return: DI(plus, minus)
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
MINUS_DI = talib.MINUS_DI(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)
PLUS_DI = talib.PLUS_DI(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)

View File

@@ -3,25 +3,24 @@ from collections import namedtuple
import numpy as np
import talib
from jesse.helpers import slice_candles
DM = namedtuple('DM', ['plus', 'minus'])
def dm(candles: np.ndarray, period: int = 14, sequential: bool = False) -> DM:
def dm(candles: np.ndarray, period=14, sequential=False) -> DM:
"""
DM - Directional Movement
:param candles: np.ndarray
:param period: int - default: 14
:param sequential: bool - default: False
:param period: int - default=14
:param sequential: bool - default=False
:return: DM(plus, minus)
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
MINUS_DI = talib.MINUS_DM(candles[:, 3], candles[:, 4], timeperiod=period)
PLUS_DI = talib.PLUS_DM(candles[:, 3], candles[:, 4], timeperiod=period)
MINUS_DI = talib.MINUS_DM(candles[:, 3], candles[:, 4], timeperiod=period)
PLUS_DI = talib.PLUS_DM(candles[:, 3], candles[:, 4], timeperiod=period)
if sequential:
return DM(PLUS_DI, MINUS_DI)

View File

@@ -3,22 +3,21 @@ from collections import namedtuple
import numpy as np
import talib
from jesse.helpers import slice_candles
DonchianChannel = namedtuple('DonchianChannel', ['upperband', 'middleband', 'lowerband'])
def donchian(candles: np.ndarray, period: int = 20, sequential: bool = False) -> DonchianChannel:
def donchian(candles: np.ndarray, period=20, sequential=False) -> DonchianChannel:
"""
Donchian Channels
:param candles: np.ndarray
:param period: int - default: 20
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: DonchianChannel(upperband, middleband, lowerband)
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
UC = talib.MAX(candles[:, 3], timeperiod=period)
LC = talib.MIN(candles[:, 4], timeperiod=period)

View File

@@ -3,24 +3,24 @@ from typing import Union
import numpy as np
import tulipy as ti
from jesse.helpers import get_candle_source, same_length, slice_candles
from jesse.helpers import get_candle_source
def dpo(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
def dpo(candles: np.ndarray, period=5, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
DPO - Detrended Price Oscillator
:param candles: np.ndarray
:param period: int - default: 5
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
res = ti.dpo(np.ascontiguousarray(source), period=period)
return same_length(candles, res) if sequential else res[-1]
return np.concatenate((np.full((candles.shape[0] - res.shape[0]), np.nan), res), axis=0) if sequential else res[-1]

View File

@@ -4,23 +4,22 @@ import numpy as np
import talib
import jesse.helpers as jh
from jesse.helpers import slice_candles
def dti(candles: np.ndarray, r: int = 14, s: int = 10, u: int = 5, sequential: bool = False) -> Union[
float, np.ndarray]:
def dti(candles: np.ndarray, r=14, s=10, u=5, sequential=False) -> Union[float, np.ndarray]:
"""
DTI by William Blau
:param candles: np.ndarray
:param r: int - default: 14
:param s: int - default: 10
:param u: int - default: 5
:param sequential: bool - default: False
:param r: int - default=14
:param s: int - default=10
:param u: int - default=5
:param sequential: bool - default=False
:return: float
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
high = candles[:, 3]
low = candles[:, 4]

View File

@@ -3,20 +3,20 @@ from typing import Union
import numpy as np
import talib
from jesse.helpers import slice_candles
def dx(candles: np.ndarray, period: int = 14, sequential: bool = False) -> Union[float, np.ndarray]:
def dx(candles: np.ndarray, period=14, sequential=False) -> Union[float, np.ndarray]:
"""
DX - Directional Movement Index
:param candles: np.ndarray
:param period: int - default: 14
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
res = talib.DX(candles[:, 3], candles[:, 4], candles[:, 2], timeperiod=period)

View File

@@ -1,53 +0,0 @@
from typing import Union
import numpy as np
try:
from numba import njit
except ImportError:
njit = lambda a: a
from jesse.helpers import get_candle_source, slice_candles
def edcf(candles: np.ndarray, period: int = 15, source_type: str = "hl2", sequential: bool = False) -> Union[
float, np.ndarray]:
"""
Ehlers Distance Coefficient Filter
:param candles: np.ndarray
:param period: int - default: 15
:param source_type: str - default: "close"
:param sequential: bool - default: False
:return: float | np.ndarray
"""
# Accept normal array too.
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
res = edcf_fast(source, period)
return res if sequential else res[-1]
@njit
def edcf_fast(source, period):
newseries = np.full_like(source, np.nan)
for j in range(2 * period, source.shape[0]):
num = 0.0
coefSum = 0.0
for i in range(period):
distance = 0.0
for lb in range(1, period):
distance += np.power(source[j - i] - source[j - i - lb], 2)
num += distance * source[j - i]
coefSum += distance
newseries[j] = num / coefSum if coefSum != 0 else 0
return newseries

View File

@@ -2,41 +2,31 @@ from typing import Union
import numpy as np
import talib
try:
from numba import njit
except ImportError:
njit = lambda a : a
from jesse.helpers import get_candle_source, same_length, slice_candles
from jesse.helpers import get_candle_source
def efi(candles: np.ndarray, period: int = 13, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
def efi(candles: np.ndarray, period=13, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
EFI - Elders Force Index
:param candles: np.ndarray
:param period: int - default: 13
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
dif = efi_fast(source, candles[:, 5])
dif = np.zeros(len(source) - 1)
for i in range(1, len(source)):
dif[i - 1] = (source[i] - source[i - 1]) * candles[:, 5][i]
res = talib.EMA(dif, timeperiod=period)
res_with_nan = same_length(candles, res)
res_with_nan = np.concatenate((np.full((candles.shape[0] - res.shape[0]), np.nan), res))
return res_with_nan if sequential else res_with_nan[-1]
@njit
def efi_fast(source, volume):
dif = np.zeros(source.size - 1)
for i in range(1, source.size):
dif[i - 1] = (source[i] - source[i - 1]) * volume[i]
return dif

View File

@@ -3,27 +3,24 @@ from typing import Union
import numpy as np
import talib
from jesse.helpers import get_candle_source, slice_candles
from jesse.helpers import get_candle_source
def ema(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
def ema(candles: np.ndarray, period=5, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
EMA - Exponential Moving Average
:param candles: np.ndarray
:param period: int - default: 5
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
res = talib.EMA(source, timeperiod=period)
return res if sequential else res[-1]

View File

@@ -1,53 +1,32 @@
import math
from collections import namedtuple
import numpy as np
import talib
try:
from numba import njit
except ImportError:
njit = lambda a : a
from jesse.helpers import slice_candles
EMD = namedtuple('EMD', ['upperband', 'middleband', 'lowerband'])
def emd(candles: np.ndarray, period: int = 20, delta=0.5, fraction=0.1, sequential: bool = False) -> EMD:
def emd(candles: np.ndarray, period=20, delta=0.5, fraction=0.1, sequential=False) -> EMD:
"""
Empirical Mode Decomposition by John F. Ehlers and Ric Way
:param candles: np.ndarray
:param period: int - default: 20
:param delta: float - default: 0.5
:param fraction: float - default: 0.1
:param sequential: bool - default: False
:param period: int - default=20
:param delta: float - default=0.5
:param fraction: float - default=0.1
:param sequential: bool - default=False
:return: EMD(upperband, middleband, lowerband)
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
price = (candles[:, 3] + candles[:, 4]) / 2
bp = bp_fast(price, period, delta)
mean = talib.SMA(bp, timeperiod=2 * period)
peak, valley = peak_valley_fast(bp, price)
avg_peak = fraction * talib.SMA(peak, timeperiod=50)
avg_valley = fraction * talib.SMA(valley, timeperiod=50)
if sequential:
return EMD(avg_peak, mean, avg_valley)
else:
return EMD(avg_peak[-1], mean[-1], avg_valley[-1])
@njit
def bp_fast(price, period, delta):
# bandpass filter
beta = np.cos(2 * np.pi / period)
gamma = 1 / np.cos(4 * np.pi * delta / period)
alpha = gamma - np.sqrt(gamma * gamma - 1)
beta = math.cos(2 * math.pi / period)
gamma = 1 / math.cos(4 * math.pi * delta / period)
alpha = gamma - math.sqrt(gamma * gamma - 1)
bp = np.zeros_like(price)
for i in range(price.shape[0]):
@@ -55,11 +34,9 @@ def bp_fast(price, period, delta):
bp[i] = 0.5 * (1 - alpha) * (price[i] - price[i - 2]) + beta * (1 + alpha) * bp[i - 1] - alpha * bp[i - 2]
else:
bp[i] = 0.5 * (1 - alpha) * (price[i] - price[i - 2])
return bp
mean = talib.SMA(bp, timeperiod=2 * period)
@njit
def peak_valley_fast(bp, price):
peak = np.copy(bp)
valley = np.copy(bp)
@@ -72,4 +49,10 @@ def peak_valley_fast(bp, price):
if bp[i - 1] < bp[i] and bp[i - 1] < bp[i - 2]:
valley[i] = bp[i - 1]
return peak, valley
avg_peak = fraction * talib.SMA(peak, timeperiod=50)
avg_valley = fraction * talib.SMA(valley, timeperiod=50)
if sequential:
return EMD(avg_peak, mean, avg_valley)
else:
return EMD(avg_peak[-1], mean[-1], avg_valley[-1])

View File

@@ -3,21 +3,20 @@ from typing import Union
import numpy as np
import tulipy as ti
from jesse.helpers import slice_candles, same_length
def emv(candles: np.ndarray, sequential: bool = False) -> Union[float, np.ndarray]:
def emv(candles: np.ndarray, sequential=False) -> Union[float, np.ndarray]:
"""
EMV - Ease of Movement
:param candles: np.ndarray
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
res = ti.emv(np.ascontiguousarray(candles[:, 3]), np.ascontiguousarray(candles[:, 4]),
np.ascontiguousarray(candles[:, 5]))
res = ti.emv(np.ascontiguousarray(candles[:, 3]), np.ascontiguousarray(candles[:, 4]), np.ascontiguousarray(candles[:, 5]))
return same_length(candles, res) if sequential else res[-1]
return np.concatenate((np.full((candles.shape[0] - res.shape[0]), np.nan), res), axis=0) if sequential else res[-1]

View File

@@ -1,50 +0,0 @@
from typing import Union
import numpy as np
try:
from numba import njit
except ImportError:
njit = lambda a : a
from jesse.helpers import get_candle_source, slice_candles
def epma(candles: np.ndarray, period: int = 11, offset: int = 4, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
"""
End Point Moving Average
:param candles: np.ndarray
:param period: int - default: 14
:param offset: int - default: 4
:param source_type: str - default: "close"
:param sequential: bool - default: False
:return: float | np.ndarray
"""
# Accept normal array too.
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
res = epma_fast(source, period, offset)
return res if sequential else res[-1]
@njit
def epma_fast(source, period, offset):
newseries = np.copy(source)
for j in range(period + offset + 1 , source.shape[0]):
my_sum = 0.0
weightSum = 0.0
for i in range(period - 1):
weight = period - i - offset
my_sum += (source[j - i] * weight)
weightSum += weight
newseries[j] = 1 / weightSum * my_sum
return newseries

View File

@@ -1,32 +0,0 @@
from typing import Union
import numpy as np
from numpy.lib.stride_tricks import sliding_window_view
from jesse.helpers import get_candle_source, slice_candles, same_length
def er(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
"""
ER - The Kaufman Efficiency indicator
:param candles: np.ndarray
:param period: int - default: 5
:param source_type: str - default: "close"
:param sequential: bool - default: False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
change = np.abs(np.diff(source, period))
abs_dif = np.abs(np.diff(source))
swv = sliding_window_view(abs_dif, window_shape=period)
volatility = swv.sum()
res = change / volatility
return same_length(candles, res) if sequential else res[-1]

View File

@@ -1,35 +0,0 @@
from collections import namedtuple
import numpy as np
from jesse.indicators.ma import ma
from jesse.helpers import get_candle_source, slice_candles
ERI = namedtuple('ERI', ['bull', 'bear'])
def eri(candles: np.ndarray, period: int = 13, matype: int = 1, source_type: str = "close",
sequential: bool = False) -> ERI:
"""
Elder Ray Index (ERI)
:param candles: np.ndarray
:param period: int - default: 13
:param matype: int - default: 1
:param source_type: str - default: "close"
:param sequential: bool - default: False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
ema = ma(source, period=period, matype=matype, sequential=True)
bull = candles[:, 3] - ema
bear = candles[:, 4] - ema
if sequential:
return ERI(bull, bear)
else:
return ERI(bull[-1], bear[-1])

View File

@@ -3,27 +3,28 @@ from collections import namedtuple
import numpy as np
import tulipy as ti
from jesse.helpers import slice_candles, same_length
FisherTransform = namedtuple('FisherTransform', ['fisher', 'signal'])
def fisher(candles: np.ndarray, period: int = 9, sequential: bool = False) -> FisherTransform:
def fisher(candles: np.ndarray, period=9, sequential=False) -> FisherTransform:
"""
The Fisher Transform helps identify price reversals.
:param candles: np.ndarray
:param period: int - default: 9
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: FisherTransform(fisher, signal)
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
fisher_val, fisher_signal = ti.fisher(np.ascontiguousarray(candles[:, 3]), np.ascontiguousarray(candles[:, 4]),
period=period)
fisher, fisher_signal = ti.fisher(np.ascontiguousarray(candles[:, 3]), np.ascontiguousarray(candles[:, 4]),
period=period)
if sequential:
return FisherTransform(same_length(candles, fisher_val), same_length(candles, fisher_signal))
return FisherTransform(np.concatenate((np.full((candles.shape[0] - fisher.shape[0]), np.nan), fisher), axis=0),
np.concatenate(
(np.full((candles.shape[0] - fisher_signal.shape[0]), np.nan), fisher_signal)))
else:
return FisherTransform(fisher_val[-1], fisher_signal[-1])
return FisherTransform(fisher[-1], fisher_signal[-1])

View File

@@ -3,24 +3,24 @@ from typing import Union
import numpy as np
import tulipy as ti
from jesse.helpers import get_candle_source, same_length, slice_candles
from jesse.helpers import get_candle_source
def fosc(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
def fosc(candles: np.ndarray, period=5, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
FOSC - Forecast Oscillator
:param candles: np.ndarray
:param period: int - default: 5
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
res = ti.fosc(np.ascontiguousarray(source), period=period)
return same_length(candles, res) if sequential else res[-1]
return np.concatenate((np.full((candles.shape[0] - res.shape[0]), np.nan), res), axis=0) if sequential else res[-1]

View File

@@ -1,16 +1,10 @@
import math
from typing import Union
import numpy as np
try:
from numba import njit
except ImportError:
njit = lambda a : a
from jesse.helpers import slice_candles
def frama(candles: np.ndarray, window: int = 10, FC: int = 1, SC: int = 300, sequential: bool = False) -> Union[
float, np.ndarray]:
def frama(candles: np.ndarray, window=10, FC=1, SC=300, sequential=False) -> Union[float, np.ndarray]:
"""
Fractal Adaptive Moving Average (FRAMA)
@@ -18,11 +12,13 @@ def frama(candles: np.ndarray, window: int = 10, FC: int = 1, SC: int = 300, seq
:param window: int - default: 10
:param FC: int - default: 1
:param SC: int - default: 300
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if len(candles) > 240:
candles = candles[-240:]
n = window
@@ -31,43 +27,35 @@ def frama(candles: np.ndarray, window: int = 10, FC: int = 1, SC: int = 300, seq
print("FRAMA n must be even. Adding one")
n += 1
res = frame_fast(candles, n, SC, FC)
w = math.log(2.0 / (SC + 1))
if sequential:
return res
else:
return res[-1]
@njit
def frame_fast(candles, n, SC, FC):
w = np.log(2.0 / (SC + 1))
D = np.zeros(candles.size)
D = np.zeros(len(candles))
D[:n] = np.NaN
alphas = np.zeros(candles.size)
alphas = np.zeros(len(candles))
alphas[:n] = np.NaN
for i in range(n, candles.shape[0]):
for i in range(n, len(candles)):
per = candles[i - n:i]
v1 = per[per.shape[0] // 2:]
v2 = per[:per.shape[0] // 2]
# take 2 batches of the input
split = np.split(per, 2)
v1 = split[0]
v2 = split[1]
N1 = (max(v1[:, 3]) - min(v1[:, 4])) / (n / 2)
N2 = (max(v2[:, 3]) - min(v2[:, 4])) / (n / 2)
N3 = (max(per[:, 3]) - min(per[:, 4])) / n
N1 = (np.max(v1[:, 3]) - np.min(v1[:, 4])) / (n / 2)
N2 = (np.max(v2[:, 3]) - np.min(v2[:, 4])) / (n / 2)
N3 = (np.max(per[:, 3]) - np.min(per[:, 4])) / n
if N1 > 0 and N2 > 0 and N3 > 0:
D[i] = (np.log(N1 + N2) - np.log(N3)) / np.log(2)
D[i] = (math.log(N1 + N2) - math.log(N3)) / math.log(2)
else:
D[i] = D[i - 1]
oldalpha = np.exp(w * (D[i] - 1))
oldalpha = math.exp(w * (D[i] - 1))
# keep btwn 1 & 0.01
oldalpha = max([oldalpha, 0.1])
oldalpha = min([oldalpha, 1])
oldalpha = np.max([oldalpha, 0.1])
oldalpha = np.min([oldalpha, 1])
oldN = (2 - oldalpha) / oldalpha
N = ((SC - FC) * ((oldN - 1) / (SC - 1))) + FC
@@ -79,10 +67,14 @@ def frame_fast(candles, n, SC, FC):
else:
alphas[i] = alpha_
frama_val = np.zeros(candles.shape[0])
frama_val[n - 1] = np.mean(candles[:, 2][:n])
frama_val[:n - 1] = np.NaN
frama = np.zeros(len(candles))
frama[n - 1] = np.mean(candles[:, 2][:n])
frama[:n - 1] = np.NaN
for i in range(n, frama_val.shape[0]):
frama_val[i] = (alphas[i] * candles[:, 2][i]) + (1 - alphas[i]) * frama_val[i - 1]
return frama_val
for i in range(n, len(frama)):
frama[i] = (alphas[i] * candles[:, 2][i]) + (1 - alphas[i]) * frama[i - 1]
if sequential:
return frama
else:
return frama[-1]

View File

@@ -1,54 +0,0 @@
from math import fabs
from typing import Union
import numpy as np
from numpy.lib.stride_tricks import sliding_window_view
from jesse.helpers import get_candle_source, slice_candles, same_length
def fwma(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
"""
Fibonacci's Weighted Moving Average (FWMA)
:param candles: np.ndarray
:param period: int - default: 5
:param source_type: str - default: "close"
:param sequential: bool - default: False
:return: float | np.ndarray
"""
# Accept normal array too.
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
fibs = fibonacci(n=period)
swv = sliding_window_view(source, window_shape=period)
res = np.average(swv, weights=fibs, axis=-1)
return same_length(candles, res) if sequential else res[-1]
def fibonacci(n: int = 2) -> np.ndarray:
"""Fibonacci Sequence as a numpy array"""
n = int(fabs(n)) if n >= 0 else 2
n -= 1
a, b = 1, 1
result = np.array([a])
for _ in range(n):
a, b = b, a + b
result = np.append(result, a)
fib_sum = np.sum(result)
if fib_sum > 0:
return result / fib_sum
else:
return result

View File

@@ -4,23 +4,23 @@ import numpy as np
import talib
from jesse.helpers import get_candle_source, np_shift
from jesse.helpers import slice_candles
GATOR = namedtuple('GATOR', ['upper', 'lower', 'upper_change', 'lower_change'])
def gatorosc(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> GATOR:
def gatorosc(candles: np.ndarray, source_type="close", sequential=False) -> GATOR:
"""
Gator Oscillator by Bill M. Williams
:param candles: np.ndarray
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: GATOR(upper, lower, upper_change, lower_change)
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
@@ -39,7 +39,6 @@ def gatorosc(candles: np.ndarray, source_type: str = "close", sequential: bool =
else:
return GATOR(upper[-1], lower[-1], upper_change[-1], lower_change[-1])
def numpy_ewma(data, window):
"""
@@ -48,11 +47,13 @@ def numpy_ewma(data, window):
:return:
"""
alpha = 1 / window
# scale = 1 / (1 - alpha)
scale = 1 / (1 - alpha)
n = data.shape[0]
scale_arr = (1 - alpha) ** (-1 * np.arange(n))
weights = (1 - alpha) ** np.arange(n)
pw0 = (1 - alpha) ** (n - 1)
mult = data * pw0 * scale_arr
cumsums = mult.cumsum()
return cumsums * scale_arr[::-1] / weights.cumsum()
out = cumsums * scale_arr[::-1] / weights.cumsum()
return out

View File

@@ -1,54 +1,36 @@
import math
from typing import Union
import numpy as np
try:
from numba import njit
except ImportError:
njit = lambda a : a
from jesse.helpers import get_candle_source, slice_candles
from jesse.helpers import get_candle_source
def gauss(candles: np.ndarray, period: int = 14, poles: int = 4, source_type: str = "close",
sequential: bool = False) -> Union[float, np.ndarray]:
def gauss(candles: np.ndarray, period=14, poles=4, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
Gaussian Filter
:param candles: np.ndarray
:param period: int - default: 14
:param poles: int - default: 4
:param period: int - default=14
:param poles: int - default=4
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
if not sequential and len(candles) > 240:
candles = candles[-240:]
fil, to_fill = gauss_fast(source, period, poles)
if to_fill != 0:
res = np.insert(fil[poles:], 0, np.repeat(np.nan, to_fill))
else:
res = fil[poles:]
return res if sequential else res[-1]
@njit
def gauss_fast(source, period, poles):
N = source.size
source = get_candle_source(candles, source_type=source_type)
N = len(source)
source = source[~np.isnan(source)]
to_fill = N - source.size
PI = np.pi
beta = (1 - np.cos(2 * PI / period)) / (np.power(2, 1 / poles) - 1)
alpha = -beta + np.sqrt(np.power(beta, 2) + 2 * beta)
to_fill = N - len(source)
PI = math.pi
beta = (1 - math.cos(2 * PI / period)) / (math.pow(2, 1 / poles) - 1)
alpha = -beta + math.sqrt(math.pow(beta, 2) + 2 * beta)
fil = np.zeros(poles + source.size)
fil = np.zeros(poles + len(source))
if poles == 1:
coeff = np.array([alpha, (1 - alpha)])
elif poles == 2:
@@ -58,7 +40,7 @@ def gauss_fast(source, period, poles):
elif poles == 4:
coeff = np.array([alpha ** 4, 4 * (1 - alpha), -6 * (1 - alpha) ** 2, 4 * (1 - alpha) ** 3, -(1 - alpha) ** 4])
for i in range(source.size):
for i in range(len(source)):
if poles == 1:
val = np.array([source[i].item(), fil[i]])
elif poles == 2:
@@ -70,4 +52,12 @@ def gauss_fast(source, period, poles):
fil[poles + i] = np.dot(coeff, val)
return fil, to_fill
if to_fill != 0:
res = np.insert(fil[poles:], 0, np.repeat(np.nan, to_fill))
else:
res = fil[poles:]
if sequential:
return res
else:
return None if np.isnan(res[-1]) else res[-1]

View File

@@ -1,47 +1,37 @@
import math
from typing import Union
import numpy as np
try:
from numba import njit
except ImportError:
njit = lambda a : a
from jesse.helpers import get_candle_source, slice_candles
from jesse.helpers import get_candle_source
def high_pass(candles: np.ndarray, period: int = 48, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
def high_pass(candles: np.ndarray, period=48, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
(1 pole) high-pass filter indicator by John F. Ehlers
High Pass Filter indicator by John F. Ehlers
:param candles: np.ndarray
:param period: int - default: 48
:param period: int - default=48
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
if not sequential and len(candles) > 240:
candles = candles[-240:]
hpf = high_pass_fast(source, period)
source = get_candle_source(candles, source_type=source_type)
hpf = np.full_like(source, 0)
for i in range(source.shape[0]):
if not (i < 2):
alpha_arg = 2 * math.pi / (period * 1.414)
alpha1 = (math.cos(alpha_arg) + math.sin(alpha_arg) - 1) / math.cos(alpha_arg)
hpf[i] = math.pow(1.0 - alpha1 / 2.0, 2) * (source[i] - 2 * source[i - 1] + source[i - 2]) + 2 * (1 - alpha1) * hpf[i - 1] - math.pow(1 - alpha1, 2) * hpf[i - 2]
if sequential:
return hpf
else:
return None if np.isnan(hpf[-1]) else hpf[-1]
@njit
def high_pass_fast(source, period): # Function is compiled to machine code when called the first time
k = 1
alpha = 1 + (np.sin(2 * np.pi * k / period) - 1) / np.cos(2 * np.pi * k / period)
newseries = np.copy(source)
for i in range(1, source.shape[0]):
newseries[i] = (1 - alpha / 2) * source[i] - (1 - alpha / 2) * source[i - 1] \
+ (1 - alpha) * newseries[i - 1]
return newseries

View File

@@ -1,49 +0,0 @@
from typing import Union
import numpy as np
try:
from numba import njit
except ImportError:
njit = lambda a : a
from jesse.helpers import get_candle_source, slice_candles
def high_pass_2_pole(candles: np.ndarray, period: int = 48, source_type: str = "close", sequential: bool = False) -> \
Union[
float, np.ndarray]:
"""
(2 pole) high-pass filter indicator by John F. Ehlers
:param candles: np.ndarray
:param period: int - default: 48
:param source_type: str - default: "close"
:param sequential: bool - default: False
:return: float | np.ndarray
"""
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
hpf = high_pass_2_pole_fast(source, period)
if sequential:
return hpf
else:
return None if np.isnan(hpf[-1]) else hpf[-1]
@njit
def high_pass_2_pole_fast(source, period, K=0.707): # Function is compiled to machine code when called the first time
alpha = 1 + (np.sin(2 * np.pi * K / period) - 1) / np.cos(2 * np.pi * K / period)
newseries = np.copy(source)
for i in range(2, source.shape[0]):
newseries[i] = (1 - alpha / 2) ** 2 * source[i] \
- 2 * (1 - alpha / 2) ** 2 * source[i - 1] \
+ (1 - alpha / 2) ** 2 * source[i - 2] \
+ 2 * (1 - alpha) * newseries[i - 1] - (1 - alpha) ** 2 * newseries[i - 2]
return newseries

View File

@@ -3,28 +3,24 @@ from typing import Union
import numpy as np
import tulipy as ti
from jesse.helpers import get_candle_source, same_length, slice_candles
from jesse.helpers import get_candle_source
def hma(candles: np.ndarray, period: int = 5, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
def hma(candles: np.ndarray, period=5, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
Hull Moving Average
:param candles: np.ndarray
:param period: int - default: 5
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
# Accept normal array too.
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
res = ti.hma(np.ascontiguousarray(source), period=period)
return same_length(candles, res) if sequential else res[-1]
return np.concatenate((np.full((candles.shape[0] - res.shape[0]), np.nan), res), axis=0) if sequential else res[-1]

View File

@@ -4,20 +4,20 @@ import numpy as np
import talib
from jesse.helpers import get_candle_source
from jesse.helpers import slice_candles
def ht_dcperiod(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> Union[float, np.ndarray]:
def ht_dcperiod(candles: np.ndarray, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
HT_DCPERIOD - Hilbert Transform - Dominant Cycle Period
:param candles: np.ndarray
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
res = talib.HT_DCPERIOD(source)

View File

@@ -4,22 +4,22 @@ import numpy as np
import talib
from jesse.helpers import get_candle_source
from jesse.helpers import slice_candles
def ht_dcphase(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> Union[float, np.ndarray]:
def ht_dcphase(candles: np.ndarray, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
HT_DCPHASE - Hilbert Transform - Dominant Cycle Phase
:param candles: np.ndarray
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
res = talib.HT_DCPHASE(source)
res = talib.HT_DCPHASE (source)
return res if sequential else res[-1]

View File

@@ -4,22 +4,21 @@ import numpy as np
import talib
from jesse.helpers import get_candle_source
from jesse.helpers import slice_candles
IQ = namedtuple('IQ', ['inphase', 'quadrature'])
def ht_phasor(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> IQ:
def ht_phasor(candles: np.ndarray, source_type="close", sequential=False) -> IQ:
"""
HT_PHASOR - Hilbert Transform - Phasor Components
:param candles: np.ndarray
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: IQ(inphase, quadrature)
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
inphase, quadrature = talib.HT_PHASOR(source)

View File

@@ -4,22 +4,21 @@ import numpy as np
import talib
from jesse.helpers import get_candle_source
from jesse.helpers import slice_candles
SINEWAVE = namedtuple('SINEWAVE', ['sine', 'lead'])
def ht_sine(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> SINEWAVE:
def ht_sine(candles: np.ndarray, source_type="close", sequential=False) -> SINEWAVE:
"""
HT_SINE - Hilbert Transform - SineWave
:param candles: np.ndarray
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: SINEWAVE(sine, lead)
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
sine, leadsine = talib.HT_SINE(source)

View File

@@ -4,25 +4,22 @@ import numpy as np
import talib
from jesse.helpers import get_candle_source
from jesse.helpers import slice_candles
def ht_trendline(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> Union[float, np.ndarray]:
def ht_trendline(candles: np.ndarray, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
HT_TRENDLINE - Hilbert Transform - Instantaneous Trendline
:param candles: np.ndarray
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
res = talib.HT_TRENDLINE(source)
return res if sequential else res[-1]

View File

@@ -4,20 +4,20 @@ import numpy as np
import talib
from jesse.helpers import get_candle_source
from jesse.helpers import slice_candles
def ht_trendmode(candles: np.ndarray, source_type: str = "close", sequential: bool = False) -> Union[float, np.ndarray]:
def ht_trendmode(candles: np.ndarray, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
HT_TRENDMODE - Hilbert Transform - Trend vs Cycle Mode
:param candles: np.ndarray
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: int | np.ndarray
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
res = talib.HT_TRENDMODE(source)

View File

@@ -1,209 +0,0 @@
import numpy as np
try:
from numba import njit
except ImportError:
njit = lambda a : a
from jesse.helpers import get_candle_source, slice_candles
from scipy import signal
def hurst_exponent(candles: np.ndarray, min_chunksize: int = 8, max_chunksize: int = 200, num_chunksize:int=5, method:int=1, source_type: str = "close") -> float:
"""
Hurst Exponent
:param candles: np.ndarray
:param min_chunksize: int - default: 8
:param max_chunksize: int - default: 200
:param num_chunksize: int - default: 5
:param method: int - default: 1 - 0: RS | 1: DMA | 2: DSOD
:param source_type: str - default: "close"
:return: float
"""
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, False)
source = get_candle_source(candles, source_type=source_type)
if method == 0:
h = hurst_rs(np.diff(source), min_chunksize, max_chunksize, num_chunksize)
elif method == 1:
h = hurst_dma(source, min_chunksize, max_chunksize, num_chunksize)
elif method == 2:
h = hurst_dsod(source)
else:
raise NotImplementedError('The method choose is not implemented.')
return None if np.isnan(h) else h
@njit
def hurst_rs(x, min_chunksize, max_chunksize, num_chunksize):
"""Estimate the Hurst exponent using R/S method.
Estimates the Hurst (H) exponent using the R/S method from the time series.
The R/S method consists of dividing the series into pieces of equal size
`series_len` and calculating the rescaled range. This repeats the process
for several `series_len` values and adjusts data regression to obtain the H.
`series_len` will take values between `min_chunksize` and `max_chunksize`,
the step size from `min_chunksize` to `max_chunksize` can be controlled
through the parameter `step_chunksize`.
Parameters
----------
x : 1D-array
A time series to calculate hurst exponent, must have more elements
than `min_chunksize` and `max_chunksize`.
min_chunksize : int
This parameter allow you control the minimum window size.
max_chunksize : int
This parameter allow you control the maximum window size.
num_chunksize : int
This parameter allow you control the size of the step from minimum to
maximum window size. Bigger step means fewer calculations.
out : 1-element-array, optional
one element array to store the output.
Returns
-------
H : float
A estimation of Hurst exponent.
References
----------
Hurst, H. E. (1951). Long term storage capacity of reservoirs. ASCE
Transactions, 116(776), 770-808.
Alessio, E., Carbone, A., Castelli, G. et al. Eur. Phys. J. B (2002) 27:
197. http://dx.doi.org/10.1140/epjb/e20020150
"""
N = len(x)
max_chunksize += 1
rs_tmp = np.empty(N, dtype=np.float64)
chunk_size_list = np.linspace(min_chunksize, max_chunksize, num_chunksize) \
.astype(np.int64)
rs_values_list = np.empty(num_chunksize, dtype=np.float64)
# 1. The series is divided into chunks of chunk_size_list size
for i in range(num_chunksize):
chunk_size = chunk_size_list[i]
# 2. it iterates on the indices of the first observation of each chunk
number_of_chunks = int(len(x) / chunk_size)
for idx in range(number_of_chunks):
# next means no overlapping
# convert index to index selection of each chunk
ini = idx * chunk_size
end = ini + chunk_size
chunk = x[ini:end]
# 2.1 Calculate the RS (chunk_size)
z = np.cumsum(chunk - np.mean(chunk))
rs_tmp[idx] = np.divide(
np.max(z) - np.min(z), # range
np.nanstd(chunk) # standar deviation
)
# 3. Average of RS(chunk_size)
rs_values_list[i] = np.nanmean(rs_tmp[:idx + 1])
# 4. calculate the Hurst exponent.
H, c = np.linalg.lstsq(
a=np.vstack((np.log(chunk_size_list), np.ones(num_chunksize))).T,
b=np.log(rs_values_list)
)[0]
return H
def hurst_dma(prices, min_chunksize=8, max_chunksize=200, num_chunksize=5):
"""Estimate the Hurst exponent using R/S method.
Estimates the Hurst (H) exponent using the DMA method from the time series.
The DMA method consists on calculate the moving average of size `series_len`
and subtract it to the original series and calculating the standard
deviation of that result. This repeats the process for several `series_len`
values and adjusts data regression to obtain the H. `series_len` will take
values between `min_chunksize` and `max_chunksize`, the step size from
`min_chunksize` to `max_chunksize` can be controlled through the parameter
`step_chunksize`.
Parameters
----------
prices
min_chunksize
max_chunksize
num_chunksize
Returns
-------
hurst_exponent : float
Estimation of hurst exponent.
References
----------
Alessio, E., Carbone, A., Castelli, G. et al. Eur. Phys. J. B (2002) 27:
197. https://dx.doi.org/10.1140/epjb/e20020150
"""
max_chunksize += 1
N = len(prices)
n_list = np.arange(min_chunksize, max_chunksize, num_chunksize, dtype=np.int64)
dma_list = np.empty(len(n_list))
factor = 1 / (N - max_chunksize)
# sweeping n_list
for i, n in enumerate(n_list):
b = np.divide([n - 1] + (n - 1) * [-1], n) # do the same as: y - y_ma_n
noise = np.power(signal.lfilter(b, 1, prices)[max_chunksize:], 2)
dma_list[i] = np.sqrt(factor * np.sum(noise))
H, const = np.linalg.lstsq(
a=np.vstack([np.log10(n_list), np.ones(len(n_list))]).T,
b=np.log10(dma_list), rcond=None
)[0]
return H
def hurst_dsod(x):
"""Estimate Hurst exponent on data timeseries.
The estimation is based on the discrete second order derivative. Consists on
get two different noise of the original series and calculate the standard
deviation and calculate the slope of two point with that values.
source: https://gist.github.com/wmvanvliet/d883c3fe1402c7ced6fc
Parameters
----------
x : numpy array
time series to estimate the Hurst exponent for.
Returns
-------
h : float
The estimation of the Hurst exponent for the given time series.
References
----------
Istas, J.; G. Lang (1994), “Quadratic variations and estimation of the local
Hölder index of data Gaussian process,” Ann. Inst. Poincaré, 33, pp. 407436.
Notes
-----
This hurst_ets is data literal traduction of wfbmesti.m of waveleet toolbox
from matlab.
"""
y = np.cumsum(np.diff(x, axis=0), axis=0)
# second order derivative
b1 = [1, -2, 1]
y1 = signal.lfilter(b1, 1, y, axis=0)
y1 = y1[len(b1) - 1:] # first values contain filter artifacts
# wider second order derivative
b2 = [1, 0, -2, 0, 1]
y2 = signal.lfilter(b2, 1, y, axis=0)
y2 = y2[len(b2) - 1:] # first values contain filter artifacts
s1 = np.mean(y1 ** 2, axis=0)
s2 = np.mean(y2 ** 2, axis=0)
return 0.5 * np.log2(s2 / s1)

View File

@@ -1,54 +0,0 @@
from typing import Union
import numpy as np
try:
from numba import njit
except ImportError:
njit = lambda a : a
from jesse.helpers import get_candle_source, slice_candles, same_length
def hwma(candles: np.ndarray, na: float = 0.2, nb: float = 0.1, nc: float = 0.1, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
"""
Holt-Winter Moving Average
:param candles: np.ndarray
:param na: float - default: 0.2
:param nb: float - default: 0.1
:param nc: float - default: 0.1
:param source_type: str - default: "close"
:param sequential: bool - default: False
:return: float | np.ndarray
"""
if not ((0 < na < 1) or (0 < nb < 1) or (0 < nc < 1)):
raise ValueError("Bad parameters. They have to be: 0 < na nb nc < 1")
# Accept normal array too.
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
source_without_nan = source[~np.isnan(source)]
res = hwma_fast(source_without_nan, na, nb, nc)
res = same_length(candles, res)
return res if sequential else res[-1]
@njit
def hwma_fast(source, na, nb, nc):
last_a = last_v = 0
last_f = source[0]
newseries = np.copy(source)
for i in range(source.size):
F = (1.0 - na) * (last_f + last_v + 0.5 * last_a) + na * source[i]
V = (1.0 - nb) * (last_v + last_a) + nb * (F - last_f)
A = (1.0 - nc) * last_a + nc * (V - last_v)
newseries[i] = F + V + 0.5 * A
last_a, last_f, last_v = A, F, V # update values
return newseries

View File

@@ -5,23 +5,23 @@ import numpy as np
IchimokuCloud = namedtuple('IchimokuCloud', ['conversion_line', 'base_line', 'span_a', 'span_b'])
def ichimoku_cloud(candles: np.ndarray, conversion_line_period: int = 9, base_line_period: int = 26,
lagging_line_period: int = 52, displacement: int = 26) -> IchimokuCloud:
def ichimoku_cloud(candles: np.ndarray, conversion_line_period=9, base_line_period=26, lagging_line_period=52,
displacement=26) -> IchimokuCloud:
"""
Ichimoku Cloud
:param candles: np.ndarray
:param conversion_line_period: int - default: 9
:param base_line_period: int - default: 26
:param lagging_line_period: int - default: 52
:param displacement: - default: 26
:param conversion_line_period: int - default=9
:param base_line_period: int - default=26
:param lagging_line_period: int - default=52
:param displacement: - default=26
:return: IchimokuCloud(conversion_line, base_line, span_a, span_b)
"""
if candles.shape[0] < 80:
if len(candles) < 80:
return IchimokuCloud(np.nan, np.nan, np.nan, np.nan)
if candles.shape[0] > 80:
if len(candles) > 80:
candles = candles[-80:]
# earlier

View File

@@ -4,40 +4,48 @@ import numpy as np
import talib
from jesse.helpers import np_shift
from jesse.helpers import slice_candles
IchimokuCloud = namedtuple('IchimokuCloud',
['conversion_line', 'base_line', 'span_a', 'span_b', 'lagging_line', 'future_span_a',
'future_span_b'])
def ichimoku_cloud_seq(candles: np.ndarray, conversion_line_period: int = 9, base_line_period: int = 26,
lagging_line_period: int = 52, displacement: int = 26,
sequential: bool = False) -> IchimokuCloud:
def ichimoku_cloud_seq(candles: np.ndarray, conversion_line_period=9, base_line_period=26, lagging_line_period=52,
displacement=26, sequential=False) -> IchimokuCloud:
"""
Ichimoku Cloud
:param candles: np.ndarray
:param conversion_line_period: int - default: 9
:param base_line_period: int - default: 26
:param lagging_line_period: int - default: 52
:param displacement: - default: 26
:param sequential: bool - default: False
:param conversion_line_period: int - default=9
:param base_line_period: int - default=26
:param lagging_line_period: int - default=52
:param displacement: - default=26
:param sequential: bool - default=False
:return: IchimokuCloud
"""
if candles.shape[0] < lagging_line_period + displacement:
if len(candles) < lagging_line_period + displacement:
raise ValueError("Too few candles available for lagging_line_period + displacement.")
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
conversion_line = _line_helper(candles, conversion_line_period)
base_line = _line_helper(candles, base_line_period)
span_b_pre = _line_helper(candles, lagging_line_period)
small_ph = talib.MAX(candles[:, 3], conversion_line_period)
small_pl = talib.MIN(candles[:, 4], conversion_line_period)
conversion_line = (small_ph + small_pl) / 2
mid_ph = talib.MAX(candles[:, 3], base_line_period)
mid_pl = talib.MIN(candles[:, 4], base_line_period)
base_line = (mid_ph + mid_pl) / 2
long_ph = talib.MAX(candles[:, 3], lagging_line_period)
long_pl = talib.MIN(candles[:, 4], lagging_line_period)
span_b_pre = (long_ph + long_pl) / 2
span_b = np_shift(span_b_pre, displacement, fill_value=np.nan)
span_a_pre = (conversion_line + base_line) / 2
span_a = np_shift(span_a_pre, displacement, fill_value=np.nan)
lagging_line = np_shift(candles[:, 2], displacement - 1, fill_value=np.nan)
if sequential:
@@ -45,8 +53,3 @@ def ichimoku_cloud_seq(candles: np.ndarray, conversion_line_period: int = 9, bas
else:
return IchimokuCloud(conversion_line[-1], base_line[-1], span_a[-1], span_b[-1], lagging_line[-1],
span_a_pre[-1], span_b_pre[-1])
def _line_helper(candles, period):
small_ph = talib.MAX(candles[:, 3], period)
small_pl = talib.MIN(candles[:, 4], period)
return (small_ph + small_pl) / 2

View File

@@ -1,36 +0,0 @@
from typing import Union
import talib
import numpy as np
from jesse.helpers import get_candle_source, slice_candles, same_length
def ift_rsi(candles: np.ndarray, rsi_period: int = 5, wma_period: int =9, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
"""
Modified Inverse Fisher Transform applied on RSI
:param candles: np.ndarray
:param rsi_period: int - default: 5
:param wma_period: int - default: 9
:param source_type: str - default: "close"
:param sequential: bool - default: False
:return: float | np.ndarray
"""
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
v1 = 0.1 * (talib.RSI(source, rsi_period) - 50)
v2 = talib.WMA(v1, wma_period)
res = (((2*v2) ** 2 - 1) / ((2*v2) ** 2 + 1))
return same_length(candles, res) if sequential else res[-1]

View File

@@ -1,53 +1,45 @@
from collections import namedtuple
import numpy as np
try:
from numba import njit
except ImportError:
njit = lambda a : a
from jesse.helpers import get_candle_source, slice_candles
from jesse.helpers import get_candle_source
ITREND = namedtuple('ITREND', ['signal', 'it', 'trigger'])
def itrend(candles: np.ndarray, alpha: float = 0.07, source_type: str = "hl2", sequential: bool = False) -> ITREND:
def itrend(candles: np.ndarray, alpha=0.07, source_type="hl2", sequential=False) -> ITREND:
"""
Instantaneous Trendline
:param candles: np.ndarray
:param alpha: float - default: 0.07
:param source_type: str - default: "hl2"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: ITREND(signal, it, trigger)
"""
candles = slice_candles(candles, sequential)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
signal, it, trigger = itrend_fast(source, alpha)
coeff = np.array(
[(alpha - alpha ** 2 / 4), alpha ** 2 / 2, - (alpha - alpha ** 2 * 3 / 4), 2 * (1 - alpha), - (1 - alpha) ** 2])
if sequential:
return ITREND(signal, it, trigger)
else:
return ITREND(signal[-1], it[-1], trigger[-1])
@njit
def itrend_fast(source, alpha):
it = np.copy(source)
for i in range(2, 7):
it[i] = (source[i] + 2 * source[i - 1] + source[i - 2]) / 4
for i in range(7, source.shape[0]):
it[i] = (alpha - alpha ** 2 / 4) * source[i] \
+ alpha ** 2 / 2 * source[i - 1] \
- (alpha - alpha ** 2 * 3 / 4) * source[i - 2] \
+ 2 * (1 - alpha) * it[i - 1] - (1 - alpha) ** 2 * it[i - 2]
val = np.array([source[i], source[i - 1], source[i - 2], it[i - 1], it[i - 2]])
it[i] = np.matmul(coeff, val)
# compute lead 2 trigger & signal
lag2 = np.roll(it, 20)
lag2[:20] = it[:20]
trigger = 2 * it - lag2
signal = (trigger > it) * 1 - (trigger < it) * 1
return signal, it, trigger
if sequential:
return ITREND(signal, it, trigger)
else:
return ITREND(signal[-1], it[-1], trigger[-1])

View File

@@ -1,48 +0,0 @@
from typing import Union
import numpy as np
try:
from numba import njit
except ImportError:
njit = lambda a : a
from jesse.helpers import get_candle_source, slice_candles
def jma(candles: np.ndarray, period:int=7, phase:float=50, power:int=2, source_type:str='close', sequential:bool=False) -> Union[
float, np.ndarray]:
"""
Jurik Moving Average
Port of: https://tradingview.com/script/nZuBWW9j-Jurik-Moving-Average/
"""
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
phaseRatio = 0.5 if phase < -100 else (2.5 if phase > 100 else phase / 100 + 1.5)
beta = 0.45 * (period - 1) / (0.45 * (period - 1) + 2)
alpha = pow(beta, power)
res = jma_helper(source, phaseRatio, beta, alpha)
return res if sequential else res[-1]
@njit
def jma_helper(src, phaseRatio, beta, alpha):
jma_val = np.copy(src)
e0 = np.full_like(src, 0)
e1 = np.full_like(src, 0)
e2 = np.full_like(src, 0)
for i in range(1, src.shape[0]):
e0[i] = (1 - alpha) * src[i] + alpha * e0[i-1]
e1[i] = (src[i] - e0[i]) * (1 - beta) + beta * e1[i-1]
e2[i] = (e0[i] + phaseRatio * e1[i] - jma_val[i - 1]) * pow(1 - alpha, 2) + pow(alpha, 2) * e2[i - 1]
jma_val[i] = e2[i] + jma_val[i - 1]
return jma_val

View File

@@ -1,28 +0,0 @@
from typing import Union
import numpy as np
from jesse.helpers import get_candle_source, slice_candles, np_shift
def jsa(candles: np.ndarray, period: int = 30, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
"""
Jsa Moving Average
:param candles: np.ndarray
:param period: int - default: 30
:param source_type: str - default: "close"
:param sequential: bool - default: False
:return: float | np.ndarray
"""
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
res = (source + np_shift(source, period, np.nan)) / 2
return res if sequential else res[-1]

View File

@@ -4,27 +4,23 @@ import numpy as np
import talib
from jesse.helpers import get_candle_source
from jesse.helpers import slice_candles
def kama(candles: np.ndarray, period: int = 30, source_type: str = "close", sequential: bool = False) -> Union[
float, np.ndarray]:
def kama(candles: np.ndarray, period=30, source_type="close", sequential=False) -> Union[float, np.ndarray]:
"""
KAMA - Kaufman Adaptive Moving Average
:param candles: np.ndarray
:param period: int - default: 30
:param source_type: str - default: "close"
:param sequential: bool - default: False
:param sequential: bool - default=False
:return: float | np.ndarray
"""
if len(candles.shape) == 1:
source = candles
else:
candles = slice_candles(candles, sequential)
source = get_candle_source(candles, source_type=source_type)
if not sequential and len(candles) > 240:
candles = candles[-240:]
source = get_candle_source(candles, source_type=source_type)
res = talib.KAMA(source, timeperiod=period)
return res if sequential else res[-1]

Some files were not shown because too many files have changed in this diff Show More