-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
49 changed files
with
2,776 additions
and
491 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,42 +1,99 @@ | ||
# 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", "3.12" ] | ||
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 & 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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.