Skip to content

Commit 27ab165

Browse files
authored
Feature/refactor poetry package (#249)
* 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
1 parent 4b67a46 commit 27ab165

File tree

65 files changed

+2835
-524
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+2835
-524
lines changed

.github/workflows/build.yml

Lines changed: 0 additions & 41 deletions
This file was deleted.

.github/workflows/publish.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Build and publish python package
2+
3+
on:
4+
release:
5+
types: [ published ]
6+
7+
jobs:
8+
publish-service-client-package:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: write
12+
steps:
13+
- name: Publish PyPi package
14+
uses: code-specialist/pypi-poetry-publish@v1
15+
with:
16+
ACCESS_TOKEN: ${{ secrets.REPOSITORY_ACCESS_TOKEN }}
17+
PUBLISH_REGISTRY_PASSWORD: ${{ secrets.PYPI_TOKEN }}
18+
BRANCH: "main"
19+
POETRY_VERSION: "1.7.1"
20+
POETRY_CORE_VERSION: "1.8.1"

.github/workflows/test.yml

Lines changed: 100 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,110 @@
1-
# This workflow will install Python dependencies, run tests and lint
2-
# with a single version of Python
3-
4-
name: Tests
1+
name: test
52

63
on:
74
push:
85
branches:
96
- '*' # matches every branch that doesn't contain a '/'
107
- '*/*' # matches every branch containing a single '/'
118
- '**' # matches every branch
12-
pull_request:
13-
branches:
14-
- '*' # matches every branch that doesn't contain a '/'
15-
- '*/*' # matches every branch containing a single '/'
16-
- '**' # matches every branch
17-
jobs:
18-
build:
9+
pull_request:
10+
branches:
11+
- '*' # matches every branch that doesn't contain a '/'
12+
- '*/*' # matches every branch containing a single '/'
13+
- '**' # matches every branch
1914

15+
jobs:
16+
linting:
2017
runs-on: ubuntu-latest
21-
2218
steps:
23-
- uses: actions/checkout@v2
24-
- name: Set up Python 3.9
25-
uses: actions/setup-python@v2
26-
with:
27-
python-version: 3.9
28-
- name: Install dependencies
29-
run: |
30-
python -m pip install --upgrade pip
31-
pip install flake8
32-
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
33-
if [ -f requirements-test.txt ]; then pip install -r requirements-test.txt; fi
34-
- name: Lint with flake8
35-
run: |
36-
# stop the build if there are Python syntax errors or undefined names
37-
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
38-
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
39-
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
40-
- name: Test
41-
run: |
42-
python -m unittest discover -s tests/
19+
#----------------------------------------------
20+
# check-out repo and set-up python
21+
#----------------------------------------------
22+
- uses: actions/checkout@v4
23+
- uses: actions/setup-python@v5
24+
#----------------------------------------------
25+
# load pip cache if cache exists
26+
#----------------------------------------------
27+
- uses: actions/cache@v3
28+
with:
29+
path: ~/.cache/pip
30+
key: ${{ runner.os }}-pip
31+
restore-keys: ${{ runner.os }}-pip
32+
#----------------------------------------------
33+
# install and run linters
34+
#----------------------------------------------
35+
- run: python -m pip install black flake8 isort
36+
- run: |
37+
flake8 ./investing_algorithm_framework
38+
test:
39+
needs: linting
40+
strategy:
41+
fail-fast: true
42+
matrix:
43+
os: [ "ubuntu-latest", "macos-latest" ]
44+
python-version: [ "3.8", "3.9", "3.10", "3.11" ]
45+
runs-on: ${{ matrix.os }}
46+
steps:
47+
#----------------------------------------------
48+
# check-out repo and set-up python
49+
#----------------------------------------------
50+
- name: Check out repository
51+
uses: actions/checkout@v4
52+
- name: Set up python ${{ matrix.python-version }}
53+
id: setup-python
54+
uses: actions/setup-python@v5
55+
with:
56+
python-version: ${{ matrix.python-version }}
57+
#----------------------------------------------
58+
# ----- install distutils if needed -----
59+
#----------------------------------------------
60+
- name: Install distutils on Ubuntu
61+
if: matrix.os == 'ubuntu-latest'
62+
run: |
63+
sudo add-apt-repository ppa:deadsnakes/ppa
64+
sudo apt-get update
65+
sudo apt install python${{ matrix.python-version }}-distutils
66+
#----------------------------------------------
67+
# ----- install & configure poetry -----
68+
#----------------------------------------------
69+
- name: Install Poetry
70+
uses: snok/install-poetry@v1
71+
with:
72+
virtualenvs-create: true
73+
virtualenvs-in-project: true
74+
#----------------------------------------------
75+
# load cached venv if cache exists
76+
#----------------------------------------------
77+
- name: Load cached venv
78+
id: cached-poetry-dependencies
79+
uses: actions/cache@v3
80+
with:
81+
path: .venv
82+
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
83+
#----------------------------------------------
84+
# install dependencies if cache does not exist
85+
#----------------------------------------------
86+
- name: Install dependencies
87+
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
88+
run: |
89+
poetry install --no-interaction --no-root
90+
#----------------------------------------------
91+
# install your root project, if required
92+
#----------------------------------------------
93+
- name: Install library
94+
run: |
95+
poetry install --no-interaction
96+
#----------------------------------------------
97+
# add matrix specifics and run test suite
98+
#----------------------------------------------
99+
- name: Run tests
100+
run: |
101+
source .venv/bin/activate
102+
coverage run -m unittest discover -s tests
103+
# #----------------------------------------------
104+
# # upload coverage stats
105+
# #----------------------------------------------
106+
# - name: Upload coverage
107+
# uses: codecov/codecov-action@v3
108+
# with:
109+
# file: ./coverage.xml
110+
# fail_ci_if_error: true

examples/expirement/__init__.py

Whitespace-only changes.

examples/expirement/app.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import pathlib
2+
3+
from data_sources import bitvavo_btc_eur_ohlcv_2h, bitvavo_dot_eur_ohlcv_2h, \
4+
bitvavo_dot_eur_ticker, bitvavo_btc_eur_ticker
5+
from investing_algorithm_framework import create_app, RESOURCE_DIRECTORY
6+
from strategy import CrossOverStrategy
7+
8+
app = create_app(
9+
config={RESOURCE_DIRECTORY: pathlib.Path(__file__).parent.resolve()}
10+
)
11+
app.add_strategy(CrossOverStrategy)
12+
app.add_market_data_source(bitvavo_btc_eur_ohlcv_2h)
13+
app.add_market_data_source(bitvavo_dot_eur_ohlcv_2h)
14+
app.add_market_data_source(bitvavo_btc_eur_ticker)
15+
app.add_market_data_source(bitvavo_dot_eur_ticker)

examples/expirement/data_sources.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from investing_algorithm_framework import CCXTOHLCVMarketDataSource, \
2+
CCXTTickerMarketDataSource
3+
4+
5+
bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource(
6+
identifier="BTC/EUR-ohlcv",
7+
market="BINANCE",
8+
symbol="BTC/EUR",
9+
timeframe="2h",
10+
window_size=200
11+
)
12+
bitvavo_dot_eur_ohlcv_2h = CCXTOHLCVMarketDataSource(
13+
identifier="DOT/EUR-ohlcv",
14+
market="BINANCE",
15+
symbol="DOT/EUR",
16+
timeframe="2h",
17+
window_size=200
18+
)
19+
bitvavo_dot_eur_ticker = CCXTTickerMarketDataSource(
20+
identifier="DOT/EUR-ticker",
21+
market="BINANCE",
22+
symbol="DOT/EUR",
23+
backtest_timeframe="2h",
24+
)
25+
bitvavo_btc_eur_ticker = CCXTTickerMarketDataSource(
26+
identifier="BTC/EUR-ticker",
27+
market="BINANCE",
28+
symbol="BTC/EUR",
29+
backtest_timeframe="2h",
30+
)

examples/expirement/experiment.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from datetime import datetime, timedelta
2+
3+
from investing_algorithm_framework import PortfolioConfiguration, \
4+
pretty_print_backtest
5+
6+
from app import app
7+
8+
9+
# Add a portfolio configuration of 400 euro initial balance
10+
app.add_portfolio_configuration(
11+
PortfolioConfiguration(
12+
market="BINANCE",
13+
trading_symbol="EUR",
14+
initial_balance=400,
15+
)
16+
)
17+
18+
if __name__ == "__main__":
19+
end_date = datetime(2023, 12, 2)
20+
start_date = end_date - timedelta(days=100)
21+
experiment_report, backtest_reports = app.experiment(
22+
start_date=start_date,
23+
end_date=end_date,
24+
pending_order_check_interval="2h",
25+
strategies=[]
26+
)
27+
pretty_print_expirement(expirement_report)

examples/expirement/production.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from investing_algorithm_framework import MarketCredential
2+
from app import app
3+
4+
# Configure your market credentials here
5+
bitvavo_market_credential = MarketCredential(
6+
api_key="",
7+
secret_key="",
8+
market="BITVAVO"
9+
)
10+
app.add_market_credential(bitvavo_market_credential)
11+
12+
if __name__ == "__main__":
13+
app.run()

examples/expirement/strategy.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import tulipy as ti
2+
3+
from investing_algorithm_framework import TimeUnit, TradingStrategy, \
4+
Algorithm, OrderSide
5+
6+
"""
7+
This strategy is based on the golden cross strategy. It will buy when the
8+
fast moving average crosses the slow moving average from below. It will sell
9+
when the fast moving average crosses the slow moving average from above.
10+
11+
The strategy will also check if the fast moving average is above the trend
12+
moving average. If it is not above the trend moving average it will not buy.
13+
14+
It uses tulipy indicators to calculate the metrics. You need to
15+
install this library in your environment to run this strategy.
16+
You can find instructions on how to install tulipy here:
17+
https://tulipindicators.org/ or go directly to the pypi page:
18+
https://pypi.org/project/tulipy/
19+
"""
20+
# Define market data sources
21+
22+
def is_below_trend(fast_series, slow_series):
23+
return fast_series[-1] < slow_series[-1]
24+
25+
26+
def is_above_trend(fast_series, slow_series):
27+
return fast_series[-1] > slow_series[-1]
28+
29+
30+
def is_crossover(fast, slow):
31+
"""
32+
Expect df to have columns: Date, ma_<period_one>, ma_<period_two>.
33+
With the given date time it will check if the ma_<period_one> is a
34+
crossover with the ma_<period_two>
35+
"""
36+
return fast[-2] <= slow[-2] and fast[-1] > slow[-1]
37+
38+
39+
def is_crossunder(fast, slow):
40+
"""
41+
Expect df to have columns: Date, ma_<period_one>, ma_<period_two>.
42+
With the given date time it will check if the ma_<period_one> is a
43+
crossover with the ma_<period_two>
44+
"""
45+
return fast[-2] >= slow[-2] and fast[-1] < slow[-1]
46+
47+
48+
class CrossOverStrategy(TradingStrategy):
49+
time_unit = TimeUnit.HOUR
50+
interval = 2
51+
market_data_sources = [
52+
"BTC/EUR-ohlcv",
53+
"DOT/EUR-ohlcv",
54+
"BTC/EUR-ticker",
55+
"DOT/EUR-ticker"
56+
]
57+
symbols = ["BTC/EUR", "DOT/EUR"]
58+
59+
def apply_strategy(self, algorithm: Algorithm, market_data):
60+
61+
for symbol in self.symbols:
62+
target_symbol = symbol.split('/')[0]
63+
64+
if algorithm.has_open_orders(target_symbol):
65+
continue
66+
67+
df = market_data[f"{symbol}-ohlcv"]
68+
ticker_data = market_data[f"{symbol}-ticker"]
69+
fast = ti.sma(df['Close'].to_numpy(), 9)
70+
slow = ti.sma(df['Close'].to_numpy(), 50)
71+
trend = ti.sma(df['Close'].to_numpy(), 100)
72+
price = ticker_data['bid']
73+
74+
if not algorithm.has_position(target_symbol) \
75+
and is_crossover(fast, slow)\
76+
and not is_above_trend(fast, trend):
77+
algorithm.create_limit_order(
78+
target_symbol=target_symbol,
79+
order_side=OrderSide.BUY,
80+
price=price,
81+
percentage_of_portfolio=25,
82+
precision=4,
83+
)
84+
85+
if algorithm.has_position(target_symbol) \
86+
and is_below_trend(fast, slow):
87+
open_trades = algorithm.get_open_trades(
88+
target_symbol=target_symbol
89+
)
90+
91+
for trade in open_trades:
92+
algorithm.close_trade(trade)

investing_algorithm_framework/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from investing_algorithm_framework.app import App, Algorithm
22
from .create_app import create_app
33
from investing_algorithm_framework.domain import ApiException, \
4-
TradingDataType, TradingTimeFrame, OrderType,\
4+
TradingDataType, TradingTimeFrame, OrderType, \
55
OrderStatus, OrderSide, Config, TimeUnit, TimeInterval, Order, Portfolio, \
66
Position, TimeFrame, BACKTESTING_INDEX_DATETIME, MarketCredential, \
77
PortfolioConfiguration, RESOURCE_DIRECTORY, pretty_print_backtest, \

0 commit comments

Comments
 (0)