Skip to content

Commit

Permalink
Feature/refactor poetry package (#249)
Browse files Browse the repository at this point in the history
* Fix flake8 warnings

* Fix tests

* Add setuptools to test sequence

* Add setuptools to test sequence

* Add setuptools to test sequence

* Add setuptools to test sequence

* Add setuptools to test sequence

* Add distutils step

* Add distutils step

* Add distutils step

* Remove setup tools

* Remove python 3.12
  • Loading branch information
MDUYN committed Feb 22, 2024
1 parent 4b67a46 commit 27ab165
Show file tree
Hide file tree
Showing 65 changed files with 2,835 additions and 524 deletions.
41 changes: 0 additions & 41 deletions .github/workflows/build.yml

This file was deleted.

20 changes: 20 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Build and publish python package

on:
release:
types: [ published ]

jobs:
publish-service-client-package:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Publish PyPi package
uses: code-specialist/pypi-poetry-publish@v1
with:
ACCESS_TOKEN: ${{ secrets.REPOSITORY_ACCESS_TOKEN }}
PUBLISH_REGISTRY_PASSWORD: ${{ secrets.PYPI_TOKEN }}
BRANCH: "main"
POETRY_VERSION: "1.7.1"
POETRY_CORE_VERSION: "1.8.1"
132 changes: 100 additions & 32 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,42 +1,110 @@
# This workflow will install Python dependencies, run tests and lint
# with a single version of Python

name: Tests
name: test

on:
push:
branches:
- '*' # matches every branch that doesn't contain a '/'
- '*/*' # matches every branch containing a single '/'
- '**' # matches every branch
pull_request:
branches:
- '*' # matches every branch that doesn't contain a '/'
- '*/*' # matches every branch containing a single '/'
- '**' # matches every branch
jobs:
build:
pull_request:
branches:
- '*' # matches every branch that doesn't contain a '/'
- '*/*' # matches every branch containing a single '/'
- '**' # matches every branch

jobs:
linting:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ -f requirements-test.txt ]; then pip install -r requirements-test.txt; fi
- 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
run: |
python -m unittest discover -s tests/
#----------------------------------------------
# check-out repo and set-up python
#----------------------------------------------
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
#----------------------------------------------
# load pip cache if cache exists
#----------------------------------------------
- uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip
restore-keys: ${{ runner.os }}-pip
#----------------------------------------------
# install and run linters
#----------------------------------------------
- run: python -m pip install black flake8 isort
- run: |
flake8 ./investing_algorithm_framework
test:
needs: linting
strategy:
fail-fast: true
matrix:
os: [ "ubuntu-latest", "macos-latest" ]
python-version: [ "3.8", "3.9", "3.10", "3.11" ]
runs-on: ${{ matrix.os }}
steps:
#----------------------------------------------
# check-out repo and set-up python
#----------------------------------------------
- name: Check out repository
uses: actions/checkout@v4
- name: Set up python ${{ matrix.python-version }}
id: setup-python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
#----------------------------------------------
# ----- install distutils if needed -----
#----------------------------------------------
- name: Install distutils on Ubuntu
if: matrix.os == 'ubuntu-latest'
run: |
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update
sudo apt install python${{ matrix.python-version }}-distutils
#----------------------------------------------
# ----- install & configure poetry -----
#----------------------------------------------
- name: Install Poetry
uses: snok/install-poetry@v1
with:
virtualenvs-create: true
virtualenvs-in-project: true
#----------------------------------------------
# load cached venv if cache exists
#----------------------------------------------
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v3
with:
path: .venv
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
#----------------------------------------------
# install dependencies if cache does not exist
#----------------------------------------------
- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: |
poetry install --no-interaction --no-root
#----------------------------------------------
# install your root project, if required
#----------------------------------------------
- name: Install library
run: |
poetry install --no-interaction
#----------------------------------------------
# add matrix specifics and run test suite
#----------------------------------------------
- name: Run tests
run: |
source .venv/bin/activate
coverage run -m unittest discover -s tests
# #----------------------------------------------
# # upload coverage stats
# #----------------------------------------------
# - name: Upload coverage
# uses: codecov/codecov-action@v3
# with:
# file: ./coverage.xml
# fail_ci_if_error: true
Empty file added examples/expirement/__init__.py
Empty file.
15 changes: 15 additions & 0 deletions examples/expirement/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import pathlib

from data_sources import bitvavo_btc_eur_ohlcv_2h, bitvavo_dot_eur_ohlcv_2h, \
bitvavo_dot_eur_ticker, bitvavo_btc_eur_ticker
from investing_algorithm_framework import create_app, RESOURCE_DIRECTORY
from strategy import CrossOverStrategy

app = create_app(
config={RESOURCE_DIRECTORY: pathlib.Path(__file__).parent.resolve()}
)
app.add_strategy(CrossOverStrategy)
app.add_market_data_source(bitvavo_btc_eur_ohlcv_2h)
app.add_market_data_source(bitvavo_dot_eur_ohlcv_2h)
app.add_market_data_source(bitvavo_btc_eur_ticker)
app.add_market_data_source(bitvavo_dot_eur_ticker)
30 changes: 30 additions & 0 deletions examples/expirement/data_sources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from investing_algorithm_framework import CCXTOHLCVMarketDataSource, \
CCXTTickerMarketDataSource


bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource(
identifier="BTC/EUR-ohlcv",
market="BINANCE",
symbol="BTC/EUR",
timeframe="2h",
window_size=200
)
bitvavo_dot_eur_ohlcv_2h = CCXTOHLCVMarketDataSource(
identifier="DOT/EUR-ohlcv",
market="BINANCE",
symbol="DOT/EUR",
timeframe="2h",
window_size=200
)
bitvavo_dot_eur_ticker = CCXTTickerMarketDataSource(
identifier="DOT/EUR-ticker",
market="BINANCE",
symbol="DOT/EUR",
backtest_timeframe="2h",
)
bitvavo_btc_eur_ticker = CCXTTickerMarketDataSource(
identifier="BTC/EUR-ticker",
market="BINANCE",
symbol="BTC/EUR",
backtest_timeframe="2h",
)
27 changes: 27 additions & 0 deletions examples/expirement/experiment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from datetime import datetime, timedelta

from investing_algorithm_framework import PortfolioConfiguration, \
pretty_print_backtest

from app import app


# Add a portfolio configuration of 400 euro initial balance
app.add_portfolio_configuration(
PortfolioConfiguration(
market="BINANCE",
trading_symbol="EUR",
initial_balance=400,
)
)

if __name__ == "__main__":
end_date = datetime(2023, 12, 2)
start_date = end_date - timedelta(days=100)
experiment_report, backtest_reports = app.experiment(
start_date=start_date,
end_date=end_date,
pending_order_check_interval="2h",
strategies=[]
)
pretty_print_expirement(expirement_report)
13 changes: 13 additions & 0 deletions examples/expirement/production.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from investing_algorithm_framework import MarketCredential
from app import app

# Configure your market credentials here
bitvavo_market_credential = MarketCredential(
api_key="",
secret_key="",
market="BITVAVO"
)
app.add_market_credential(bitvavo_market_credential)

if __name__ == "__main__":
app.run()
92 changes: 92 additions & 0 deletions examples/expirement/strategy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import tulipy as ti

from investing_algorithm_framework import TimeUnit, TradingStrategy, \
Algorithm, OrderSide

"""
This strategy is based on the golden cross strategy. It will buy when the
fast moving average crosses the slow moving average from below. It will sell
when the fast moving average crosses the slow moving average from above.
The strategy will also check if the fast moving average is above the trend
moving average. If it is not above the trend moving average it will not buy.
It uses tulipy indicators to calculate the metrics. You need to
install this library in your environment to run this strategy.
You can find instructions on how to install tulipy here:
https://tulipindicators.org/ or go directly to the pypi page:
https://pypi.org/project/tulipy/
"""
# Define market data sources

def is_below_trend(fast_series, slow_series):
return fast_series[-1] < slow_series[-1]


def is_above_trend(fast_series, slow_series):
return fast_series[-1] > slow_series[-1]


def is_crossover(fast, slow):
"""
Expect df to have columns: Date, ma_<period_one>, ma_<period_two>.
With the given date time it will check if the ma_<period_one> is a
crossover with the ma_<period_two>
"""
return fast[-2] <= slow[-2] and fast[-1] > slow[-1]


def is_crossunder(fast, slow):
"""
Expect df to have columns: Date, ma_<period_one>, ma_<period_two>.
With the given date time it will check if the ma_<period_one> is a
crossover with the ma_<period_two>
"""
return fast[-2] >= slow[-2] and fast[-1] < slow[-1]


class CrossOverStrategy(TradingStrategy):
time_unit = TimeUnit.HOUR
interval = 2
market_data_sources = [
"BTC/EUR-ohlcv",
"DOT/EUR-ohlcv",
"BTC/EUR-ticker",
"DOT/EUR-ticker"
]
symbols = ["BTC/EUR", "DOT/EUR"]

def apply_strategy(self, algorithm: Algorithm, market_data):

for symbol in self.symbols:
target_symbol = symbol.split('/')[0]

if algorithm.has_open_orders(target_symbol):
continue

df = market_data[f"{symbol}-ohlcv"]
ticker_data = market_data[f"{symbol}-ticker"]
fast = ti.sma(df['Close'].to_numpy(), 9)
slow = ti.sma(df['Close'].to_numpy(), 50)
trend = ti.sma(df['Close'].to_numpy(), 100)
price = ticker_data['bid']

if not algorithm.has_position(target_symbol) \
and is_crossover(fast, slow)\
and not is_above_trend(fast, trend):
algorithm.create_limit_order(
target_symbol=target_symbol,
order_side=OrderSide.BUY,
price=price,
percentage_of_portfolio=25,
precision=4,
)

if algorithm.has_position(target_symbol) \
and is_below_trend(fast, slow):
open_trades = algorithm.get_open_trades(
target_symbol=target_symbol
)

for trade in open_trades:
algorithm.close_trade(trade)
2 changes: 1 addition & 1 deletion investing_algorithm_framework/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from investing_algorithm_framework.app import App, Algorithm
from .create_app import create_app
from investing_algorithm_framework.domain import ApiException, \
TradingDataType, TradingTimeFrame, OrderType,\
TradingDataType, TradingTimeFrame, OrderType, \
OrderStatus, OrderSide, Config, TimeUnit, TimeInterval, Order, Portfolio, \
Position, TimeFrame, BACKTESTING_INDEX_DATETIME, MarketCredential, \
PortfolioConfiguration, RESOURCE_DIRECTORY, pretty_print_backtest, \
Expand Down
Loading

0 comments on commit 27ab165

Please sign in to comment.