Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ef0812480 | ||
|
|
05bb4d9359 | ||
|
|
f4961413bc | ||
|
|
37d3e7ec30 | ||
|
|
5466470a1e | ||
|
|
7e3dc05259 | ||
|
|
50806b9eb8 | ||
|
|
fe82bc0fe6 | ||
|
|
26381a3329 | ||
|
|
10da7af267 |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -8,7 +8,7 @@ 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`.
|
||||
IMPORTANT: Please open an issue ONLY if you find something wrong with the source code. For questions and feedback use the Forum (https://forum.jesse.trade/) or Discord (https://jesse.trade/discord). Also make sure to give the documentation (https://docs.jesse.trade/) 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**
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -8,7 +8,7 @@ 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.
|
||||
Make sure to check the Projects tab if 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.
|
||||
-->
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
|
||||
17
.github/stale.yml
vendored
17
.github/stale.yml
vendored
@@ -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
|
||||
98
.github/workflows/codeql-analysis.yml
vendored
98
.github/workflows/codeql-analysis.yml
vendored
@@ -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
|
||||
97
.github/workflows/python-package.yml
vendored
97
.github/workflows/python-package.yml
vendored
@@ -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
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -148,4 +148,3 @@ cython_debug/
|
||||
/.vagrant
|
||||
testing-*.py
|
||||
/storage/full-reports/*.html
|
||||
/storage/logs/*
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
FROM python:3.9-slim
|
||||
FROM python:3.9.4-slim
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get -y install git build-essential libssl-dev \
|
||||
&& apt-get -y install build-essential libssl-dev \
|
||||
&& apt-get clean \
|
||||
&& pip install --upgrade pip
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.9-slim
|
||||
FROM python:3.9.4-slim
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
RUN apt-get update \
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
include jesse/static/*
|
||||
include jesse/static/**/*
|
||||
@@ -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
|
||||
|
||||
[](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.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 122 KiB |
@@ -1,181 +1,228 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
# Hide the "FutureWarning: pandas.util.testing is deprecated." caused by empyrical
|
||||
import warnings
|
||||
from typing import Optional
|
||||
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
|
||||
|
||||
warnings.simplefilter(action='ignore', category=FutureWarning)
|
||||
|
||||
# Python version validation.
|
||||
if jh.python_version() < 3.7:
|
||||
print(
|
||||
jh.color(
|
||||
f'Jesse requires Python version above 3.7. Yours is {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:
|
||||
"""
|
||||
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() -> None:
|
||||
"""
|
||||
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() -> None:
|
||||
"""
|
||||
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() -> None:
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
import logging
|
||||
from jesse.services import logger as jesse_logger
|
||||
import click
|
||||
from jesse import exceptions
|
||||
|
||||
background_tasks.add_task(jh.terminate_app)
|
||||
return JSONResponse({'message': 'Shutting down...'})
|
||||
log_format = "%(message)s"
|
||||
os.makedirs('storage/logs', exist_ok=True)
|
||||
|
||||
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)
|
||||
|
||||
@fastapi_app.post("/auth")
|
||||
def auth(json_request: LoginRequestJson):
|
||||
return authenticator.password_to_token(json_request.password)
|
||||
|
||||
|
||||
@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()
|
||||
|
||||
from jesse.services import strategy_maker
|
||||
return strategy_maker.generate(json_request.name)
|
||||
|
||||
|
||||
@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):
|
||||
# main thread
|
||||
def handle_exception(exc_type, exc_value, exc_traceback) -> None:
|
||||
if issubclass(exc_type, KeyboardInterrupt):
|
||||
sys.excepthook(exc_type, exc_value, exc_traceback)
|
||||
return
|
||||
|
||||
await websocket.accept()
|
||||
# handle Breaking exceptions
|
||||
if exc_type in [
|
||||
exceptions.InvalidConfig, exceptions.RouteNotFound, exceptions.InvalidRoutes,
|
||||
exceptions.CandleNotFoundInDatabase
|
||||
]:
|
||||
click.clear()
|
||||
print(f"{'=' * 30} EXCEPTION TRACEBACK:")
|
||||
traceback.print_tb(exc_traceback, file=sys.stdout)
|
||||
print("=" * 73)
|
||||
print(
|
||||
'\n',
|
||||
jh.color('Uncaught Exception:', 'red'),
|
||||
jh.color(f'{exc_type.__name__}: {exc_value}', 'yellow')
|
||||
)
|
||||
return
|
||||
|
||||
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(
|
||||
f'{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(f"{'=' * 30} EXCEPTION TRACEBACK:")
|
||||
traceback.print_tb(exc_traceback, file=sys.stdout)
|
||||
print("=" * 73)
|
||||
print(
|
||||
'\n',
|
||||
jh.color('Uncaught Exception:', 'red'),
|
||||
jh.color(f'{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:\nstorage/logs/paper-trade.txt',
|
||||
'red'
|
||||
)
|
||||
)
|
||||
elif jh.is_livetrading():
|
||||
print(
|
||||
jh.color(
|
||||
'An uncaught exception was raised. Check the log file at:\nstorage/logs/live-trade.txt',
|
||||
'red'
|
||||
)
|
||||
)
|
||||
elif jh.is_collecting_data():
|
||||
print(
|
||||
jh.color(
|
||||
'An uncaught exception was raised. Check the log file at:\nstorage/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) -> None:
|
||||
if args.exc_type == SystemExit:
|
||||
return
|
||||
|
||||
# handle Breaking exceptions
|
||||
if args.exc_type in [
|
||||
exceptions.InvalidConfig, exceptions.RouteNotFound, exceptions.InvalidRoutes,
|
||||
exceptions.CandleNotFoundInDatabase
|
||||
]:
|
||||
click.clear()
|
||||
print(f"{'=' * 30} EXCEPTION TRACEBACK:")
|
||||
traceback.print_tb(args.exc_traceback, file=sys.stdout)
|
||||
print("=" * 73)
|
||||
print(
|
||||
'\n',
|
||||
jh.color('Uncaught Exception:', 'red'),
|
||||
jh.color(f'{args.exc_type.__name__}: {args.exc_value}', 'yellow')
|
||||
)
|
||||
return
|
||||
|
||||
# send notifications if it's a live session
|
||||
if jh.is_live():
|
||||
jesse_logger.error(
|
||||
f'{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(f"{'=' * 30} EXCEPTION TRACEBACK:")
|
||||
traceback.print_tb(args.exc_traceback, file=sys.stdout)
|
||||
print("=" * 73)
|
||||
print(
|
||||
'\n',
|
||||
jh.color('Uncaught Exception:', 'red'),
|
||||
jh.color(f'{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:\nstorage/logs/paper-trade.txt',
|
||||
'red'
|
||||
)
|
||||
)
|
||||
elif jh.is_livetrading():
|
||||
print(
|
||||
jh.color(
|
||||
'An uncaught exception was raised. Check the log file at:\nstorage/logs/live-trade.txt',
|
||||
'red'
|
||||
)
|
||||
)
|
||||
elif jh.is_collecting_data():
|
||||
print(
|
||||
jh.color(
|
||||
'An uncaught exception was raised. Check the log file at:\nstorage/logs/collect.txt',
|
||||
'red'
|
||||
)
|
||||
)
|
||||
|
||||
threading.excepthook = handle_thread_exception
|
||||
|
||||
|
||||
# create a Click group
|
||||
@@ -186,310 +233,275 @@ def cli() -> None:
|
||||
|
||||
|
||||
@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)
|
||||
@click.option('--skip-confirmation', is_flag=True,
|
||||
help="Will prevent confirmation for skipping duplicates")
|
||||
def import_candles(exchange: str, symbol: str, start_date: str, skip_confirmation: bool) -> None:
|
||||
"""
|
||||
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, skip_confirmation)
|
||||
|
||||
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.")
|
||||
@click.option('--full-reports/--no-full-reports', default=False,
|
||||
help="Generates QuantStats' HTML output with metrics reports like Sharpe ratio, Win rate, Volatility, etc., and batch plotting for visualizing performance, drawdowns, rolling statistics, monthly returns, etc.")
|
||||
def backtest(start_date: str, finish_date: str, debug: bool, csv: bool, json: bool, fee: bool, chart: bool,
|
||||
tradingview: bool, full_reports: bool) -> None:
|
||||
"""
|
||||
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, full_reports=full_reports)
|
||||
|
||||
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()
|
||||
@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.'
|
||||
)
|
||||
@click.option('--csv/--no-csv', default=False, help='Outputs a CSV file of all DNAs on completion.')
|
||||
@click.option('--json/--no-json', default=False, help='Outputs a JSON file of all DNAs on completion.')
|
||||
def optimize(start_date: str, finish_date: str, optimal_total: int, cpu: int, debug: bool, csv: bool,
|
||||
json: bool) -> None:
|
||||
"""
|
||||
tunes the hyper-parameters of your strategy
|
||||
"""
|
||||
validate_cwd()
|
||||
from jesse.config import config
|
||||
config['app']['trading_mode'] = 'optimize'
|
||||
|
||||
from jesse.services.multiprocessing import process_manager
|
||||
register_custom_exception_handler()
|
||||
|
||||
# debug flag
|
||||
config['app']['debug_mode'] = debug
|
||||
|
||||
from jesse.modes.optimize_mode import optimize_mode
|
||||
|
||||
optimize_mode(start_date, finish_date, optimal_total, cpu, csv, json)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('name', required=True, type=str)
|
||||
def make_strategy(name: str) -> None:
|
||||
"""
|
||||
generates a new strategy folder from jesse/strategies/ExampleStrategy
|
||||
"""
|
||||
validate_cwd()
|
||||
from jesse.config import config
|
||||
|
||||
config['app']['trading_mode'] = 'make-strategy'
|
||||
|
||||
register_custom_exception_handler()
|
||||
|
||||
from jesse.services import strategy_maker
|
||||
|
||||
strategy_maker.generate(name)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('name', required=True, type=str)
|
||||
def make_project(name: str) -> None:
|
||||
"""
|
||||
generates a new strategy folder from jesse/strategies/ExampleStrategy
|
||||
"""
|
||||
from jesse.config import config
|
||||
|
||||
config['app']['trading_mode'] = 'make-project'
|
||||
|
||||
register_custom_exception_handler()
|
||||
|
||||
from jesse.services import project_maker
|
||||
|
||||
project_maker.generate(name)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--dna/--no-dna', default=False,
|
||||
help='Translates DNA into parameters. Used in optimize mode only')
|
||||
def routes(dna: bool) -> None:
|
||||
"""
|
||||
lists all routes
|
||||
"""
|
||||
validate_cwd()
|
||||
from jesse.config import config
|
||||
|
||||
config['app']['trading_mode'] = 'routes'
|
||||
|
||||
register_custom_exception_handler()
|
||||
|
||||
from jesse.modes import routes_mode
|
||||
|
||||
routes_mode.run(dna)
|
||||
|
||||
|
||||
live_package_exists = True
|
||||
try:
|
||||
import jesse_live
|
||||
except ModuleNotFoundError:
|
||||
live_package_exists = False
|
||||
if live_package_exists:
|
||||
# @cli.command()
|
||||
# def collect() -> None:
|
||||
# """
|
||||
# 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 jesse_live.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)
|
||||
def live(testdrive: bool, debug: bool, dev: bool) -> None:
|
||||
"""
|
||||
trades in real-time on exchange with REAL money
|
||||
"""
|
||||
validate_cwd()
|
||||
|
||||
from jesse.modes.optimize_mode import run as run_optimization
|
||||
# set trading mode
|
||||
from jesse.config import config
|
||||
config['app']['trading_mode'] = 'livetrade'
|
||||
config['app']['is_test_driving'] = testdrive
|
||||
|
||||
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
|
||||
)
|
||||
register_custom_exception_handler()
|
||||
|
||||
# optimize_mode(start_date, finish_date, optimal_total, cpu, csv, json)
|
||||
# debug flag
|
||||
config['app']['debug_mode'] = debug
|
||||
|
||||
return JSONResponse({'message': 'Started optimization...'}, status_code=202)
|
||||
from jesse_live import init
|
||||
from jesse.services.selectors import get_exchange
|
||||
live_config = locate('live-config.config')
|
||||
|
||||
# validate that the "live-config.py" file exists
|
||||
if live_config is None:
|
||||
jh.error('You\'re either missing the live-config.py file or haven\'t logged in. Run "jesse login" to fix it.', True)
|
||||
jh.terminate_app()
|
||||
|
||||
@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(...)):
|
||||
"""
|
||||
Log files require session_id because there is one log per each session. Except for the optimize mode
|
||||
"""
|
||||
if not authenticator.is_valid_token(token):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
from jesse.modes import data_provider
|
||||
|
||||
return data_provider.download_file(mode, file_type, session_id)
|
||||
|
||||
|
||||
@fastapi_app.get("/download/optimize/log")
|
||||
def download_optimization_log(token: str = Query(...)):
|
||||
"""
|
||||
Optimization logs don't have have session ID
|
||||
"""
|
||||
if not authenticator.is_valid_token(token):
|
||||
return authenticator.unauthorized_response()
|
||||
|
||||
from jesse.modes import data_provider
|
||||
|
||||
return data_provider.download_file('optimize', 'log')
|
||||
|
||||
|
||||
@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()
|
||||
|
||||
from jesse.services.multiprocessing import process_manager
|
||||
|
||||
process_manager.cancel_process('backtest-' + request_json.id)
|
||||
|
||||
return JSONResponse({'message': f'Backtest process with ID of {request_json.id} was requested for termination'}, status_code=202)
|
||||
|
||||
|
||||
@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
|
||||
|
||||
validate_cwd()
|
||||
# inject live config
|
||||
init(config, live_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 jesse_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)
|
||||
def paper(debug: bool, dev: bool) -> None:
|
||||
"""
|
||||
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 jesse_live import init
|
||||
from jesse.services.selectors import get_exchange
|
||||
live_config = locate('live-config.config')
|
||||
|
||||
@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()
|
||||
# validate that the "live-config.py" file exists
|
||||
if live_config is None:
|
||||
jh.error('You\'re either missing the live-config.py file or haven\'t logged in. Run "jesse login" to fix it.', True)
|
||||
jh.terminate_app()
|
||||
|
||||
from jesse_live.services.data_provider import get_logs as gl
|
||||
# inject live config
|
||||
init(config, live_config)
|
||||
|
||||
arr = gl(json_request.session_id, json_request.type)
|
||||
# execute live session
|
||||
from jesse_live.live_mode import run
|
||||
run(dev)
|
||||
|
||||
return JSONResponse({
|
||||
'id': json_request.id,
|
||||
'data': arr
|
||||
}, status_code=200)
|
||||
@cli.command()
|
||||
@click.option('--email', prompt='Email')
|
||||
@click.option('--password', prompt='Password', hide_input=True)
|
||||
def login(email, password) -> None:
|
||||
"""
|
||||
(Initially) Logins to the website.
|
||||
"""
|
||||
validate_cwd()
|
||||
|
||||
# set trading mode
|
||||
from jesse.config import config
|
||||
config['app']['trading_mode'] = 'login'
|
||||
|
||||
@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()
|
||||
register_custom_exception_handler()
|
||||
|
||||
from jesse_live.services.data_provider import get_orders as go
|
||||
from jesse_live.auth import login
|
||||
|
||||
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")
|
||||
login(email, password)
|
||||
|
||||
151
jesse/config.py
151
jesse/config.py
@@ -1,9 +1,14 @@
|
||||
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'
|
||||
},
|
||||
@@ -37,64 +42,6 @@ config = {
|
||||
],
|
||||
},
|
||||
|
||||
'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,
|
||||
@@ -196,6 +143,20 @@ config = {
|
||||
},
|
||||
},
|
||||
|
||||
# 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,
|
||||
},
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# Optimize mode
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
@@ -203,7 +164,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',
|
||||
},
|
||||
|
||||
@@ -255,67 +216,27 @@ config = {
|
||||
},
|
||||
}
|
||||
|
||||
backup_config = config.copy()
|
||||
|
||||
def set_config(conf: dict) -> None:
|
||||
|
||||
def set_config(c) -> None:
|
||||
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',
|
||||
config['env'] = c
|
||||
# add sandbox because it isn't in the local config file
|
||||
config['env']['exchanges']['Sandbox'] = {
|
||||
'type': 'spot',
|
||||
# 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']),
|
||||
'settlement_currency': 'USDT',
|
||||
'fee': 0,
|
||||
'futures_leverage_mode': 'cross',
|
||||
'futures_leverage': 1,
|
||||
'assets': [
|
||||
{'asset': 'USDT', 'balance': float(e['balance'])},
|
||||
{'asset': 'USDT', 'balance': 10_000},
|
||||
{'asset': 'BTC', 'balance': 0},
|
||||
],
|
||||
}
|
||||
|
||||
# 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},
|
||||
# ],
|
||||
# }
|
||||
|
||||
|
||||
def reset_config() -> None:
|
||||
global config
|
||||
config = backup_config.copy()
|
||||
|
||||
|
||||
backup_config = config.copy()
|
||||
|
||||
@@ -12,9 +12,7 @@ class order_statuses:
|
||||
ACTIVE = 'ACTIVE'
|
||||
CANCELED = 'CANCELED'
|
||||
EXECUTED = 'EXECUTED'
|
||||
PARTIALLY_EXECUTED = 'PARTIALLY EXECUTED'
|
||||
QUEUED = 'QUEUED'
|
||||
LIQUIDATED = 'LIQUIDATED'
|
||||
|
||||
|
||||
class timeframes:
|
||||
@@ -32,6 +30,8 @@ class timeframes:
|
||||
HOUR_8 = '8h'
|
||||
HOUR_12 = '12h'
|
||||
DAY_1 = '1D'
|
||||
DAY_3 = '3D'
|
||||
WEEK_1 = '1W'
|
||||
|
||||
|
||||
class colors:
|
||||
|
||||
@@ -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,10 +54,6 @@ class ExchangeNotResponding(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ExchangeRejectedOrder(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidShape(Exception):
|
||||
pass
|
||||
|
||||
@@ -68,7 +72,3 @@ class NegativeBalance(Exception):
|
||||
|
||||
class InsufficientMargin(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Termination(Exception):
|
||||
pass
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Union
|
||||
from jesse.models import Order
|
||||
|
||||
|
||||
class Exchange(ABC):
|
||||
"""
|
||||
@@ -8,29 +7,29 @@ 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):
|
||||
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):
|
||||
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):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def cancel_all_orders(self, symbol: str) -> None:
|
||||
def cancel_all_orders(self, symbol):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def cancel_order(self, symbol: str, order_id: str) -> None:
|
||||
def cancel_order(self, symbol, order_id):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_exec_inst(self, flags: list) -> Union[str, None]:
|
||||
def get_exec_inst(self, flags):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _fetch_precisions(self) -> None:
|
||||
def _get_precisions(self):
|
||||
pass
|
||||
|
||||
@@ -3,14 +3,14 @@ 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):
|
||||
order = Order({
|
||||
'id': jh.generate_unique_id(),
|
||||
'symbol': symbol,
|
||||
@@ -29,7 +29,7 @@ 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):
|
||||
order = Order({
|
||||
'id': jh.generate_unique_id(),
|
||||
'symbol': symbol,
|
||||
@@ -46,7 +46,7 @@ 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):
|
||||
order = Order({
|
||||
'id': jh.generate_unique_id(),
|
||||
'symbol': symbol,
|
||||
@@ -63,7 +63,7 @@ class Sandbox(Exchange):
|
||||
|
||||
return order
|
||||
|
||||
def cancel_all_orders(self, symbol: str) -> None:
|
||||
def cancel_all_orders(self, symbol):
|
||||
orders = filter(lambda o: o.is_new,
|
||||
store.orders.get_orders(self.name, symbol))
|
||||
|
||||
@@ -73,13 +73,13 @@ class Sandbox(Exchange):
|
||||
if not jh.is_unit_testing():
|
||||
store.orders.storage[f'{self.name}-{symbol}'].clear()
|
||||
|
||||
def cancel_order(self, symbol: str, order_id: str) -> None:
|
||||
def cancel_order(self, symbol, 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):
|
||||
if flags:
|
||||
return flags[0]
|
||||
return None
|
||||
|
||||
def _fetch_precisions(self) -> None:
|
||||
def _get_precisions(self):
|
||||
pass
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,10 +12,7 @@ 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:
|
||||
"""
|
||||
Generates a range of candles with random values.
|
||||
"""
|
||||
def fake_range_candle(count) -> np.ndarray:
|
||||
fake_candle(reset=True)
|
||||
arr = np.zeros((count, 6))
|
||||
for i in range(count):
|
||||
@@ -25,11 +20,7 @@ def range_candles(count: int) -> np.ndarray:
|
||||
return arr
|
||||
|
||||
|
||||
def candles_from_close_prices(prices: Union[list, range]) -> 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"
|
||||
"""
|
||||
def fake_range_candle_from_range_prices(prices) -> np.ndarray:
|
||||
fake_candle(reset=True)
|
||||
global first_timestamp
|
||||
arr = []
|
||||
@@ -54,7 +45,7 @@ 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):
|
||||
global first_timestamp
|
||||
global open_price
|
||||
global close_price
|
||||
@@ -64,7 +55,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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
272
jesse/helpers.py
272
jesse/helpers.py
@@ -6,7 +6,7 @@ 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
|
||||
@@ -77,7 +77,7 @@ 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')
|
||||
@@ -94,28 +94,15 @@ def convert_number(old_max: float, old_min: float, new_max: float, new_min: floa
|
||||
|
||||
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:
|
||||
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:
|
||||
if type(date1) is not arrow.arrow.Arrow or type(
|
||||
date2) is not arrow.arrow.Arrow:
|
||||
@@ -208,16 +195,6 @@ 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:
|
||||
temp = 10 ** precision
|
||||
return math.floor(num * temp) / temp
|
||||
@@ -278,7 +255,7 @@ 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 is_unit_testing() or not keys 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:
|
||||
@@ -293,17 +270,10 @@ def get_config(keys: str, default: Any = None) -> Any:
|
||||
def get_strategy_class(strategy_name: str):
|
||||
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(f'jesse.strategies.{strategy_name}.{strategy_name}')
|
||||
else:
|
||||
strategy_dir = f'jesse.strategies.{strategy_name}.{strategy_name}'
|
||||
|
||||
return locate(strategy_dir)
|
||||
return locate(f'strategies.{strategy_name}.{strategy_name}')
|
||||
|
||||
|
||||
def insecure_hash(msg: str) -> str:
|
||||
@@ -342,7 +312,7 @@ def is_debugging() -> bool:
|
||||
|
||||
def is_importing_candles() -> bool:
|
||||
from jesse.config import config
|
||||
return config['app']['trading_mode'] == 'candles'
|
||||
return config['app']['trading_mode'] == 'import-candles'
|
||||
|
||||
|
||||
def is_live() -> bool:
|
||||
@@ -375,7 +345,7 @@ def is_unit_testing() -> bool:
|
||||
return "pytest" in sys.modules or config['app']['is_unit_testing']
|
||||
|
||||
|
||||
def is_valid_uuid(uuid_to_test:str, version: int = 4) -> bool:
|
||||
def is_valid_uuid(uuid_to_test, version: int = 4) -> bool:
|
||||
try:
|
||||
uuid_obj = uuid.UUID(uuid_to_test, version=version)
|
||||
except ValueError:
|
||||
@@ -393,6 +363,10 @@ def key(exchange: str, symbol: str, timeframe: str = None):
|
||||
def max_timeframe(timeframes_list: list) -> str:
|
||||
from jesse.enums import timeframes
|
||||
|
||||
if timeframes.WEEK_1 in timeframes_list:
|
||||
return timeframes.WEEK_1
|
||||
if timeframes.DAY_3 in timeframes_list:
|
||||
return timeframes.DAY_3
|
||||
if timeframes.DAY_1 in timeframes_list:
|
||||
return timeframes.DAY_1
|
||||
if timeframes.HOUR_12 in timeframes_list:
|
||||
@@ -427,42 +401,32 @@ def normalize(x: float, x_min: float, x_max: float) -> float:
|
||||
"""
|
||||
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:
|
||||
def now() -> int:
|
||||
"""
|
||||
Always returns the current time in milliseconds but rounds time in matter of seconds
|
||||
"""
|
||||
return now_to_timestamp(force_fresh)
|
||||
return now_to_timestamp()
|
||||
|
||||
|
||||
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() -> int:
|
||||
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 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 = [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)]
|
||||
|
||||
@@ -508,10 +472,10 @@ def orderbook_insertion_index_search(arr, target: int, ascending: bool = True) -
|
||||
lower = 0
|
||||
upper = len(arr)
|
||||
|
||||
if ascending:
|
||||
while lower < upper:
|
||||
x = lower + (upper - lower) // 2
|
||||
val = arr[x][0]
|
||||
if ascending:
|
||||
if target == val:
|
||||
return True, x
|
||||
elif target > val:
|
||||
@@ -522,7 +486,11 @@ def orderbook_insertion_index_search(arr, target: int, ascending: bool = True) -
|
||||
if lower == x:
|
||||
return False, lower
|
||||
upper = x
|
||||
elif target == val:
|
||||
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:
|
||||
@@ -556,8 +524,8 @@ def prepare_qty(qty: float, side: str) -> float:
|
||||
raise ValueError(f'{side} is not a valid input')
|
||||
|
||||
|
||||
def python_version() -> tuple:
|
||||
return sys.version_info[:2]
|
||||
def python_version() -> float:
|
||||
return float(f'{sys.version_info[0]}.{sys.version_info[1]}')
|
||||
|
||||
|
||||
def quote_asset(symbol: str) -> str:
|
||||
@@ -569,7 +537,7 @@ def quote_asset(symbol: str) -> str:
|
||||
|
||||
|
||||
def random_str(num_characters: int = 8) -> str:
|
||||
return ''.join(random.choice(string.ascii_letters) for _ in range(num_characters))
|
||||
return ''.join(random.choice(string.ascii_letters) for i in range(num_characters))
|
||||
|
||||
|
||||
def readable_duration(seconds: int, granularity: int = 2) -> str:
|
||||
@@ -657,9 +625,6 @@ def should_execute_silently() -> bool:
|
||||
def side_to_type(s: str) -> str:
|
||||
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,9 +632,9 @@ 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) -> str:
|
||||
try:
|
||||
return s.split(character, 1)[1]
|
||||
return string.split(character, 1)[1]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@@ -696,8 +661,8 @@ def style(msg_text: str, msg_style: str) -> str:
|
||||
|
||||
def terminate_app() -> None:
|
||||
# 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)
|
||||
|
||||
@@ -708,12 +673,10 @@ def error(msg: str, force_print: bool = False) -> None:
|
||||
from jesse.services import logger
|
||||
logger.error(msg)
|
||||
if force_print:
|
||||
_print_error(msg)
|
||||
print('\n')
|
||||
print(color('========== critical error =========='.upper(), 'red'))
|
||||
print(color(msg, 'red'))
|
||||
else:
|
||||
_print_error(msg)
|
||||
|
||||
|
||||
def _print_error(msg: str) -> None:
|
||||
print('\n')
|
||||
print(color('========== critical error =========='.upper(), 'red'))
|
||||
print(color(msg, 'red'))
|
||||
@@ -738,6 +701,8 @@ def timeframe_to_one_minutes(timeframe: str) -> int:
|
||||
timeframes.HOUR_8: 60 * 8,
|
||||
timeframes.HOUR_12: 60 * 12,
|
||||
timeframes.DAY_1: 60 * 24,
|
||||
timeframes.DAY_3: 60 * 24 * 3,
|
||||
timeframes.WEEK_1: 60 * 24 * 7,
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -797,156 +762,3 @@ def closing_side(position_type: str) -> str:
|
||||
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')
|
||||
|
||||
@@ -62,7 +62,6 @@ 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
|
||||
@@ -114,7 +113,6 @@ 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 .rocp import rocp
|
||||
from .rocr import rocr
|
||||
@@ -171,6 +169,5 @@ 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
|
||||
|
||||
@@ -39,11 +39,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
|
||||
|
||||
@@ -33,7 +33,7 @@ def alma(candles: np.ndarray, period: int = 9, sigma: float = 6.0, distribution_
|
||||
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[period - 1:] = np.tensordot(pnp_array3D, wtds, axes=((1), (0)))[:]
|
||||
res /= wtds.sum()
|
||||
res[res == 0] = np.nan
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ def cfo(candles: np.ndarray, period: int = 14, scalar: float = 100, source_type:
|
||||
|
||||
: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
|
||||
|
||||
@@ -25,10 +24,10 @@ def cfo(candles: np.ndarray, period: int = 14, scalar: float = 100, source_type:
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
res = scalar * (source - talib.LINEARREG(source, timeperiod=period))
|
||||
res /= source
|
||||
cfo = scalar * (source - talib.LINEARREG(source, timeperiod=period))
|
||||
cfo /= source
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
return cfo
|
||||
else:
|
||||
return None if np.isnan(res[-1]) else res[-1]
|
||||
return None if np.isnan(cfo[-1]) else cfo[-1]
|
||||
|
||||
@@ -32,15 +32,15 @@ def cg(candles: np.ndarray, period: int = 10, source_type: str = "close", sequen
|
||||
@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):
|
||||
for i in range(0, source.size):
|
||||
if i > period:
|
||||
num = 0
|
||||
denom = 0
|
||||
for count in range(period - 1):
|
||||
for count in range(0, period - 1):
|
||||
close = source[i - count]
|
||||
if not np.isnan(close):
|
||||
num += (1 + count) * close
|
||||
denom += close
|
||||
num = num + (1 + count) * close
|
||||
denom = denom + close
|
||||
result = -num / denom if denom != 0 else 0
|
||||
res[i] = result
|
||||
return res
|
||||
|
||||
@@ -14,7 +14,7 @@ def chande(candles: np.ndarray, period: int = 22, mult: float = 3.0, direction:
|
||||
|
||||
: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
|
||||
|
||||
@@ -40,10 +40,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: np.ndarray, W: int, type: str, 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)
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.helpers import slice_candles
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def chop(candles: np.ndarray, period: int = 14, scalar: float = 100, drift: int = 1, sequential: bool = False) -> Union[
|
||||
@@ -13,8 +13,6 @@ def chop(candles: np.ndarray, period: int = 14, scalar: float = 100, drift: int
|
||||
|
||||
: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
|
||||
|
||||
@@ -28,7 +28,7 @@ def correlation_cycle(candles: np.ndarray, period: int = 20, threshold: int = 9,
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
realPart, imagPart, angle = go_fast(source, period)
|
||||
realPart, imagPart, angle = go_fast(source, period, threshold)
|
||||
|
||||
priorAngle = np_shift(angle, 1, fill_value=np.nan)
|
||||
angle = np.where(np.logical_and(priorAngle > angle, priorAngle - angle < 270.0), priorAngle, angle)
|
||||
@@ -43,7 +43,7 @@ def correlation_cycle(candles: np.ndarray, period: int = 20, threshold: int = 9,
|
||||
|
||||
|
||||
@njit
|
||||
def go_fast(source, period): # Function is compiled to machine code when called the first time
|
||||
def go_fast(source, period, threshold): # Function is compiled to machine code when called the first time
|
||||
# Correlation Cycle Function
|
||||
PIx2 = 4.0 * np.arcsin(1.0)
|
||||
period = max(2, period)
|
||||
@@ -65,28 +65,31 @@ 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
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
@@ -39,11 +39,11 @@ def cwma(candles: np.ndarray, period: int = 14, source_type: str = "close", sequ
|
||||
def vpwma_fast(source, period):
|
||||
newseries = np.copy(source)
|
||||
for j in range(period + 1, source.shape[0]):
|
||||
my_sum = 0.0
|
||||
sum = 0.0
|
||||
weightSum = 0.0
|
||||
for i in range(period - 1):
|
||||
weight = np.power(period - i, 3)
|
||||
my_sum += (source[j - i] * weight)
|
||||
sum += (source[j - i] * weight)
|
||||
weightSum += weight
|
||||
newseries[j] = my_sum / weightSum
|
||||
newseries[j] = sum / weightSum
|
||||
return newseries
|
||||
|
||||
@@ -54,7 +54,7 @@ def damiani_volatmeter_fast(source, sed_std, atrvis, atrsed, vis_std,
|
||||
vol = np.full_like(source, 0)
|
||||
t = np.full_like(source, 0)
|
||||
for i in range(source.shape[0]):
|
||||
if i >= sed_std:
|
||||
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
|
||||
|
||||
@@ -14,7 +14,6 @@ def dec_osc(candles: np.ndarray, hp_period: int = 125, k: float = 1, source_type
|
||||
: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
|
||||
|
||||
:return: float | np.ndarray
|
||||
|
||||
@@ -13,7 +13,6 @@ def decycler(candles: np.ndarray, hp_period: int = 125, source_type: str = "clos
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param hp_period: int - default: 125
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
|
||||
@@ -17,7 +17,6 @@ def devstop(candles: np.ndarray, period: int = 20, mult: float = 0, devtype: int
|
||||
: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
|
||||
|
||||
|
||||
@@ -45,9 +45,9 @@ def edcf_fast(source, period):
|
||||
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
|
||||
distance = distance + np.power(source[j - i] - source[j - i - lb], 2)
|
||||
num = num + (distance * source[j - i])
|
||||
coefSum = coefSum + distance
|
||||
newseries[j] = num / coefSum if coefSum != 0 else 0
|
||||
|
||||
return newseries
|
||||
|
||||
@@ -17,7 +17,6 @@ def epma(candles: np.ndarray, period: int = 11, offset: int = 4, source_type: st
|
||||
|
||||
: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
|
||||
|
||||
@@ -40,11 +39,11 @@ def epma(candles: np.ndarray, period: int = 11, offset: int = 4, source_type: st
|
||||
def epma_fast(source, period, offset):
|
||||
newseries = np.copy(source)
|
||||
for j in range(period + offset + 1 , source.shape[0]):
|
||||
my_sum = 0.0
|
||||
sum = 0.0
|
||||
weightSum = 0.0
|
||||
for i in range(period - 1):
|
||||
weight = period - i - offset
|
||||
my_sum += (source[j - i] * weight)
|
||||
sum += (source[j - i] * weight)
|
||||
weightSum += weight
|
||||
newseries[j] = 1 / weightSum * my_sum
|
||||
newseries[j] = 1 / weightSum * sum
|
||||
return newseries
|
||||
|
||||
@@ -20,10 +20,10 @@ def fisher(candles: np.ndarray, period: int = 9, sequential: bool = False) -> Fi
|
||||
"""
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
fisher_val, fisher_signal = ti.fisher(np.ascontiguousarray(candles[:, 3]), np.ascontiguousarray(candles[:, 4]),
|
||||
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(same_length(candles, fisher), same_length(candles, fisher_signal))
|
||||
else:
|
||||
return FisherTransform(fisher_val[-1], fisher_signal[-1])
|
||||
return FisherTransform(fisher[-1], fisher_signal[-1])
|
||||
|
||||
@@ -31,12 +31,12 @@ 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)
|
||||
frama = frame_fast(candles, n, SC, FC)
|
||||
|
||||
if sequential:
|
||||
return res
|
||||
return frama
|
||||
else:
|
||||
return res[-1]
|
||||
return frama[-1]
|
||||
|
||||
|
||||
@njit
|
||||
@@ -79,10 +79,10 @@ 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(candles.shape[0])
|
||||
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, frama.shape[0]):
|
||||
frama[i] = (alphas[i] * candles[:, 2][i]) + (1 - alphas[i]) * frama[i - 1]
|
||||
return frama
|
||||
|
||||
@@ -43,7 +43,7 @@ def fibonacci(n: int = 2) -> np.ndarray:
|
||||
|
||||
result = np.array([a])
|
||||
|
||||
for _ in range(n):
|
||||
for i in range(0, n):
|
||||
a, b = b, a + b
|
||||
result = np.append(result, a)
|
||||
|
||||
|
||||
@@ -48,11 +48,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
|
||||
|
||||
@@ -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. 407–436.
|
||||
|
||||
|
||||
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)
|
||||
@@ -6,7 +6,7 @@ try:
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles, same_length
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
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[
|
||||
@@ -33,9 +33,7 @@ def hwma(candles: np.ndarray, na: float = 0.2, nb: float = 0.1, nc: float = 0.1,
|
||||
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)
|
||||
res = hwma_fast(source, na, nb, nc)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
|
||||
@@ -32,12 +32,21 @@ def ichimoku_cloud_seq(candles: np.ndarray, conversion_line_period: int = 9, bas
|
||||
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
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 +54,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
|
||||
|
||||
@@ -33,7 +33,7 @@ def jma(candles: np.ndarray, period:int=7, phase:float=50, power:int=2, source_t
|
||||
|
||||
@njit
|
||||
def jma_helper(src, phaseRatio, beta, alpha):
|
||||
jma_val = np.copy(src)
|
||||
jma = np.copy(src)
|
||||
|
||||
e0 = np.full_like(src, 0)
|
||||
e1 = np.full_like(src, 0)
|
||||
@@ -42,7 +42,7 @@ def jma_helper(src, phaseRatio, beta, alpha):
|
||||
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]
|
||||
e2[i] = (e0[i] + phaseRatio * e1[i] - jma[i-1]) * pow(1 - alpha, 2) + pow(alpha, 2) * e2[i-1]
|
||||
jma[i] = e2[i] + jma[i-1]
|
||||
|
||||
return jma_val
|
||||
return jma
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
|
||||
from jesse.indicators.ma import ma
|
||||
from jesse.helpers import slice_candles
|
||||
|
||||
|
||||
def kaufmanstop(candles: np.ndarray, period: int = 22, mult: float = 2, direction: str = "long", matype: int = 0,
|
||||
def kaufmanstop(candles: np.ndarray, period: int = 22, mult: float = 2, direction: str = "long",
|
||||
sequential: bool = False) -> Union[
|
||||
float, np.ndarray]:
|
||||
"""
|
||||
@@ -16,7 +16,6 @@ def kaufmanstop(candles: np.ndarray, period: int = 22, mult: float = 2, directio
|
||||
:param period: int - default: 22
|
||||
:param mult: float - default: 2
|
||||
:param direction: str - default: long
|
||||
:param matype: int - default: 0
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
@@ -26,7 +25,11 @@ def kaufmanstop(candles: np.ndarray, period: int = 22, mult: float = 2, directio
|
||||
high = candles[:, 3]
|
||||
low = candles[:, 4]
|
||||
|
||||
hl_diff = ma(high - low, period=period, matype=matype, sequential=True)
|
||||
hl_diff = talib.SMA(high - low, period)
|
||||
|
||||
if direction == "long":
|
||||
res = hl_diff * mult - low
|
||||
else:
|
||||
res = hl_diff * mult + high
|
||||
|
||||
res = low - hl_diff * mult if direction == "long" else high + hl_diff * mult
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -23,7 +23,7 @@ def kurtosis(candles: np.ndarray, period: int = 5, source_type: str = "hl2", seq
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
swv = sliding_window_view(source, window_shape=period)
|
||||
kurtosis_val = stats.kurtosis(swv, axis=-1)
|
||||
res = same_length(source, kurtosis_val)
|
||||
kurtosis = stats.kurtosis(swv, axis=-1)
|
||||
res = same_length(source, kurtosis)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -64,5 +64,9 @@ def lrsi_fast(alpha, candles):
|
||||
else:
|
||||
cd = cd + l3[i] - l2[i]
|
||||
|
||||
rsi[i] = 0 if cu + cd == 0 else cu / (cu + cd)
|
||||
if cu + cd == 0:
|
||||
rsi[i] = 0
|
||||
else:
|
||||
rsi[i] = cu / (cu + cd)
|
||||
|
||||
return rsi
|
||||
|
||||
@@ -12,69 +12,67 @@ def ma(candles: np.ndarray, period: int = 30, matype: int = 0, source_type: str
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 30
|
||||
:param matype: int - default: 0
|
||||
: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)
|
||||
|
||||
if matype <= 8:
|
||||
from talib import MA
|
||||
if len(candles.shape) != 1:
|
||||
candles = get_candle_source(candles, source_type=source_type)
|
||||
res = MA(candles, timeperiod=period, matype=matype)
|
||||
res = MA(source, timeperiod=period, matype=matype)
|
||||
elif matype == 9:
|
||||
from . import fwma
|
||||
res = fwma(candles, period, source_type=source_type, sequential=True)
|
||||
res = fwma(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 10:
|
||||
from . import hma
|
||||
res = hma(candles, period, source_type=source_type, sequential=True)
|
||||
res = hma(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 11:
|
||||
from talib import LINEARREG
|
||||
if len(candles.shape) != 1:
|
||||
candles = get_candle_source(candles, source_type=source_type)
|
||||
res = LINEARREG(candles, period)
|
||||
res = LINEARREG(source, period)
|
||||
elif matype == 12:
|
||||
from . import wilders
|
||||
res = wilders(candles, period, source_type=source_type, sequential=True)
|
||||
res = wilders(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 13:
|
||||
from . import sinwma
|
||||
res = sinwma(candles, period, source_type=source_type, sequential=True)
|
||||
res = sinwma(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 14:
|
||||
from . import supersmoother
|
||||
res = supersmoother(candles, period, source_type=source_type, sequential=True)
|
||||
res = supersmoother(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 15:
|
||||
from . import supersmoother_3_pole
|
||||
res = supersmoother_3_pole(candles, period, source_type=source_type, sequential=True)
|
||||
res = supersmoother_3_pole(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 16:
|
||||
from . import gauss
|
||||
res = gauss(candles, period, source_type=source_type, sequential=True)
|
||||
res = gauss(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 17:
|
||||
from . import high_pass
|
||||
res = high_pass(candles, period, source_type=source_type, sequential=True)
|
||||
res = high_pass(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 18:
|
||||
from . import high_pass_2_pole
|
||||
res = high_pass_2_pole(candles, period, source_type=source_type, sequential=True)
|
||||
res = high_pass_2_pole(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 19:
|
||||
from talib import HT_TRENDLINE
|
||||
if len(candles.shape) != 1:
|
||||
candles = get_candle_source(candles, source_type=source_type)
|
||||
res = HT_TRENDLINE(candles)
|
||||
res = HT_TRENDLINE(source)
|
||||
elif matype == 20:
|
||||
from . import jma
|
||||
res = jma(candles, period, source_type=source_type, sequential=True)
|
||||
res = jma(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 21:
|
||||
from . import reflex
|
||||
res = reflex(candles, period, source_type=source_type, sequential=True)
|
||||
res = reflex(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 22:
|
||||
from . import trendflex
|
||||
res = trendflex(candles, period, source_type=source_type, sequential=True)
|
||||
res = trendflex(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 23:
|
||||
from . import smma
|
||||
res = smma(candles, period, source_type=source_type, sequential=True)
|
||||
res = smma(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 24:
|
||||
if len(candles.shape) == 1:
|
||||
raise ValueError("vwma only works with normal candles.")
|
||||
@@ -82,50 +80,50 @@ def ma(candles: np.ndarray, period: int = 30, matype: int = 0, source_type: str
|
||||
res = vwma(candles, period, source_type=source_type, sequential=True)
|
||||
elif matype == 25:
|
||||
from . import pwma
|
||||
res = pwma(candles, period, source_type=source_type, sequential=True)
|
||||
res = pwma(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 26:
|
||||
from . import swma
|
||||
res = swma(candles, period, source_type=source_type, sequential=True)
|
||||
res = swma(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 27:
|
||||
from . import alma
|
||||
res = alma(candles, period, source_type=source_type, sequential=True)
|
||||
res = alma(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 28:
|
||||
from . import hwma
|
||||
res = hwma(candles, source_type=source_type, sequential=True)
|
||||
res = hwma(source, source_type=source_type, sequential=True)
|
||||
elif matype == 29:
|
||||
from . import vwap
|
||||
if len(candles.shape) == 1:
|
||||
raise ValueError("vwap only works with normal candles.")
|
||||
res = vwap(candles, source_type=source_type, sequential=True)
|
||||
res = vwap(source, source_type=source_type, sequential=True)
|
||||
elif matype == 30:
|
||||
from . import nma
|
||||
res = nma(candles, period, source_type=source_type, sequential=True)
|
||||
res = nma(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 31:
|
||||
from . import edcf
|
||||
res = edcf(candles, period, source_type=source_type, sequential=True)
|
||||
res = edcf(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 32:
|
||||
from . import mwdx
|
||||
res = mwdx(candles, source_type=source_type, sequential=True)
|
||||
res = mwdx(source, source_type=source_type, sequential=True)
|
||||
elif matype == 33:
|
||||
from . import maaq
|
||||
res = maaq(candles, period, source_type=source_type, sequential=True)
|
||||
res = maaq(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 34:
|
||||
from . import srwma
|
||||
res = srwma(candles, period, source_type=source_type, sequential=True)
|
||||
res = srwma(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 35:
|
||||
from . import sqwma
|
||||
res = sqwma(candles, period, source_type=source_type, sequential=True)
|
||||
res = sqwma(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 36:
|
||||
from . import vpwma
|
||||
res = vpwma(candles, period, source_type=source_type, sequential=True)
|
||||
res = vpwma(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 37:
|
||||
from . import cwma
|
||||
res = cwma(candles, period, source_type=source_type, sequential=True)
|
||||
res = cwma(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 38:
|
||||
from . import jsa
|
||||
res = jsa(candles, period, source_type=source_type, sequential=True)
|
||||
res = jsa(source, period, source_type=source_type, sequential=True)
|
||||
elif matype == 39:
|
||||
from . import epma
|
||||
res = epma(candles, period, source_type=source_type, sequential=True)
|
||||
res = epma(source, period, source_type=source_type, sequential=True)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -7,7 +7,7 @@ try:
|
||||
except ImportError:
|
||||
njit = lambda a : a
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles, np_shift, same_length
|
||||
from jesse.helpers import get_candle_source, slice_candles, same_length, np_shift
|
||||
|
||||
|
||||
def maaq(candles: np.ndarray, period: int = 11, fast_period: int = 2, slow_period: int = 30, source_type: str = "close", sequential: bool = False) -> Union[
|
||||
@@ -16,7 +16,7 @@ def maaq(candles: np.ndarray, period: int = 11, fast_period: int = 2, slow_perio
|
||||
Moving Average Adaptive Q
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 11
|
||||
:param period: int - default: 14
|
||||
:param fast_period: int - default: 2
|
||||
:param slow_period: int - default: 30
|
||||
:param source_type: str - default: "close"
|
||||
@@ -32,8 +32,6 @@ def maaq(candles: np.ndarray, period: int = 11, fast_period: int = 2, slow_perio
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
source = source[~np.isnan(source)]
|
||||
|
||||
diff = np.abs(source - np_shift(source, 1, np.nan))
|
||||
signal = np.abs(source - np_shift(source, period, np.nan))
|
||||
noise = talib.SUM(diff, period)
|
||||
@@ -46,7 +44,6 @@ def maaq(candles: np.ndarray, period: int = 11, fast_period: int = 2, slow_perio
|
||||
temp = np.power((ratio * fastSc) + slowSc, 2)
|
||||
|
||||
res = maaq_fast(source, temp, period)
|
||||
res = same_length(candles, res)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
|
||||
@@ -27,10 +27,10 @@ def macd(candles: np.ndarray, fast_period: int = 12, slow_period: int = 26, sign
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
macd_val, macdsignal, macdhist = talib.MACD(source, fastperiod=fast_period, slowperiod=slow_period,
|
||||
macd, macdsignal, macdhist = talib.MACD(source, fastperiod=fast_period, slowperiod=slow_period,
|
||||
signalperiod=signal_period)
|
||||
|
||||
if sequential:
|
||||
return MACD(macd_val, macdsignal, macdhist)
|
||||
return MACD(macd, macdsignal, macdhist)
|
||||
else:
|
||||
return MACD(macd_val[-1], macdsignal[-1], macdhist[-1])
|
||||
return MACD(macd[-1], macdsignal[-1], macdhist[-1])
|
||||
|
||||
@@ -2,7 +2,8 @@ from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles, same_length
|
||||
from jesse.helpers import get_candle_source
|
||||
from jesse.helpers import slice_candles
|
||||
from jesse.indicators.ma import ma
|
||||
|
||||
MACDEXT = namedtuple('MACDEXT', ['macd', 'signal', 'hist'])
|
||||
@@ -16,11 +17,11 @@ def macdext(candles: np.ndarray, fast_period: int = 12, fast_matype: int = 0, sl
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param fast_period: int - default: 12
|
||||
:param fast_matype: int - default: 0
|
||||
:param fastmatype: int - default: 0
|
||||
:param slow_period: int - default: 26
|
||||
:param slow_matype: int - default: 0
|
||||
:param slowmatype: int - default: 0
|
||||
:param signal_period: int - default: 9
|
||||
:param signal_matype: int - default: 0
|
||||
:param signalmatype: int - default: 0
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
@@ -28,23 +29,10 @@ def macdext(candles: np.ndarray, fast_period: int = 12, fast_matype: int = 0, sl
|
||||
"""
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
if fast_matype == 29 or slow_matype == 29 or signal_matype == 29:
|
||||
raise ValueError("VWAP not supported in macdext.")
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
ma_fast = ma(candles, period=fast_period, matype=fast_matype, source_type=source_type, sequential=True)
|
||||
ma_slow = ma(candles, period=slow_period, matype=slow_matype, source_type=source_type, sequential=True)
|
||||
macd = ma_fast - ma_slow
|
||||
|
||||
if signal_matype == 24:
|
||||
# volume needed.
|
||||
candles[:, 2] = macd
|
||||
candles_without_nan = candles[~np.isnan(candles).any(axis=1)]
|
||||
macdsignal = ma(candles_without_nan, period=signal_period, matype=signal_matype, source_type="close", sequential=True)
|
||||
else:
|
||||
macd_without_nan = macd[~np.isnan(macd)]
|
||||
macdsignal = ma(macd_without_nan, period=signal_period, matype=signal_matype, sequential=True)
|
||||
|
||||
macdsignal = same_length(candles, macdsignal)
|
||||
macd = ma(source, period=fast_period, matype=fast_matype, sequential=True) - ma(source, period=slow_period, matype=slow_matype, sequential=True)
|
||||
macdsignal = ma(macd, period=signal_period, matype=signal_matype, sequential=True)
|
||||
macdhist = macd - macdsignal
|
||||
|
||||
if sequential:
|
||||
|
||||
@@ -28,9 +28,9 @@ def mama(candles: np.ndarray, fastlimit: float = 0.5, slowlimit: float = 0.05, s
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
mama_val, fama = talib.MAMA(source, fastlimit=fastlimit, slowlimit=slowlimit)
|
||||
mama, fama = talib.MAMA(source, fastlimit=fastlimit, slowlimit=slowlimit)
|
||||
|
||||
if sequential:
|
||||
return MAMA(mama_val, fama)
|
||||
return MAMA(mama, fama)
|
||||
else:
|
||||
return MAMA(mama_val[-1], fama[-1])
|
||||
return MAMA(mama[-1], fama[-1])
|
||||
|
||||
@@ -18,7 +18,6 @@ def mcginley_dynamic(candles: np.ndarray, period: int = 10, k: float = 0.6, sour
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 10
|
||||
:param k: float - default: 0.6
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
|
||||
@@ -40,4 +40,4 @@ def minmax(candles: np.ndarray, order: int = 3, sequential: bool = False) -> EXT
|
||||
if sequential:
|
||||
return EXTREMA(is_min, is_max, last_min, last_max)
|
||||
else:
|
||||
return EXTREMA(is_min[-(order+1)], is_max[-(order+1)], last_min[-1], last_max[-1])
|
||||
return EXTREMA(is_min[-1], is_max[-1], last_min[-1], last_max[-1])
|
||||
|
||||
@@ -16,7 +16,7 @@ def mwdx(candles: np.ndarray, factor: float = 0.2, source_type: str = "close", s
|
||||
MWDX Average
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param factor: float - default: 0.2
|
||||
:param factor: float - default: 14
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
import numpy as np
|
||||
|
||||
from collections import namedtuple
|
||||
@@ -37,7 +38,7 @@ def pma(candles: np.ndarray, source_type: str = "hl2", sequential: bool = False)
|
||||
return PMA(predict[-1], trigger[-1])
|
||||
|
||||
|
||||
@njit
|
||||
|
||||
def pma_fast(source):
|
||||
predict = np.full_like(source, np.nan)
|
||||
trigger = np.full_like(source, np.nan)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
from typing import Union
|
||||
from functools import reduce
|
||||
from operator import mul
|
||||
@@ -44,9 +45,11 @@ def pascals_triangle(n: int = None) -> np.ndarray:
|
||||
n = int(np.fabs(n)) if n is not None else 0
|
||||
|
||||
# Calculation
|
||||
triangle = np.array([combination(n=n, r=i) for i in range(n + 1)])
|
||||
triangle = np.array([combination(n=n, r=i) for i in range(0, n + 1)])
|
||||
triangle_sum = np.sum(triangle)
|
||||
return triangle / triangle_sum
|
||||
triangle_weights = triangle / triangle_sum
|
||||
|
||||
return triangle_weights
|
||||
|
||||
|
||||
def combination(n, r) -> int:
|
||||
|
||||
@@ -45,13 +45,13 @@ def reflex_fast(ssf, period):
|
||||
ms = np.full_like(ssf, 0)
|
||||
sums = np.full_like(ssf, 0)
|
||||
for i in range(ssf.shape[0]):
|
||||
if i >= period:
|
||||
if not (i < period):
|
||||
slope = (ssf[i - period] - ssf[i]) / period
|
||||
my_sum = 0
|
||||
sum = 0
|
||||
for t in range(1, period + 1):
|
||||
my_sum = my_sum + (ssf[i] + t * slope) - ssf[i - t]
|
||||
my_sum /= period
|
||||
sums[i] = my_sum
|
||||
sum = sum + (ssf[i] + t * slope) - ssf[i - t]
|
||||
sum = sum / period
|
||||
sums[i] = sum
|
||||
|
||||
ms[i] = 0.04 * sums[i] * sums[i] + 0.96 * ms[i - 1]
|
||||
if ms[i] > 0:
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import numpy as np
|
||||
from typing import Union
|
||||
|
||||
try:
|
||||
from numba import njit, guvectorize
|
||||
except ImportError:
|
||||
njit = lambda a: a
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
|
||||
def rma(candles: np.ndarray, length: int = 14, source_type="close", sequential=False) -> \
|
||||
Union[float, np.ndarray]:
|
||||
"""
|
||||
Moving average used in RSI. It is the exponentially weighted moving average with alpha = 1 / length.
|
||||
RETURNS Exponential moving average of x with alpha = 1 / y.
|
||||
https://www.tradingview.com/pine-script-reference/#fun_rma
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param length: int - default: 14
|
||||
:param source_type: str - default: close
|
||||
:param sequential: bool - default: False
|
||||
:return: Union[float, np.ndarray]
|
||||
"""
|
||||
|
||||
if length < 1:
|
||||
raise ValueError('Bad parameters.')
|
||||
|
||||
# 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 = rma_fast(source, length)
|
||||
return res if sequential else res[-1]
|
||||
|
||||
|
||||
@njit
|
||||
def rma_fast(source, _length):
|
||||
alpha = 1 / _length
|
||||
newseries = np.copy(source)
|
||||
out = np.full_like(source, np.nan)
|
||||
for i in range(source.size):
|
||||
if np.isnan(newseries[i - 1]):
|
||||
# Sma in Numba
|
||||
asum = 0.0
|
||||
count = 0
|
||||
for i in range(_length):
|
||||
asum += source[i]
|
||||
count += 1
|
||||
out[i] = asum / count
|
||||
for i in range(_length, len(source)):
|
||||
asum += source[i] - source[i - _length]
|
||||
out[i] = asum / count
|
||||
newseries[i] = out[-1]
|
||||
else:
|
||||
prev = newseries[i - 1]
|
||||
if np.isnan(prev):
|
||||
prev = 0
|
||||
newseries[i] = alpha * source[i] + (1 - alpha) * prev
|
||||
return newseries
|
||||
@@ -13,8 +13,7 @@ def roofing(candles: np.ndarray, hp_period: int = 48, lp_period: int = 10, sourc
|
||||
Roofing Filter indicator by John F. Ehlers
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param hp_period: int - default: 48
|
||||
:param lp_period: int - default: 10
|
||||
:param period: int - default: 20
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
|
||||
@@ -17,11 +17,7 @@ def rsmk(candles: np.ndarray, candles_compare: np.ndarray, lookback: int = 90, p
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param candles_compare: np.ndarray
|
||||
:param lookback: int - default: 90
|
||||
:param period: int - default: 3
|
||||
:param signal_period: int - default: 20
|
||||
:param matype: int - default: 1
|
||||
:param signal_matype: int - default: 1
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ def rsx(candles: np.ndarray, period: int = 14, source_type: str = "close", seque
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 14
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
@@ -34,7 +33,7 @@ def rsx_fast(source, period):
|
||||
# variables
|
||||
f0 = 0
|
||||
f8 = 0
|
||||
# f10 = 0
|
||||
f10 = 0
|
||||
f18 = 0
|
||||
f20 = 0
|
||||
f28 = 0
|
||||
@@ -52,15 +51,15 @@ def rsx_fast(source, period):
|
||||
f88 = 0
|
||||
f90 = 0
|
||||
|
||||
# v4 = 0
|
||||
# v8 = 0
|
||||
# v10 = 0
|
||||
v4 = 0
|
||||
v8 = 0
|
||||
v10 = 0
|
||||
v14 = 0
|
||||
# v18 = 0
|
||||
v18 = 0
|
||||
v20 = 0
|
||||
|
||||
# vC = 0
|
||||
# v1C = 0
|
||||
vC = 0
|
||||
v1C = 0
|
||||
|
||||
res = np.full_like(source, np.nan)
|
||||
|
||||
@@ -68,12 +67,18 @@ def rsx_fast(source, period):
|
||||
if f90 == 0:
|
||||
f90 = 1.0
|
||||
f0 = 0.0
|
||||
f88 = period - 1.0 if period >= 6 else 5.0
|
||||
if period - 1.0 >= 5:
|
||||
f88 = period - 1.0
|
||||
else:
|
||||
f88 = 5.0
|
||||
f8 = 100.0 * source[i]
|
||||
f18 = 3.0 / (period + 2.0)
|
||||
f20 = 1.0 - f18
|
||||
else:
|
||||
f90 = f88 + 1 if f88 <= f90 else f90 + 1
|
||||
if f88 <= f90:
|
||||
f90 = f88 + 1
|
||||
else:
|
||||
f90 = f90 + 1
|
||||
f10 = f8
|
||||
f8 = 100 * source[i]
|
||||
v8 = f8 - f10
|
||||
@@ -101,8 +106,10 @@ def rsx_fast(source, period):
|
||||
f90 = 0.0
|
||||
if f88 < f90 and v20 > 0.0000000001:
|
||||
v4 = (v14 / v20 + 1.0) * 50.0
|
||||
v4 = min(v4, 100.0)
|
||||
v4 = max(v4, 0.0)
|
||||
if v4 > 100.0:
|
||||
v4 = 100.0
|
||||
if v4 < 0.0:
|
||||
v4 = 0.0
|
||||
else:
|
||||
v4 = 50.0
|
||||
res[i] = v4
|
||||
|
||||
@@ -17,7 +17,6 @@ def rvi(candles: np.ndarray, period: int = 10, ma_len: int = 14, matype: int = 1
|
||||
:param period: int - default: 10
|
||||
:param ma_len: int - default: 14
|
||||
:param matype: int - default: 1
|
||||
:param devtype: int - default: 0
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
:return: float | np.ndarray
|
||||
|
||||
@@ -15,13 +15,13 @@ def sarext(candles: np.ndarray, start_value: float = 0, offset_on_reverse: float
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param start_value: float - default: 0
|
||||
:param offset_on_reverse: float - default: 0
|
||||
:param acceleration_init_long: float - default: 0
|
||||
:param acceleration_long: float - default: 0
|
||||
:param acceleration_max_long: float - default: 0
|
||||
:param acceleration_init_short: float - default: 0
|
||||
:param acceleration_short: float - default: 0
|
||||
:param acceleration_max_short: float - default: 0
|
||||
:param offsetonreverse: float - default: 0
|
||||
:param accelerationinitlong: float - default: 0
|
||||
:param accelerationlong: float - default: 0
|
||||
:param accelerationmaxlong: float - default: 0
|
||||
:param accelerationinitshort: float - default: 0
|
||||
:param accelerationshort: float - default: 0
|
||||
:param accelerationmaxshort: float - default: 0
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
|
||||
@@ -25,10 +25,7 @@ def sinwma(candles: np.ndarray, period: int = 14, source_type: str = "close", se
|
||||
candles = slice_candles(candles, sequential)
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
sines = np.array(
|
||||
[np.sin((i + 1) * np.pi / (period + 1)) for i in range(period)]
|
||||
)
|
||||
|
||||
sines = np.array([np.sin((i + 1) * np.pi / (period + 1)) for i in range(0, period)])
|
||||
w = sines / sines.sum()
|
||||
swv = sliding_window_view(source, window_shape=period)
|
||||
res = np.average(swv, weights=w, axis=-1)
|
||||
|
||||
@@ -36,11 +36,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
|
||||
|
||||
@@ -39,11 +39,11 @@ def sqwma(candles: np.ndarray, period: int = 14, source_type: str = "close", seq
|
||||
def sqwma_fast(source, period):
|
||||
newseries = np.copy(source)
|
||||
for j in range(period + 1, source.shape[0]):
|
||||
my_sum = 0.0
|
||||
sum = 0.0
|
||||
weightSum = 0.0
|
||||
for i in range(period - 1):
|
||||
weight = np.power(period - i, 2)
|
||||
my_sum += (source[j - i] * weight)
|
||||
sum += (source[j - i] * weight)
|
||||
weightSum += weight
|
||||
newseries[j] = my_sum / weightSum
|
||||
newseries[j] = sum / weightSum
|
||||
return newseries
|
||||
|
||||
@@ -15,7 +15,7 @@ def srsi(candles: np.ndarray, period: int = 14, period_stoch: int = 14, k: int =
|
||||
Stochastic RSI
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default: 14 - RSI Length
|
||||
:param period_rsi: int - default: 14 - RSI Length
|
||||
:param period_stoch: int - default: 14 - Stochastic Length
|
||||
:param k: int - default: 3
|
||||
:param d: int - default: 3
|
||||
|
||||
@@ -39,11 +39,11 @@ def srwma(candles: np.ndarray, period: int = 14, source_type: str = "close", seq
|
||||
def srwma_fast(source, period):
|
||||
newseries = np.copy(source)
|
||||
for j in range(period + 1, source.shape[0]):
|
||||
my_sum = 0.0
|
||||
sum = 0.0
|
||||
weightSum = 0.0
|
||||
for i in range(period - 1):
|
||||
weight = np.power(period - i, 0.5)
|
||||
my_sum += (source[j - i] * weight)
|
||||
sum += (source[j - i] * weight)
|
||||
weightSum += weight
|
||||
newseries[j] = my_sum / weightSum
|
||||
newseries[j] = sum / weightSum
|
||||
return newseries
|
||||
|
||||
@@ -16,9 +16,9 @@ def stc(candles: np.ndarray, fast_period: int = 23, fast_matype: int = 1, slow_p
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param fast_period: int - default: 23
|
||||
:param fast_matype: int - default: 1
|
||||
:param fastmatype: int - default: 1
|
||||
:param slow_period: int - default: 50
|
||||
:param slow_matype: int - default: 1
|
||||
:param slowmatype: int - default: 1
|
||||
:param k_period: int - default: 10
|
||||
:param d_period: int - default: 3
|
||||
:param source_type: str - default: "close"
|
||||
|
||||
@@ -33,8 +33,8 @@ def stoch(candles: np.ndarray, fastk_period: int = 14, slowk_period: int = 3, sl
|
||||
hh = talib.MAX(candles_high, fastk_period)
|
||||
ll = talib.MIN(candles_low, fastk_period)
|
||||
|
||||
stoch_val = 100 * (candles_close - ll) / (hh - ll)
|
||||
k = ma(stoch_val, period=slowk_period, matype=slowk_matype, sequential=True)
|
||||
stoch = 100 * (candles_close - ll) / (hh - ll)
|
||||
k = ma(stoch, period=slowk_period, matype=slowk_matype, sequential=True)
|
||||
d = ma(k, period=slowd_period, matype=slowd_matype, sequential=True)
|
||||
|
||||
if sequential:
|
||||
|
||||
@@ -15,10 +15,12 @@ SuperTrend = namedtuple('SuperTrend', ['trend', 'changed'])
|
||||
def supertrend(candles: np.ndarray, period: int = 10, factor: float = 3, sequential: bool = False) -> SuperTrend:
|
||||
"""
|
||||
SuperTrend
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param period: int - default=14
|
||||
:param factor: float - default=3
|
||||
:param sequential: bool - default=False
|
||||
:param period: int - default: 14
|
||||
:param factor: float - default: 3
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: SuperTrend(trend, changed)
|
||||
"""
|
||||
|
||||
@@ -42,13 +44,13 @@ def supertrend_fast(candles, atr, factor, period):
|
||||
lower_basic = (candles[:, 3] + candles[:, 4]) / 2 - (factor * atr)
|
||||
upper_band = upper_basic
|
||||
lower_band = lower_basic
|
||||
super_trend = np.zeros(len(candles))
|
||||
changed = np.zeros(len(candles))
|
||||
super_trend = np.zeros(candles.size)
|
||||
changed = np.zeros(candles.size)
|
||||
|
||||
# calculate the bands:
|
||||
# in an UPTREND, lower band does not decrease
|
||||
# in a DOWNTREND, upper band does not increase
|
||||
for i in range(period, len(candles)):
|
||||
for i in range(period, candles.size):
|
||||
# if currently in DOWNTREND (i.e. price is below upper band)
|
||||
prevClose = candles[:, 2][i - 1]
|
||||
prevUpperBand = upper_band[i - 1]
|
||||
@@ -71,7 +73,7 @@ def supertrend_fast(candles, atr, factor, period):
|
||||
super_trend[i - 1] = prevLowerBand
|
||||
prevSuperTrend = super_trend[i - 1]
|
||||
|
||||
for i in range(period, len(candles)):
|
||||
for i in range(period, candles.size):
|
||||
prevClose = candles[:, 2][i - 1]
|
||||
prevUpperBand = upper_band[i - 1]
|
||||
currUpperBand = upper_band[i]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
from typing import Union
|
||||
from math import floor
|
||||
import numpy as np
|
||||
@@ -48,16 +49,17 @@ def symmetric_triangle(n: int = None) -> np.ndarray:
|
||||
|
||||
if n > 2:
|
||||
if n % 2 == 0:
|
||||
front = [i + 1 for i in range(floor(n / 2))]
|
||||
front = [i + 1 for i in range(0, floor(n / 2))]
|
||||
triangle = front + front[::-1]
|
||||
else:
|
||||
front = [i + 1 for i in range(floor(0.5 * (n + 1)))]
|
||||
front = [i + 1 for i in range(0, floor(0.5 * (n + 1)))]
|
||||
triangle = front.copy()
|
||||
front.pop()
|
||||
triangle += front[::-1]
|
||||
|
||||
|
||||
triangle_sum = np.sum(triangle)
|
||||
return triangle / triangle_sum
|
||||
triangle_weights = triangle / triangle_sum
|
||||
return triangle_weights
|
||||
|
||||
|
||||
|
||||
@@ -47,12 +47,12 @@ def trendflex_fast(ssf, period):
|
||||
sums = np.full_like(ssf, 0)
|
||||
|
||||
for i in range(ssf.shape[0]):
|
||||
if i >= period:
|
||||
my_sum = 0
|
||||
if not (i < period):
|
||||
sum = 0
|
||||
for t in range(1, period + 1):
|
||||
my_sum = my_sum + ssf[i] - ssf[i - t]
|
||||
my_sum /= period
|
||||
sums[i] = my_sum
|
||||
sum = sum + ssf[i] - ssf[i - t]
|
||||
sum = sum / period
|
||||
sums[i] = sum
|
||||
|
||||
ms[i] = 0.04 * sums[i] * sums[i] + 0.96 * ms[i - 1]
|
||||
if ms[i] != 0:
|
||||
|
||||
@@ -23,6 +23,6 @@ def var(candles: np.ndarray, period: int = 14, nbdev: float = 1, source_type: st
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
res = talib.VAR(source, timeperiod=period, nbdev=nbdev)
|
||||
res = talib.VAR(candles[:, 2], timeperiod=period, nbdev=nbdev)
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -62,7 +62,7 @@ def vlma_fast(source, a, b, c, d, min_period, max_period):
|
||||
period = np.zeros_like(source)
|
||||
for i in range(1, source.shape[0]):
|
||||
nz_period = period[i - 1] if period[i - 1] != 0 else max_period
|
||||
period[i] = nz_period + 1 if b[i] <= source[i] <= c[i] else nz_period - 1 if source[i] < a[i] or source[i] > d[i] else nz_period
|
||||
period[i] = nz_period + 1 if source[i] >= b[i] and source[i] <= c[i] else nz_period - 1 if source[i] < a[i] or source[i] > d[i] else nz_period
|
||||
period[i] = max(min(period[i], max_period), min_period)
|
||||
sc = 2 / (period[i] + 1)
|
||||
newseries[i] = (source[i] * sc) + ((1 - sc) * newseries[i - 1])
|
||||
|
||||
@@ -29,12 +29,12 @@ def voss(candles: np.ndarray, period: int = 20, predict: int = 3, bandwith: floa
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
voss_val, filt = voss_fast(source, period, predict, bandwith)
|
||||
voss, filt = voss_fast(source, period, predict, bandwith)
|
||||
|
||||
if sequential:
|
||||
return VossFilter(voss_val, filt)
|
||||
return VossFilter(voss, filt)
|
||||
else:
|
||||
return VossFilter(voss_val[-1], filt[-1])
|
||||
return VossFilter(voss[-1], filt[-1])
|
||||
|
||||
|
||||
@njit
|
||||
@@ -50,7 +50,7 @@ def voss_fast(source, period, predict, bandwith):
|
||||
s1 = 1 / g1 - np.sqrt(1 / (g1 * g1) - 1)
|
||||
|
||||
for i in range(source.shape[0]):
|
||||
if i > period and i > 5 and i > order:
|
||||
if not (i <= period or i <= 5 or i <= order):
|
||||
filt[i] = 0.5 * (1 - s1) * (source[i] - source[i - 2]) + f1 * (1 + s1) * filt[i - 1] - s1 * filt[i - 2]
|
||||
|
||||
for i in range(source.shape[0]):
|
||||
@@ -58,5 +58,6 @@ def voss_fast(source, period, predict, bandwith):
|
||||
sumc = 0
|
||||
for count in range(order):
|
||||
sumc = sumc + ((count + 1) / float(order)) * voss[i - (order - count)]
|
||||
|
||||
voss[i] = ((3 + order) / 2) * filt[i] - sumc
|
||||
return voss, filt
|
||||
|
||||
@@ -18,7 +18,7 @@ def vpt(candles: np.ndarray, source_type: str = "close", sequential: bool = Fals
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
source = get_candle_source(candles, source_type=source_type)
|
||||
vpt_val = (candles[:, 5] * ((source - np_shift(source, 1, fill_value=np.nan)) / np_shift(source, 1, fill_value=np.nan)))
|
||||
res = np_shift(vpt_val, 1, fill_value=np.nan) + vpt_val
|
||||
vpt = (candles[:, 5] * ((source - np_shift(source, 1, fill_value=np.nan)) / np_shift(source, 1, fill_value=np.nan)))
|
||||
res = np_shift(vpt, 1, fill_value=np.nan) + vpt
|
||||
|
||||
return res if sequential else res[-1]
|
||||
|
||||
@@ -40,11 +40,11 @@ def vpwma(candles: np.ndarray, period: int = 14, power: float = 0.382, source_ty
|
||||
def vpwma_fast(source, period, power):
|
||||
newseries = np.copy(source)
|
||||
for j in range(period + 1, source.shape[0]):
|
||||
my_sum = 0.0
|
||||
sum = 0.0
|
||||
weightSum = 0.0
|
||||
for i in range(period - 1):
|
||||
weight = np.power(period - i, power)
|
||||
my_sum += (source[j - i] * weight)
|
||||
sum += (source[j - i] * weight)
|
||||
weightSum += weight
|
||||
newseries[j] = my_sum / weightSum
|
||||
newseries[j] = sum / weightSum
|
||||
return newseries
|
||||
|
||||
@@ -18,7 +18,6 @@ def vwap(
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param source_type: str - default: "close"
|
||||
:param anchor: str - default: "D"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: float | np.ndarray
|
||||
|
||||
@@ -25,11 +25,11 @@ def vwmacd(candles: np.ndarray, fast_period: int = 12, slow_period: int = 26, si
|
||||
|
||||
vwma_slow = talib.SMA(candles[:, 2] * candles[:, 5], slow_period) / talib.SMA(candles[:, 5], slow_period)
|
||||
vwma_fast = talib.SMA(candles[:, 2] * candles[:, 5], fast_period) / talib.SMA(candles[:, 5], fast_period)
|
||||
vwmacd_val = vwma_fast - vwma_slow
|
||||
signal = talib.EMA(vwmacd_val, signal_period)
|
||||
hist = vwmacd_val - signal
|
||||
vwmacd = vwma_fast - vwma_slow
|
||||
signal = talib.EMA(vwmacd, signal_period)
|
||||
hist = vwmacd - signal
|
||||
|
||||
if sequential:
|
||||
return VWMACD(vwmacd_val, signal, hist)
|
||||
return VWMACD(vwmacd, signal, hist)
|
||||
else:
|
||||
return VWMACD(vwmacd_val[-1], signal[-1], hist[-1])
|
||||
return VWMACD(vwmacd[-1], signal[-1], hist[-1])
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
import talib as ta
|
||||
|
||||
from jesse.helpers import get_candle_source, slice_candles
|
||||
|
||||
Wavetrend = namedtuple('Wavetrend', ['wt1', 'wt2', 'wtCrossUp', 'wtCrossDown', 'wtOversold', 'wtOverbought', 'wtVwap'])
|
||||
|
||||
|
||||
# Wavetrend indicator ported from: https://www.tradingview.com/script/Msm4SjwI-VuManChu-Cipher-B-Divergences/
|
||||
# https://www.tradingview.com/script/2KE8wTuF-Indicator-WaveTrend-Oscillator-WT/
|
||||
#
|
||||
# buySignal = wtCross and wtCrossUp and wtOversold
|
||||
# sellSignal = wtCross and wtCrossDown and wtOverbought
|
||||
#
|
||||
# See https://github.com/ysdede/lazarus3/blob/partialexit/strategies/lazarus3/__init__.py for working jesse.ai example.
|
||||
|
||||
|
||||
def wt(candles: np.ndarray,
|
||||
wtchannellen: int = 9,
|
||||
wtaveragelen: int = 12,
|
||||
wtmalen: int = 3,
|
||||
oblevel: int = 53,
|
||||
oslevel: int = -53,
|
||||
source_type: str = "hlc3",
|
||||
sequential: bool = False) -> Wavetrend:
|
||||
"""
|
||||
Wavetrend indicator
|
||||
|
||||
:param candles: np.ndarray
|
||||
:param wtchannellen: int - default: 9
|
||||
:param wtaveragelen: int - default: 12
|
||||
:param wtmalen: int - default: 3
|
||||
:param oblevel: int - default: 53
|
||||
:param oslevel: int - default: -53
|
||||
:param source_type: str - default: "hlc3"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
:return: Wavetrend
|
||||
"""
|
||||
candles = slice_candles(candles, sequential)
|
||||
|
||||
src = get_candle_source(candles, source_type=source_type)
|
||||
|
||||
# wt
|
||||
esa = ta.EMA(src, wtchannellen)
|
||||
de = ta.EMA(abs(src - esa), wtchannellen)
|
||||
ci = (src - esa) / (0.015 * de)
|
||||
wt1 = ta.EMA(ci, wtaveragelen)
|
||||
wt2 = ta.SMA(wt1, wtmalen)
|
||||
|
||||
wtVwap = wt1 - wt2
|
||||
wtOversold = wt2 <= oslevel
|
||||
wtOverbought = wt2 >= oblevel
|
||||
wtCrossUp = wt2 - wt1 <= 0
|
||||
wtCrossDown = wt2 - wt1 >= 0
|
||||
|
||||
if sequential:
|
||||
return Wavetrend(wt1, wt2, wtCrossUp, wtCrossDown, wtOversold, wtOverbought, wtVwap)
|
||||
else:
|
||||
return Wavetrend(wt1[-1], wt2[-1], wtCrossUp[-1], wtCrossDown[-1], wtOversold[-1], wtOverbought[-1], wtVwap[-1])
|
||||
@@ -19,7 +19,6 @@ def zscore(candles: np.ndarray, period: int = 14, matype: int = 0, nbdev: float
|
||||
:param period: int - default: 14
|
||||
:param matype: int - default: 0
|
||||
:param nbdev: float - default: 1
|
||||
:param devtype: int - default: 0
|
||||
:param source_type: str - default: "close"
|
||||
:param sequential: bool - default: False
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import numpy as np
|
||||
import simplejson as json
|
||||
|
||||
|
||||
class NpEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, np.integer):
|
||||
return int(obj)
|
||||
elif isinstance(obj, np.bool_):
|
||||
return bool(obj)
|
||||
elif isinstance(obj, np.floating):
|
||||
return float(obj)
|
||||
elif isinstance(obj, np.ndarray):
|
||||
return obj.tolist()
|
||||
else:
|
||||
return super(NpEncoder, self).default(obj)
|
||||
@@ -11,17 +11,17 @@ class DynamicNumpyArray:
|
||||
allocation every N number. Hence, it's both fast and dynamic.
|
||||
"""
|
||||
|
||||
def __init__(self, shape: tuple, drop_at: int = None):
|
||||
def __init__(self, shape: tuple, drop_at=None):
|
||||
self.index = -1
|
||||
self.array = np.zeros(shape)
|
||||
self.bucket_size = shape[0]
|
||||
self.shape = shape
|
||||
self.drop_at = drop_at
|
||||
|
||||
def __str__(self) -> str:
|
||||
def __str__(self):
|
||||
return str(self.array[:self.index + 1])
|
||||
|
||||
def __len__(self) -> int:
|
||||
def __len__(self):
|
||||
return self.index + 1
|
||||
|
||||
def __getitem__(self, i):
|
||||
@@ -31,7 +31,9 @@ class DynamicNumpyArray:
|
||||
|
||||
if stop < 0:
|
||||
stop = (self.index + 1) - abs(stop)
|
||||
stop = min(stop, self.index + 1)
|
||||
if stop > (self.index + 1):
|
||||
stop = self.index + 1
|
||||
|
||||
return self.array[start:stop]
|
||||
else:
|
||||
if i < 0:
|
||||
@@ -43,7 +45,7 @@ class DynamicNumpyArray:
|
||||
|
||||
return self.array[i]
|
||||
|
||||
def __setitem__(self, i, item) -> None:
|
||||
def __setitem__(self, i, item):
|
||||
if i < 0:
|
||||
i = (self.index + 1) - abs(i)
|
||||
|
||||
@@ -53,7 +55,7 @@ class DynamicNumpyArray:
|
||||
|
||||
self.array[i] = item
|
||||
|
||||
def append(self, item: np.ndarray) -> None:
|
||||
def append(self, item: np.ndarray):
|
||||
self.index += 1
|
||||
|
||||
# expand if the arr is almost full
|
||||
@@ -62,11 +64,8 @@ class DynamicNumpyArray:
|
||||
self.array = np.concatenate((self.array, new_bucket), axis=0)
|
||||
|
||||
# drop N% of the beginning values to free memory
|
||||
if (
|
||||
self.drop_at is not None
|
||||
and self.index != 0
|
||||
and (self.index + 1) % self.drop_at == 0
|
||||
):
|
||||
if self.drop_at is not None:
|
||||
if self.index != 0 and (self.index + 1) % self.drop_at == 0:
|
||||
shift_num = int(self.drop_at / 2)
|
||||
self.index -= shift_num
|
||||
self.array = np_shift(self.array, -shift_num)
|
||||
@@ -80,7 +79,7 @@ class DynamicNumpyArray:
|
||||
|
||||
return self.array[self.index]
|
||||
|
||||
def get_past_item(self, past_index) -> np.ndarray:
|
||||
def get_past_item(self, past_index):
|
||||
# validation
|
||||
if self.index == -1:
|
||||
raise IndexError('list assignment index out of range')
|
||||
@@ -90,7 +89,7 @@ class DynamicNumpyArray:
|
||||
|
||||
return self.array[self.index - past_index]
|
||||
|
||||
def flush(self) -> None:
|
||||
def flush(self):
|
||||
self.index = -1
|
||||
self.array = np.zeros(self.shape)
|
||||
self.bucket_size = self.shape[0]
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import peewee
|
||||
from jesse.services.db import database
|
||||
|
||||
|
||||
if database.is_closed():
|
||||
database.open_connection()
|
||||
import jesse.helpers as jh
|
||||
from jesse.services.db import db
|
||||
|
||||
|
||||
class Candle(peewee.Model):
|
||||
@@ -21,23 +19,21 @@ class Candle(peewee.Model):
|
||||
is_partial = True
|
||||
|
||||
class Meta:
|
||||
from jesse.services.db import database
|
||||
|
||||
database = database.db
|
||||
database = db
|
||||
indexes = (
|
||||
(('timestamp', 'exchange', 'symbol'), True),
|
||||
)
|
||||
|
||||
def __init__(self, attributes: dict = None, **kwargs) -> None:
|
||||
def __init__(self, attributes=None, **kwargs) -> None:
|
||||
peewee.Model.__init__(self, attributes=attributes, **kwargs)
|
||||
|
||||
if attributes is None:
|
||||
attributes = {}
|
||||
|
||||
for a, value in attributes.items():
|
||||
setattr(self, a, value)
|
||||
for a in attributes:
|
||||
setattr(self, a, attributes[a])
|
||||
|
||||
|
||||
# if database is open, create the table
|
||||
if database.is_open():
|
||||
if not jh.is_unit_testing():
|
||||
# create the table
|
||||
Candle.create_table()
|
||||
|
||||
@@ -3,11 +3,7 @@ import peewee
|
||||
|
||||
import jesse.helpers as jh
|
||||
from jesse.config import config
|
||||
from jesse.services.db import database
|
||||
|
||||
|
||||
if database.is_closed():
|
||||
database.open_connection()
|
||||
from jesse.services.db import db
|
||||
|
||||
|
||||
class CompletedTrade(peewee.Model):
|
||||
@@ -33,22 +29,22 @@ class CompletedTrade(peewee.Model):
|
||||
orders = []
|
||||
|
||||
class Meta:
|
||||
from jesse.services.db import database
|
||||
|
||||
database = database.db
|
||||
database = db
|
||||
indexes = ((('strategy_name', 'exchange', 'symbol'), False),)
|
||||
|
||||
def __init__(self, attributes: dict = None, **kwargs) -> None:
|
||||
def __init__(self, attributes=None, **kwargs) -> None:
|
||||
peewee.Model.__init__(self, attributes=attributes, **kwargs)
|
||||
|
||||
if attributes is None:
|
||||
attributes = {}
|
||||
|
||||
for a, value in attributes.items():
|
||||
setattr(self, a, value)
|
||||
for a in attributes:
|
||||
setattr(self, a, attributes[a])
|
||||
|
||||
def toJSON(self) -> dict:
|
||||
orders = [o.__dict__ for o in self.orders]
|
||||
orders = []
|
||||
for o in self.orders:
|
||||
orders.append(o.__dict__)
|
||||
return {
|
||||
"id": self.id,
|
||||
"strategy_name": self.strategy_name,
|
||||
@@ -57,9 +53,15 @@ class CompletedTrade(peewee.Model):
|
||||
"type": self.type,
|
||||
"entry_price": self.entry_price,
|
||||
"exit_price": self.exit_price,
|
||||
"take_profit_at": self.take_profit_at,
|
||||
"stop_loss_at": self.stop_loss_at,
|
||||
"qty": self.qty,
|
||||
"fee": self.fee,
|
||||
"reward": self.reward,
|
||||
"size": self.size,
|
||||
"risk": self.risk,
|
||||
"risk_percentage": self.risk_percentage,
|
||||
"R": self.r,
|
||||
"PNL": self.pnl,
|
||||
"PNL_percentage": self.pnl_percentage,
|
||||
"holding_period": self.holding_period,
|
||||
@@ -79,13 +81,19 @@ class CompletedTrade(peewee.Model):
|
||||
'type': self.type,
|
||||
'entry_price': self.entry_price,
|
||||
'exit_price': self.exit_price,
|
||||
'take_profit_at': self.take_profit_at,
|
||||
'stop_loss_at': self.stop_loss_at,
|
||||
'qty': self.qty,
|
||||
'opened_at': self.opened_at,
|
||||
'closed_at': self.closed_at,
|
||||
'entry_candle_timestamp': self.entry_candle_timestamp,
|
||||
'exit_candle_timestamp': self.exit_candle_timestamp,
|
||||
"fee": self.fee,
|
||||
"reward": self.reward,
|
||||
"size": self.size,
|
||||
"risk": self.risk,
|
||||
"risk_percentage": self.risk_percentage,
|
||||
"R": self.r,
|
||||
"PNL": self.pnl,
|
||||
"PNL_percentage": self.pnl_percentage,
|
||||
"holding_period": self.holding_period,
|
||||
@@ -96,10 +104,31 @@ class CompletedTrade(peewee.Model):
|
||||
trading_fee = jh.get_config(f'env.exchanges.{self.exchange}.fee')
|
||||
return trading_fee * self.qty * (self.entry_price + self.exit_price)
|
||||
|
||||
@property
|
||||
def reward(self) -> float:
|
||||
return abs(self.take_profit_at - self.entry_price) * self.qty
|
||||
|
||||
@property
|
||||
def size(self) -> float:
|
||||
return self.qty * self.entry_price
|
||||
|
||||
@property
|
||||
def risk(self) -> float:
|
||||
return abs(self.stop_loss_at - self.entry_price) * self.qty
|
||||
|
||||
@property
|
||||
def risk_percentage(self) -> float:
|
||||
return round((self.risk / self.size) * 100, 2)
|
||||
|
||||
@property
|
||||
def risk_reward_ratio(self) -> float:
|
||||
return self.reward / self.risk
|
||||
|
||||
@property
|
||||
def r(self) -> float:
|
||||
"""alias for risk_reward_ratio"""
|
||||
return self.risk_reward_ratio
|
||||
|
||||
@property
|
||||
def pnl(self) -> float:
|
||||
"""PNL"""
|
||||
@@ -137,6 +166,6 @@ class CompletedTrade(peewee.Model):
|
||||
return (self.closed_at - self.opened_at) / 1000
|
||||
|
||||
|
||||
# if database is open, create the table
|
||||
if database.is_open():
|
||||
if not jh.is_unit_testing():
|
||||
# create the table
|
||||
CompletedTrade.create_table()
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import peewee
|
||||
from jesse.services.db import database
|
||||
|
||||
|
||||
if database.is_closed():
|
||||
database.open_connection()
|
||||
import jesse.helpers as jh
|
||||
from jesse.services.db import db
|
||||
|
||||
|
||||
class DailyBalance(peewee.Model):
|
||||
@@ -15,24 +13,22 @@ class DailyBalance(peewee.Model):
|
||||
balance = peewee.FloatField()
|
||||
|
||||
class Meta:
|
||||
from jesse.services.db import database
|
||||
|
||||
database = database.db
|
||||
database = db
|
||||
indexes = (
|
||||
(('identifier', 'exchange', 'timestamp', 'asset'), True),
|
||||
(('identifier', 'exchange'), False),
|
||||
)
|
||||
|
||||
def __init__(self, attributes: dict = None, **kwargs) -> None:
|
||||
def __init__(self, attributes=None, **kwargs) -> None:
|
||||
peewee.Model.__init__(self, attributes=attributes, **kwargs)
|
||||
|
||||
if attributes is None:
|
||||
attributes = {}
|
||||
|
||||
for a, value in attributes.items():
|
||||
setattr(self, a, value)
|
||||
for a in attributes:
|
||||
setattr(self, a, attributes[a])
|
||||
|
||||
|
||||
# if database is open, create the table
|
||||
if database.is_open():
|
||||
if not jh.is_unit_testing():
|
||||
# create the table
|
||||
DailyBalance.create_table()
|
||||
|
||||
@@ -32,29 +32,29 @@ class Exchange(ABC):
|
||||
self.fee_rate = fee_rate
|
||||
|
||||
@abstractmethod
|
||||
def wallet_balance(self, symbol: str = '') -> float:
|
||||
def wallet_balance(self, symbol=''):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def available_margin(self, symbol: str = '') -> float:
|
||||
def available_margin(self, symbol=''):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def on_order_submission(self, order: Order, skip_market_order: bool = True) -> None:
|
||||
def on_order_submission(self, order: Order, skip_market_order=True):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def on_order_execution(self, order: Order) -> None:
|
||||
def on_order_execution(self, order: Order):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def on_order_cancellation(self, order: Order) -> None:
|
||||
def on_order_cancellation(self, order: Order):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_realized_pnl(self, realized_pnl: float) -> None:
|
||||
def add_realized_pnl(self, realized_pnl: float):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def charge_fee(self, amount: float) -> None:
|
||||
def charge_fee(self, amount):
|
||||
pass
|
||||
|
||||
@@ -58,10 +58,10 @@ class FuturesExchange(Exchange):
|
||||
|
||||
self.settlement_currency = settlement_currency.upper()
|
||||
|
||||
def wallet_balance(self, symbol: str = '') -> float:
|
||||
def wallet_balance(self, symbol=''):
|
||||
return self.assets[self.settlement_currency]
|
||||
|
||||
def available_margin(self, symbol: str = '') -> float:
|
||||
def available_margin(self, symbol=''):
|
||||
# a temp which gets added to per each asset (remember that all future assets use the same currency for settlement)
|
||||
temp_credits = self.assets[self.settlement_currency]
|
||||
|
||||
@@ -96,26 +96,26 @@ class FuturesExchange(Exchange):
|
||||
# count in the leverage
|
||||
return temp_credits * self.futures_leverage
|
||||
|
||||
def charge_fee(self, amount: float) -> None:
|
||||
def charge_fee(self, amount):
|
||||
fee_amount = abs(amount) * self.fee_rate
|
||||
new_balance = self.assets[self.settlement_currency] - fee_amount
|
||||
if fee_amount != 0:
|
||||
logger.info(
|
||||
f'Charged {round(fee_amount, 2)} as fee. Balance for {self.settlement_currency} on {self.name} changed from {round(self.assets[self.settlement_currency], 2)} to {round(new_balance, 2)}'
|
||||
)
|
||||
self.assets[self.settlement_currency] = new_balance
|
||||
|
||||
def add_realized_pnl(self, realized_pnl: float) -> None:
|
||||
def add_realized_pnl(self, realized_pnl: float):
|
||||
new_balance = self.assets[self.settlement_currency] + realized_pnl
|
||||
logger.info(
|
||||
f'Added realized PNL of {round(realized_pnl, 2)}. Balance for {self.settlement_currency} on {self.name} changed from {round(self.assets[self.settlement_currency], 2)} to {round(new_balance, 2)}')
|
||||
self.assets[self.settlement_currency] = new_balance
|
||||
|
||||
def on_order_submission(self, order: Order, skip_market_order: bool = True) -> None:
|
||||
def on_order_submission(self, order: Order, skip_market_order=True):
|
||||
base_asset = jh.base_asset(order.symbol)
|
||||
|
||||
# make sure we don't spend more than we're allowed considering current allowed leverage
|
||||
if (order.type != order_types.MARKET or skip_market_order) and not order.is_reduce_only:
|
||||
if order.type != order_types.MARKET or skip_market_order:
|
||||
if not order.is_reduce_only:
|
||||
order_size = abs(order.qty * order.price)
|
||||
remaining_margin = self.available_margin()
|
||||
if order_size > remaining_margin:
|
||||
@@ -136,7 +136,7 @@ class FuturesExchange(Exchange):
|
||||
else:
|
||||
self.sell_orders[base_asset].append(np.array([order.qty, order.price]))
|
||||
|
||||
def on_order_execution(self, order: Order) -> None:
|
||||
def on_order_execution(self, order: Order):
|
||||
base_asset = jh.base_asset(order.symbol)
|
||||
|
||||
if order.type == order_types.MARKET:
|
||||
@@ -156,7 +156,7 @@ class FuturesExchange(Exchange):
|
||||
self.sell_orders[base_asset][index] = np.array([0, 0])
|
||||
break
|
||||
|
||||
def on_order_cancellation(self, order: Order) -> None:
|
||||
def on_order_cancellation(self, order: Order):
|
||||
base_asset = jh.base_asset(order.symbol)
|
||||
|
||||
self.available_assets[base_asset] -= order.qty
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import peewee
|
||||
from jesse.services.db import database
|
||||
|
||||
|
||||
if database.is_closed():
|
||||
database.open_connection()
|
||||
|
||||
|
||||
class Log(peewee.Model):
|
||||
id = peewee.UUIDField(primary_key=True)
|
||||
session_id = peewee.UUIDField(index=True)
|
||||
timestamp = peewee.BigIntegerField()
|
||||
message = peewee.TextField()
|
||||
# 1: info, 2: error, maybe add more in the future?
|
||||
type = peewee.SmallIntegerField()
|
||||
|
||||
class Meta:
|
||||
from jesse.services.db import database
|
||||
database = database.db
|
||||
|
||||
def __init__(self, attributes=None, **kwargs) -> None:
|
||||
peewee.Model.__init__(self, attributes=attributes, **kwargs)
|
||||
|
||||
if attributes is None:
|
||||
attributes = {}
|
||||
|
||||
for a in attributes:
|
||||
setattr(self, a, attributes[a])
|
||||
|
||||
|
||||
# if database is open, create the table
|
||||
if database.is_open():
|
||||
Log.create_table()
|
||||
@@ -1,32 +0,0 @@
|
||||
import peewee
|
||||
from jesse.services.db import database
|
||||
|
||||
|
||||
if database.is_closed():
|
||||
database.open_connection()
|
||||
|
||||
|
||||
class Option(peewee.Model):
|
||||
id = peewee.UUIDField(primary_key=True)
|
||||
updated_at = peewee.BigIntegerField()
|
||||
type = peewee.CharField()
|
||||
json = peewee.TextField()
|
||||
|
||||
class Meta:
|
||||
from jesse.services.db import database
|
||||
|
||||
database = database.db
|
||||
|
||||
def __init__(self, attributes=None, **kwargs) -> None:
|
||||
peewee.Model.__init__(self, attributes=attributes, **kwargs)
|
||||
|
||||
if attributes is None:
|
||||
attributes = {}
|
||||
|
||||
for a in attributes:
|
||||
setattr(self, a, attributes[a])
|
||||
|
||||
|
||||
# if database is open, create the table
|
||||
if database.is_open():
|
||||
Option.create_table()
|
||||
@@ -1,89 +1,69 @@
|
||||
import numpy as np
|
||||
from playhouse.postgres_ext import *
|
||||
|
||||
import jesse.helpers as jh
|
||||
import jesse.services.logger as logger
|
||||
import jesse.services.selectors as selectors
|
||||
from jesse import sync_publish
|
||||
from jesse.config import config
|
||||
from jesse.services.notifier import notify
|
||||
from jesse.enums import order_statuses, order_flags
|
||||
from jesse.services.db import database
|
||||
|
||||
|
||||
if database.is_closed():
|
||||
database.open_connection()
|
||||
from jesse.services.db import db
|
||||
from jesse.services.notifier import notify
|
||||
|
||||
|
||||
class Order(Model):
|
||||
# id generated by Jesse for database usage
|
||||
id = UUIDField(primary_key=True)
|
||||
trade_id = UUIDField(index=True, null=True)
|
||||
session_id = UUIDField(index=True)
|
||||
trade_id = UUIDField(index=True)
|
||||
|
||||
# id generated by market, used in live-trade mode
|
||||
exchange_id = CharField(null=True)
|
||||
exchange_id = CharField()
|
||||
# some exchanges might require even further info
|
||||
vars = JSONField(default={})
|
||||
|
||||
symbol = CharField()
|
||||
exchange = CharField()
|
||||
side = CharField()
|
||||
type = CharField()
|
||||
flag = CharField(null=True)
|
||||
qty = FloatField()
|
||||
price = FloatField(null=True)
|
||||
price = FloatField(default=np.nan)
|
||||
status = CharField(default=order_statuses.ACTIVE)
|
||||
created_at = BigIntegerField()
|
||||
executed_at = BigIntegerField(null=True)
|
||||
canceled_at = BigIntegerField(null=True)
|
||||
role = CharField(null=True)
|
||||
submitted_via = None
|
||||
|
||||
class Meta:
|
||||
from jesse.services.db import database
|
||||
|
||||
database = database.db
|
||||
database = db
|
||||
indexes = ((('exchange', 'symbol'), False),)
|
||||
|
||||
def __init__(self, attributes: dict = None, **kwargs) -> None:
|
||||
def __init__(self, attributes=None, **kwargs) -> None:
|
||||
Model.__init__(self, attributes=attributes, **kwargs)
|
||||
|
||||
if attributes is None:
|
||||
attributes = {}
|
||||
|
||||
for a, value in attributes.items():
|
||||
setattr(self, a, value)
|
||||
for a in attributes:
|
||||
setattr(self, a, attributes[a])
|
||||
|
||||
if self.created_at is None:
|
||||
self.created_at = jh.now_to_timestamp()
|
||||
|
||||
if jh.is_live():
|
||||
from jesse.store import store
|
||||
self.session_id = store.app.session_id
|
||||
self.save(force_insert=True)
|
||||
|
||||
if jh.is_live():
|
||||
if jh.is_live() and config['env']['notifications']['events']['submitted_orders']:
|
||||
self.notify_submission()
|
||||
if jh.is_debuggable('order_submission'):
|
||||
txt = f'{"QUEUED" if self.is_queued else "SUBMITTED"} order: {self.symbol}, {self.type}, {self.side}, {self.qty}'
|
||||
if self.price:
|
||||
txt += f', ${round(self.price, 2)}'
|
||||
logger.info(txt)
|
||||
logger.info(
|
||||
f'{"QUEUED" if self.is_queued else "SUBMITTED"} order: {self.symbol}, {self.type}, {self.side}, {self.qty}, ${round(self.price, 2)}'
|
||||
)
|
||||
|
||||
# handle exchange balance for ordered asset
|
||||
e = selectors.get_exchange(self.exchange)
|
||||
e.on_order_submission(self)
|
||||
|
||||
def broadcast(self) -> None:
|
||||
sync_publish('order', self.to_dict)
|
||||
|
||||
def notify_submission(self) -> None:
|
||||
self.broadcast()
|
||||
|
||||
if config['env']['notifications']['events']['submitted_orders']:
|
||||
txt = f'{"QUEUED" if self.is_queued else "SUBMITTED"} order: {self.symbol}, {self.type}, {self.side}, {self.qty}'
|
||||
if self.price:
|
||||
txt += f', ${round(self.price, 2)}'
|
||||
notify(txt)
|
||||
notify(
|
||||
f'{"QUEUED" if self.is_queued else "SUBMITTED"} order: {self.symbol}, {self.type}, {self.side}, { self.qty}, ${round(self.price, 2)}'
|
||||
)
|
||||
|
||||
@property
|
||||
def is_canceled(self) -> bool:
|
||||
@@ -124,79 +104,43 @@ class Order(Model):
|
||||
def is_close(self) -> bool:
|
||||
return self.flag == order_flags.CLOSE
|
||||
|
||||
@property
|
||||
def is_stop_loss(self):
|
||||
return self.submitted_via == 'stop-loss'
|
||||
|
||||
@property
|
||||
def is_take_profit(self):
|
||||
return self.submitted_via == 'take-profit'
|
||||
|
||||
@property
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'session_id': self.session_id,
|
||||
'exchange_id': self.exchange_id,
|
||||
'symbol': self.symbol,
|
||||
'side': self.side,
|
||||
'type': self.type,
|
||||
'qty': self.qty,
|
||||
'price': self.price,
|
||||
'flag': self.flag,
|
||||
'status': self.status,
|
||||
'created_at': self.created_at,
|
||||
'canceled_at': self.canceled_at,
|
||||
'executed_at': self.executed_at,
|
||||
}
|
||||
|
||||
def cancel(self, silent=False) -> None:
|
||||
def cancel(self) -> None:
|
||||
if self.is_canceled or self.is_executed:
|
||||
return
|
||||
|
||||
self.canceled_at = jh.now_to_timestamp()
|
||||
self.status = order_statuses.CANCELED
|
||||
|
||||
if jh.is_live():
|
||||
self.save()
|
||||
|
||||
if not silent:
|
||||
txt = f'CANCELED order: {self.symbol}, {self.type}, {self.side}, {self.qty}'
|
||||
if self.price:
|
||||
txt += f', ${round(self.price, 2)}'
|
||||
if jh.is_debuggable('order_cancellation'):
|
||||
logger.info(txt)
|
||||
if jh.is_live():
|
||||
self.broadcast()
|
||||
if config['env']['notifications']['events']['cancelled_orders']:
|
||||
notify(txt)
|
||||
logger.info(
|
||||
f'CANCELED order: {self.symbol}, {self.type}, {self.side}, { self.qty}, ${round(self.price, 2)}'
|
||||
)
|
||||
if jh.is_live() and config['env']['notifications']['events']['cancelled_orders']:
|
||||
notify(
|
||||
f'CANCELED order: {self.symbol}, {self.type}, {self.side}, {self.qty}, {round(self.price, 2)}'
|
||||
)
|
||||
|
||||
# handle exchange balance
|
||||
e = selectors.get_exchange(self.exchange)
|
||||
e.on_order_cancellation(self)
|
||||
|
||||
def execute(self, silent=False) -> None:
|
||||
def execute(self) -> None:
|
||||
if self.is_canceled or self.is_executed:
|
||||
return
|
||||
|
||||
self.executed_at = jh.now_to_timestamp()
|
||||
self.status = order_statuses.EXECUTED
|
||||
|
||||
if jh.is_live():
|
||||
self.save()
|
||||
|
||||
if not silent:
|
||||
txt = f'EXECUTED order: {self.symbol}, {self.type}, {self.side}, {self.qty}'
|
||||
if self.price:
|
||||
txt += f', ${round(self.price, 2)}'
|
||||
# log
|
||||
if jh.is_debuggable('order_execution'):
|
||||
logger.info(txt)
|
||||
logger.info(
|
||||
f'EXECUTED order: {self.symbol}, {self.type}, {self.side}, {self.qty}, ${round(self.price, 2)}'
|
||||
)
|
||||
# notify
|
||||
if jh.is_live():
|
||||
self.broadcast()
|
||||
if config['env']['notifications']['events']['executed_orders']:
|
||||
notify(txt)
|
||||
if jh.is_live() and config['env']['notifications']['events']['executed_orders']:
|
||||
notify(
|
||||
f'EXECUTED order: {self.symbol}, {self.type}, {self.side}, {self.qty}, {round(self.price, 2)}'
|
||||
)
|
||||
|
||||
p = selectors.get_position(self.exchange, self.symbol)
|
||||
|
||||
@@ -208,6 +152,6 @@ class Order(Model):
|
||||
e.on_order_execution(self)
|
||||
|
||||
|
||||
# if database is open, create the table
|
||||
if database.is_open():
|
||||
if not jh.is_unit_testing():
|
||||
# create the table
|
||||
Order.create_table()
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import peewee
|
||||
|
||||
import jesse.helpers as jh
|
||||
from jesse.services.db import db
|
||||
|
||||
|
||||
class Orderbook(peewee.Model):
|
||||
id = peewee.UUIDField(primary_key=True)
|
||||
@@ -11,16 +14,19 @@ class Orderbook(peewee.Model):
|
||||
data = peewee.BlobField()
|
||||
|
||||
class Meta:
|
||||
from jesse.services.db import database
|
||||
|
||||
database = database.db
|
||||
database = db
|
||||
indexes = ((('timestamp', 'exchange', 'symbol'), True),)
|
||||
|
||||
def __init__(self, attributes: dict = None, **kwargs) -> None:
|
||||
def __init__(self, attributes=None, **kwargs) -> None:
|
||||
peewee.Model.__init__(self, attributes=attributes, **kwargs)
|
||||
|
||||
if attributes is None:
|
||||
attributes = {}
|
||||
|
||||
for a, value in attributes.items():
|
||||
setattr(self, a, value)
|
||||
for a in attributes:
|
||||
setattr(self, a, attributes[a])
|
||||
|
||||
|
||||
if not jh.is_unit_testing():
|
||||
# create the table
|
||||
Orderbook.create_table()
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
import jesse.helpers as jh
|
||||
@@ -13,7 +11,7 @@ from jesse.utils import sum_floats, subtract_floats
|
||||
|
||||
|
||||
class Position:
|
||||
def __init__(self, exchange_name: str, symbol: str, attributes: dict = None) -> None:
|
||||
def __init__(self, exchange_name: str, symbol: str, attributes=None) -> None:
|
||||
self.id = jh.generate_unique_id()
|
||||
self.entry_price = None
|
||||
self.exit_price = None
|
||||
@@ -39,21 +37,21 @@ class Position:
|
||||
setattr(self, a, attributes[a])
|
||||
|
||||
@property
|
||||
def mark_price(self) -> float:
|
||||
def mark_price(self):
|
||||
if not jh.is_live():
|
||||
return self.current_price
|
||||
|
||||
return self._mark_price
|
||||
|
||||
@property
|
||||
def funding_rate(self) -> float:
|
||||
def funding_rate(self):
|
||||
if not jh.is_live():
|
||||
return 0
|
||||
|
||||
return self._funding_rate
|
||||
|
||||
@property
|
||||
def next_funding_timestamp(self) -> Union[int, None]:
|
||||
def next_funding_timestamp(self):
|
||||
if not jh.is_live():
|
||||
return None
|
||||
|
||||
@@ -114,7 +112,7 @@ class Position:
|
||||
return base_cost
|
||||
|
||||
@property
|
||||
def leverage(self) -> Union[int, np.float64]:
|
||||
def leverage(self):
|
||||
if self.exchange.type == 'spot':
|
||||
return 1
|
||||
|
||||
@@ -170,7 +168,7 @@ class Position:
|
||||
return self.exchange.futures_leverage_mode
|
||||
|
||||
@property
|
||||
def liquidation_price(self) -> Union[float, np.float64]:
|
||||
def liquidation_price(self):
|
||||
"""
|
||||
The price at which the position gets liquidated. formulas are taken from:
|
||||
https://help.bybit.com/hc/en-us/articles/900000181046-Liquidation-Price-USDT-Contract-
|
||||
@@ -181,10 +179,7 @@ class Position:
|
||||
if jh.is_livetrading():
|
||||
return self._liquidation_price
|
||||
|
||||
if self.mode in ['cross', 'spot']:
|
||||
return np.nan
|
||||
|
||||
elif self.mode == 'isolated':
|
||||
if self.mode == 'isolated':
|
||||
if self.type == 'long':
|
||||
return self.entry_price * (1 - self._initial_margin_rate + 0.004)
|
||||
elif self.type == 'short':
|
||||
@@ -192,15 +187,21 @@ class Position:
|
||||
else:
|
||||
return np.nan
|
||||
|
||||
elif self.mode == 'cross':
|
||||
return np.nan
|
||||
|
||||
elif self.mode == 'spot':
|
||||
return np.nan
|
||||
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
@property
|
||||
def _initial_margin_rate(self) -> float:
|
||||
def _initial_margin_rate(self):
|
||||
return 1 / self.leverage
|
||||
|
||||
@property
|
||||
def bankruptcy_price(self) -> Union[float, np.float64]:
|
||||
def bankruptcy_price(self):
|
||||
if self.type == 'long':
|
||||
return self.entry_price * (1 - self._initial_margin_rate)
|
||||
elif self.type == 'short':
|
||||
@@ -208,23 +209,6 @@ class Position:
|
||||
else:
|
||||
return np.nan
|
||||
|
||||
@property
|
||||
def to_dict(self):
|
||||
return {
|
||||
'entry_price': self.entry_price,
|
||||
'qty': self.qty,
|
||||
'current_price': self.current_price,
|
||||
'value': self.value,
|
||||
'type': self.type,
|
||||
'exchange': self.exchange_name,
|
||||
'pnl': self.pnl,
|
||||
'pnl_percentage': self.pnl_percentage,
|
||||
'leverage': self.leverage,
|
||||
'liquidation_price': self.liquidation_price,
|
||||
'bankruptcy_price': self.bankruptcy_price,
|
||||
'mode': self.mode,
|
||||
}
|
||||
|
||||
def _close(self, close_price: float) -> None:
|
||||
if self.is_open is False:
|
||||
raise EmptyPosition('The position is already closed.')
|
||||
@@ -288,7 +272,7 @@ class Position:
|
||||
raise OpenPositionError('position must be already open in order to increase its size')
|
||||
|
||||
qty = abs(qty)
|
||||
# size = qty * price
|
||||
size = qty * price
|
||||
|
||||
# if self.exchange:
|
||||
# self.exchange.decrease_futures_balance(size)
|
||||
@@ -318,7 +302,7 @@ class Position:
|
||||
self.qty = qty
|
||||
self.opened_at = jh.now_to_timestamp()
|
||||
|
||||
info_text = f'OPENED {self.type} position: {self.exchange_name}, {self.symbol}, {self.qty}, ${round(self.entry_price, 2)}'
|
||||
info_text = f'OPENED {self.type} position: {self.exchange_name}, {self.symbol}, { self.qty}, ${round(self.entry_price, 2)}'
|
||||
|
||||
if jh.is_debuggable('position_opened'):
|
||||
logger.info(info_text)
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
class Route:
|
||||
def __init__(self, exchange: str, symbol: str, timeframe: str = None, strategy_name: str = None,
|
||||
dna: str = None) -> None:
|
||||
# replace PERP with USD in FTX routes
|
||||
if exchange.startswith('FTX') and symbol.upper().endswith('PERP'):
|
||||
symbol = symbol.replace('PERP', 'USD')
|
||||
|
||||
self.exchange = exchange
|
||||
self.symbol = symbol
|
||||
self.timeframe = timeframe
|
||||
|
||||
@@ -7,10 +7,10 @@ from .Exchange import Exchange
|
||||
|
||||
|
||||
class SpotExchange(Exchange):
|
||||
def add_realized_pnl(self, realized_pnl: float) -> None:
|
||||
def add_realized_pnl(self, realized_pnl: float):
|
||||
pass
|
||||
|
||||
def charge_fee(self, amount: float) -> None:
|
||||
def charge_fee(self, amount):
|
||||
pass
|
||||
|
||||
# current holding assets
|
||||
@@ -29,13 +29,14 @@ class SpotExchange(Exchange):
|
||||
raise InvalidConfig(
|
||||
f"Jesse needs to know the balance of your base asset for spot mode. Please add {base_asset} to your exchanges assets config.")
|
||||
|
||||
def wallet_balance(self, symbol: str = '') -> float:
|
||||
|
||||
def wallet_balance(self, symbol=''):
|
||||
if symbol == '':
|
||||
raise ValueError
|
||||
quote_asset = jh.quote_asset(symbol)
|
||||
return self.assets[quote_asset]
|
||||
|
||||
def available_margin(self, symbol: str = '') -> float:
|
||||
def available_margin(self, symbol=''):
|
||||
return self.wallet_balance(symbol)
|
||||
|
||||
def on_order_submission(self, order: Order, skip_market_order=True):
|
||||
@@ -81,7 +82,7 @@ class SpotExchange(Exchange):
|
||||
f'Available balance for {base_asset} on {self.name} changed from {round(temp_old_base_available_asset, 2)} to {round(temp_new_base_available_asset, 2)}'
|
||||
)
|
||||
|
||||
def on_order_execution(self, order: Order) -> None:
|
||||
def on_order_execution(self, order: Order):
|
||||
base_asset = jh.base_asset(order.symbol)
|
||||
quote_asset = jh.quote_asset(order.symbol)
|
||||
|
||||
@@ -108,7 +109,7 @@ class SpotExchange(Exchange):
|
||||
temp_new_quote_asset = self.assets[quote_asset]
|
||||
if jh.is_debuggable('balance_update') and temp_old_quote_asset != temp_new_quote_asset:
|
||||
logger.info(
|
||||
f'Balance for {quote_asset} on {self.name} changed from {round(temp_old_quote_asset, 2)} to {round(temp_new_quote_asset, 2)}'
|
||||
f'Balance for {quote_asset} on {self.name} changed from {round(temp_old_quote_asset, 2)} to { round(temp_new_quote_asset, 2)}'
|
||||
)
|
||||
temp_new_quote_available_asset = self.available_assets[quote_asset]
|
||||
if jh.is_debuggable('balance_update') and temp_old_quote_available_asset != temp_new_quote_available_asset:
|
||||
@@ -127,7 +128,7 @@ class SpotExchange(Exchange):
|
||||
f'Balance for {base_asset} on {self.name} changed from {round(temp_old_base_available_asset, 2)} to {round(temp_new_base_available_asset, 2)}'
|
||||
)
|
||||
|
||||
def on_order_cancellation(self, order: Order) -> None:
|
||||
def on_order_cancellation(self, order: Order):
|
||||
base_asset = jh.base_asset(order.symbol)
|
||||
quote_asset = jh.quote_asset(order.symbol)
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import peewee
|
||||
|
||||
import jesse.helpers as jh
|
||||
from jesse.services.db import db
|
||||
|
||||
|
||||
class Ticker(peewee.Model):
|
||||
id = peewee.UUIDField(primary_key=True)
|
||||
@@ -17,17 +20,19 @@ class Ticker(peewee.Model):
|
||||
exchange = peewee.CharField()
|
||||
|
||||
class Meta:
|
||||
from jesse.services.db import database
|
||||
|
||||
database = database.db
|
||||
database = db
|
||||
indexes = ((('timestamp', 'exchange', 'symbol'), True),)
|
||||
|
||||
def __init__(self, attributes: dict = None, **kwargs) -> None:
|
||||
def __init__(self, attributes=None, **kwargs) -> None:
|
||||
peewee.Model.__init__(self, attributes=attributes, **kwargs)
|
||||
|
||||
if attributes is None:
|
||||
attributes = {}
|
||||
|
||||
for a, value in attributes.items():
|
||||
setattr(self, a, value)
|
||||
for a in attributes:
|
||||
setattr(self, a, attributes[a])
|
||||
|
||||
|
||||
if not jh.is_unit_testing():
|
||||
# create the table
|
||||
Ticker.create_table()
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import peewee
|
||||
|
||||
import jesse.helpers as jh
|
||||
from jesse.services.db import db
|
||||
|
||||
|
||||
class Trade(peewee.Model):
|
||||
id = peewee.UUIDField(primary_key=True)
|
||||
@@ -18,16 +21,19 @@ class Trade(peewee.Model):
|
||||
exchange = peewee.CharField()
|
||||
|
||||
class Meta:
|
||||
from jesse.services.db import database
|
||||
|
||||
database = database.db
|
||||
database = db
|
||||
indexes = ((('timestamp', 'exchange', 'symbol'), True),)
|
||||
|
||||
def __init__(self, attributes: dict = None, **kwargs) -> None:
|
||||
def __init__(self, attributes=None, **kwargs) -> None:
|
||||
peewee.Model.__init__(self, attributes=attributes, **kwargs)
|
||||
|
||||
if attributes is None:
|
||||
attributes = {}
|
||||
|
||||
for a, value in attributes.items():
|
||||
setattr(self, a, value)
|
||||
for a in attributes:
|
||||
setattr(self, a, attributes[a])
|
||||
|
||||
|
||||
if not jh.is_unit_testing():
|
||||
# create the table
|
||||
Trade.create_table()
|
||||
|
||||
@@ -7,6 +7,4 @@ from .Position import Position
|
||||
from .Route import Route
|
||||
from .SpotExchange import SpotExchange
|
||||
from .Ticker import Ticker
|
||||
from .Log import Log
|
||||
# from .DailyBalance import DailyBalance
|
||||
from .utils import store_candle_into_db, store_ticker_into_db, store_trade_into_db, store_orderbook_into_db
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user