Skip to content

Commit 58732e0

Browse files
committed
Merge branch 'main' into 297-save-strategies-as-part-of-the-backtest-report
2 parents 7fd447e + 83fe0fa commit 58732e0

File tree

65 files changed

+3360
-3957
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

+3360
-3957
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ bumpversion.egg-info/
148148
*.sqlite3
149149

150150
**/backtest_data/*
151-
*/backtest_reports/
151+
**/backtest_reports/
152152
**/backtest_reports/*
153+
**/databases/
153154
.vscode/

README.md

+21-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
<br/>
2+
<div align="center">
3+
<h1><a href="https://investing-algorithm-framework.com" target="_blank">Investing Algorithm Framework</a></h4>
4+
</div>
5+
<br/>
6+
7+
<div align="center">
8+
<b>Rapidly build and deploy quantitative strategies and trading bots</b>
9+
</div>
10+
<br/>
11+
12+
<p align="center">
13+
<a target="_blank" href="https://investing-algorithm-framework.com">View Docs</a>
14+
<a href="https://investing-algorithm-framework.com/Getting%20Started/installation)">Getting Started</a>
15+
</p>
16+
17+
---
18+
119
<a href=https://investing-algorithm-framework.com><img src="https://img.shields.io/badge/docs-website-brightgreen"></a>
220
[![Build](https://github.com/coding-kitties/investing-algorithm-framework/actions/workflows/publish.yml/badge.svg)](https://github.com/coding-kitties/investing-algorithm-framework/actions/workflows/publish.yml)
321
[![Tests](https://github.com/coding-kitties/investing-algorithm-framework/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/coding-kitties/investing-algorithm-framework/actions/workflows/test.yml)
@@ -6,10 +24,6 @@
624
<a href="https://www.reddit.com/r/InvestingBots/"><img src="https://img.shields.io/reddit/subreddit-subscribers/investingbots?style=social"></a> <br/>
725
[![GitHub stars](https://img.shields.io/github/stars/coding-kitties/investing-algorithm-framework.svg?style=social&label=Star&maxAge=1)](https://github.com/SeaQL/sea-orm/stargazers/) If you like what we do, consider starring, sharing and contributing!
826

9-
# [Investing Algorithm Framework](https://github.com/coding-kitties/investing-algorithm-framework)
10-
11-
The Investing Algorithm Framework is a Python framework that enables swift and elegant development of trading bots.
12-
1327
## Sponsors
1428

1529
<a href="https://www.finterion.com/" target="_blank">
@@ -22,7 +36,7 @@ The Investing Algorithm Framework is a Python framework that enables swift and e
2236

2337
## Features and planned features:
2438

25-
- [x] **Based on Python 3.9+**: Windows, macOS and Linux.
39+
- [x] **Based on Python 3.10+**: Windows, macOS and Linux.
2640
- [x] **Documentation**: [Documentation](https://investing-algorithm-framework.com)
2741
- [x] **Persistence of portfolios, orders, positions and trades**: Persistence is achieved through sqlite.
2842
- [x] **Limit orders**: Create limit orders for buying and selling.
@@ -33,7 +47,7 @@ The Investing Algorithm Framework is a Python framework that enables swift and e
3347
- [x] **Live trading**: Live trading.
3448
- [x] **Backtesting and performance analysis reports** [example](./examples/backtest_example)
3549
- [x] **Backtesting multiple algorithms with different backtest date ranges** [example](./examples/backtests_example)
36-
- [x] **Backtest comparison and experiments**: Compare multiple backtests and run experiments.
50+
- [x] **Backtesting and results evaluation**: Compare multiple backtests and run experiments. Save and load backtests. Save strategies as part of the backtest. [docs](https://investing-algorithm-framework.com/Getting%20Started/backtesting)
3751
- [x] **Order execution**: Currently support for a wide range of crypto exchanges through [ccxt](https://github.com/ccxt/ccxt) (Support for traditional asset brokers is planned).
3852
- [x] **Web API**: Rest API for interacting with your deployed trading bot
3953
- [x] **PyIndicators**: Works natively with [PyIndicators](https://github.com/coding-kitties/PyIndicators) for technical analysis on your Pandas and Polars dataframes.
@@ -362,4 +376,4 @@ You can pick up a task by assigning yourself to it.
362376
**Note** before starting any major new feature work, *please open an issue describing what you are planning to do*.
363377
This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it.
364378

365-
**Important:** Always create your feature or hotfix against the `develop` branch, not `main`.
379+
**Important:** Always create your feature or hotfix against the `develop` branch, not `main`.

__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__='v6.1.0'
1+
__version__='v6.2.0'

examples/backtest_example/run_backtest.py

+98-76
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
from datetime import datetime
33
import logging.config
44
from datetime import datetime, timedelta
5-
6-
75
from investing_algorithm_framework import (
86
CCXTOHLCVMarketDataSource, CCXTTickerMarketDataSource, PortfolioConfiguration, create_app, pretty_print_backtest, BacktestDateRange, TimeUnit, TradingStrategy, OrderSide, DEFAULT_LOGGING_CONFIG, Context
97
)
108

11-
import tulipy as ti
9+
from pyindicators import ema, is_crossover, is_above, is_below, is_crossunder
10+
1211

1312
logging.config.dictConfig(DEFAULT_LOGGING_CONFIG)
1413

@@ -20,22 +19,22 @@
2019
The strategy will also check if the fast moving average is above the trend
2120
moving average. If it is not above the trend moving average it will not buy.
2221
23-
It uses tulipy indicators to calculate the metrics. You need to
22+
It uses pyindicators to calculate the metrics. You need to
2423
install this library in your environment to run this strategy.
2524
You can find instructions on how to install tulipy here:
26-
https://tulipindicators.org/ or go directly to the pypi page:
27-
https://pypi.org/project/tulipy/
25+
https://github.com/coding-kitties/PyIndicators or go directly
26+
to the pypi page: https://pypi.org/project/PyIndicators/
2827
"""
2928

3029
bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource(
31-
identifier="BTC/EUR-ohlcv",
30+
identifier="BTC/EUR-ohlcv-2h",
3231
market="BINANCE",
3332
symbol="BTC/EUR",
3433
time_frame="2h",
3534
window_size=200
3635
)
3736
bitvavo_dot_eur_ohlcv_2h = CCXTOHLCVMarketDataSource(
38-
identifier="DOT/EUR-ohlcv",
37+
identifier="DOT/EUR-ohlch-2h",
3938
market="BINANCE",
4039
symbol="DOT/EUR",
4140
time_frame="2h",
@@ -54,68 +53,73 @@
5453
backtest_time_frame="2h",
5554
)
5655

57-
58-
def is_below_trend(fast_series, slow_series):
59-
return fast_series[-1] < slow_series[-1]
60-
61-
62-
def is_above_trend(fast_series, slow_series):
63-
return fast_series[-1] > slow_series[-1]
64-
65-
66-
def is_crossover(fast, slow):
67-
"""
68-
Expect df to have columns: Date, ma_<period_one>, ma_<period_two>.
69-
With the given date time it will check if the ma_<period_one> is a
70-
crossover with the ma_<period_two>
71-
"""
72-
return fast[-2] <= slow[-2] and fast[-1] > slow[-1]
73-
74-
75-
def is_crossunder(fast, slow):
56+
class CrossOverStrategy(TradingStrategy):
7657
"""
77-
Expect df to have columns: Date, ma_<period_one>, ma_<period_two>.
78-
With the given date time it will check if the ma_<period_one> is a
79-
crossover with the ma_<period_two>
58+
A simple trading strategy that uses EMA crossovers to generate buy and
59+
sell signals. The strategy uses a 50-period EMA and a 100-period EMA
60+
to detect golden and death crosses. It also uses a 200-period EMA to
61+
determine the overall trend direction. The strategy trades BTC/EUR
62+
on a 2-hour timeframe. The strategy is designed to be used with the
63+
Investing Algorithm Framework and uses the PyIndicators library
64+
to calculate the EMAs and crossover signals.
65+
66+
The strategy uses a trailing stop loss and take profit to manage
67+
risk. The stop loss is set to 5% below the entry price and the
68+
take profit is set to 10% above the entry price. The stop loss and
69+
take profit are both trailing, meaning that they will move up
70+
with the price when the price goes up.
8071
"""
81-
return fast[-2] >= slow[-2] and fast[-1] < slow[-1]
82-
83-
84-
class CrossOverStrategy(TradingStrategy):
8572
time_unit = TimeUnit.HOUR
8673
interval = 2
87-
market_data_sources = [
88-
bitvavo_dot_eur_ticker,
89-
bitvavo_btc_eur_ticker,
90-
bitvavo_dot_eur_ohlcv_2h,
91-
bitvavo_btc_eur_ohlcv_2h
92-
]
93-
symbols = ["BTC/EUR", "DOT/EUR"]
94-
fast = 21
95-
slow = 75
96-
trend = 150
97-
stop_loss_percentage = 7
74+
symbol_pairs = ["BTC/EUR"]
75+
market_data_sources = [bitvavo_btc_eur_ohlcv_2h, bitvavo_btc_eur_ticker]
76+
fast = 50
77+
slow = 100
78+
trend = 200
79+
stop_loss_percentage = 2
80+
stop_loss_sell_size = 50
81+
take_profit_percentage = 8
82+
take_profit_sell_size = 50
9883

9984
def apply_strategy(self, context: Context, market_data):
10085

101-
for symbol in self.symbols:
102-
target_symbol = symbol.split('/')[0]
86+
for pair in self.symbol_pairs:
87+
symbol = pair.split('/')[0]
10388

104-
if context.has_open_orders(target_symbol):
89+
# Don't trade if there are open orders for the symbol
90+
# This is important to avoid placing new orders while there are
91+
# existing orders that are not yet filled
92+
if context.has_open_orders(symbol):
10593
continue
10694

107-
df = market_data[f"{symbol}-ohlcv"]
108-
ticker_data = market_data[f"{symbol}-ticker"]
109-
fast = ti.sma(df['Close'].to_numpy(), self.fast)
110-
slow = ti.sma(df['Close'].to_numpy(), self.slow)
111-
trend = ti.sma(df['Close'].to_numpy(), self.trend)
112-
price = ticker_data["bid"]
113-
114-
if not context.has_position(target_symbol) \
115-
and is_crossover(fast, slow) \
116-
and is_above_trend(fast, trend):
95+
ohlvc_data = market_data[f"{pair}-ohlcv-2h"]
96+
# ticker_data = market_data[f"{symbol}-ticker"]
97+
# Add fast, slow, and trend EMAs to the data
98+
ohlvc_data = ema(
99+
ohlvc_data,
100+
source_column="Close",
101+
period=self.fast,
102+
result_column=f"ema_{self.fast}"
103+
)
104+
ohlvc_data = ema(
105+
ohlvc_data,
106+
source_column="Close",
107+
period=self.slow,
108+
result_column=f"ema_{self.slow}"
109+
)
110+
ohlvc_data = ema(
111+
ohlvc_data,
112+
source_column="Close",
113+
period=self.trend,
114+
result_column=f"ema_{self.trend}"
115+
)
116+
117+
price = ohlvc_data["Close"][-1]
118+
119+
if not context.has_position(symbol) \
120+
and self._is_buy_signal(ohlvc_data):
117121
order = context.create_limit_order(
118-
target_symbol=target_symbol,
122+
target_symbol=symbol,
119123
order_side=OrderSide.BUY,
120124
price=price,
121125
percentage_of_portfolio=25,
@@ -125,31 +129,49 @@ def apply_strategy(self, context: Context, market_data):
125129
context.add_stop_loss(
126130
trade=trade,
127131
trade_risk_type="trailing",
128-
percentage=5,
129-
sell_percentage=50
132+
percentage=self.stop_loss_percentage,
133+
sell_percentage=self.stop_loss_sell_size
130134
)
131135
context.add_take_profit(
132136
trade=trade,
133-
percentage=5,
137+
percentage=self.take_profit_percentage,
134138
trade_risk_type="trailing",
135-
sell_percentage=50
136-
)
137-
context.add_take_profit(
138-
trade=trade,
139-
percentage=10,
140-
trade_risk_type="trailing",
141-
sell_percentage=20
139+
sell_percentage=self.take_profit_sell_size
142140
)
143141

144-
if context.has_position(target_symbol) \
145-
and is_below_trend(fast, slow):
142+
if context.has_position(symbol) \
143+
and self._is_sell_signal(ohlvc_data):
146144
open_trades = context.get_open_trades(
147-
target_symbol=target_symbol
145+
target_symbol=symbol
148146
)
149147

150148
for trade in open_trades:
151149
context.close_trade(trade)
152150

151+
def _is_sell_signal(self, data):
152+
return is_crossunder(
153+
data,
154+
first_column=f"ema_{self.fast}",
155+
second_column=f"ema_{self.slow}",
156+
number_of_data_points=2
157+
) and is_below(
158+
data,
159+
first_column=f"ema_{self.fast}",
160+
second_column=f"ema_{self.trend}",
161+
)
162+
163+
def _is_buy_signal(self, data):
164+
return is_crossover(
165+
data=data,
166+
first_column=f"ema_{self.fast}",
167+
second_column=f"ema_{self.slow}",
168+
number_of_data_points=2
169+
) and is_above(
170+
data=data,
171+
first_column=f"ema_{self.fast}",
172+
second_column=f"ema_{self.trend}",
173+
)
174+
153175

154176
app = create_app(name="GoldenCrossStrategy")
155177
app.add_strategy(CrossOverStrategy)
@@ -161,9 +183,7 @@ def apply_strategy(self, context: Context, market_data):
161183
# Add a portfolio configuration of 400 euro initial balance
162184
app.add_portfolio_configuration(
163185
PortfolioConfiguration(
164-
market="BINANCE",
165-
trading_symbol="EUR",
166-
initial_balance=400,
186+
market="BINANCE", trading_symbol="EUR", initial_balance=400,
167187
)
168188
)
169189

@@ -175,7 +195,9 @@ def apply_strategy(self, context: Context, market_data):
175195
end_date=end_date
176196
)
177197
start_time = time.time()
178-
backtest_report = app.run_backtest(backtest_date_range=date_range)
198+
backtest_report = app.run_backtest(
199+
backtest_date_range=date_range, save_in_memory_strategies=True
200+
)
179201
pretty_print_backtest(backtest_report)
180202
end_time = time.time()
181203
print(f"Execution Time: {end_time - start_time:.6f} seconds")

examples/bitvavo_trading_bot.py

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
# Define your market credential for bitvavo, keys are read from .env file
2424
bitvavo_market_credential = MarketCredential(
2525
market="bitvavo",
26+
api_key="your_api_key",
27+
secret_key="your_secret_key"
2628
)
2729
# Define your market data sources for coinbase
2830
bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource(

examples/deployments/azure_function/.funcignore

-1
This file was deleted.

examples/deployments/azure_function/.gitignore

-48
This file was deleted.

0 commit comments

Comments
 (0)