bbstrader 0.1.5__tar.gz → 0.1.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bbstrader might be problematic. Click here for more details.
- {bbstrader-0.1.5 → bbstrader-0.1.7}/PKG-INFO +15 -7
- {bbstrader-0.1.5 → bbstrader-0.1.7}/README.md +11 -6
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader/__ini__.py +0 -1
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader/btengine/__init__.py +12 -9
- bbstrader-0.1.7/bbstrader/btengine/backtest.py +298 -0
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader/btengine/data.py +25 -12
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader/btengine/event.py +18 -11
- bbstrader-0.1.7/bbstrader/btengine/execution.py +143 -0
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader/btengine/performance.py +34 -1
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader/btengine/portfolio.py +24 -14
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader/btengine/strategy.py +4 -3
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader/metatrader/account.py +18 -6
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader/metatrader/rates.py +35 -12
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader/metatrader/trade.py +54 -38
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader/metatrader/utils.py +3 -2
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader/models/risk.py +39 -2
- bbstrader-0.1.7/bbstrader/trading/__init__.py +11 -0
- bbstrader-0.1.7/bbstrader/trading/execution.py +386 -0
- bbstrader-0.1.7/bbstrader/trading/strategies.py +838 -0
- bbstrader-0.1.7/bbstrader/tseries.py +1176 -0
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader.egg-info/PKG-INFO +15 -7
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader.egg-info/SOURCES.txt +1 -3
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader.egg-info/requires.txt +3 -0
- {bbstrader-0.1.5 → bbstrader-0.1.7}/setup.py +3 -2
- bbstrader-0.1.5/bbstrader/btengine/backtest.py +0 -900
- bbstrader-0.1.5/bbstrader/btengine/execution.py +0 -83
- bbstrader-0.1.5/bbstrader/strategies.py +0 -681
- bbstrader-0.1.5/bbstrader/trading/__init__.py +0 -4
- bbstrader-0.1.5/bbstrader/trading/execution.py +0 -965
- bbstrader-0.1.5/bbstrader/trading/run.py +0 -131
- bbstrader-0.1.5/bbstrader/trading/utils.py +0 -153
- bbstrader-0.1.5/bbstrader/tseries.py +0 -592
- {bbstrader-0.1.5 → bbstrader-0.1.7}/LICENSE +0 -0
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader/metatrader/__init__.py +0 -0
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader/metatrader/risk.py +0 -0
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader/models/__init__.py +0 -0
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader.egg-info/dependency_links.txt +0 -0
- {bbstrader-0.1.5 → bbstrader-0.1.7}/bbstrader.egg-info/top_level.txt +0 -0
- {bbstrader-0.1.5 → bbstrader-0.1.7}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: bbstrader
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: Simplified Investment & Trading Toolkit
|
|
5
5
|
Home-page: https://github.com/bbalouki/bbstrader
|
|
6
6
|
Download-URL: https://pypi.org/project/bbstrader/
|
|
@@ -36,6 +36,9 @@ Requires-Dist: matplotlib
|
|
|
36
36
|
Requires-Dist: filterpy
|
|
37
37
|
Requires-Dist: pytest
|
|
38
38
|
Requires-Dist: CurrencyConverter
|
|
39
|
+
Requires-Dist: tabulate
|
|
40
|
+
Requires-Dist: ipython
|
|
41
|
+
Requires-Dist: quantstats
|
|
39
42
|
Requires-Dist: Metatrader5
|
|
40
43
|
|
|
41
44
|
# Simplified Investment & Trading Toolkit
|
|
@@ -43,6 +46,7 @@ Requires-Dist: Metatrader5
|
|
|
43
46
|
|
|
44
47
|
[](https://bbstrader.readthedocs.io/en/latest/?badge=latest)
|
|
45
48
|
|
|
49
|
+
[Dcoumentation](https://bbstrader.readthedocs.io/en/latest/index.html)
|
|
46
50
|
## Overview
|
|
47
51
|
|
|
48
52
|
BBSTrader is a trading system suite developed for MetaTrader 5 (MT5) and IBKR platforms (comming soon), designed to offer a comprehensive set of tools for developping, backtesting, executing, and managing a wide array of trading strategies. With an emphasis on algorithmic and quantitative trading to provide traders with a robust platform for exploring and deploying sophisticated trading strategies.
|
|
@@ -50,7 +54,7 @@ BBSTrader is a trading system suite developed for MetaTrader 5 (MT5) and IBKR pl
|
|
|
50
54
|
`bbstrader` is comprised of several key modules, each focus on specific aspects of trading strategy development and execution:
|
|
51
55
|
|
|
52
56
|
- **Backtesting Module (btengine)** : Enables traders to rigorously test their trading strategies using historical data to evaluate performance before live deployment.
|
|
53
|
-
- **Trading Strategies Module**: A collection of predefined trading strategies, including ARIMA+GARCH models, Kalman Filters,
|
|
57
|
+
- **Trading Strategies Module**: A collection of predefined trading strategies, including ARIMA+GARCH models, Kalman Filters, and Simple Moving Averages, equipped with risk management through Hidden Markov Models.
|
|
54
58
|
- **MetaTrader5 Module (metatrader)**: Facilitates the direct execution of trading strategies on the MetaTrader 5 platform, supporting real-time trading across multiple financial instruments.
|
|
55
59
|
- **Modles Module**: Serves as a framework for implementing various types of financial models (risk managment models, Machine learing models etc).
|
|
56
60
|
- **Time serie Module (tseries)** designed for conducting advanced time series analysis in financial markets.
|
|
@@ -78,18 +82,22 @@ Then, you can install `bbstrader` using pip:
|
|
|
78
82
|
pip install bbstrader
|
|
79
83
|
```
|
|
80
84
|
|
|
81
|
-
|
|
82
85
|
## Examples
|
|
83
86
|
### Backtesting Module
|
|
84
87
|
```python
|
|
85
|
-
from bbstrader.
|
|
88
|
+
from bbstrader.trading.strategies import test_strategy
|
|
86
89
|
|
|
87
90
|
if __name__ == '__main__':
|
|
88
|
-
#
|
|
89
|
-
|
|
91
|
+
# Run backtesting for Stock Index Short Term Buy Only Strategy
|
|
92
|
+
test_strategy(strategy='sistbo')
|
|
93
|
+
|
|
90
94
|
```
|
|
91
95
|
### Backtesting Results
|
|
92
|
-

|
|
97
|
+

|
|
98
|
+

|
|
99
|
+

|
|
100
|
+

|
|
93
101
|
|
|
94
102
|
## Customization and Contribution
|
|
95
103
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
[](https://bbstrader.readthedocs.io/en/latest/?badge=latest)
|
|
5
5
|
|
|
6
|
+
[Dcoumentation](https://bbstrader.readthedocs.io/en/latest/index.html)
|
|
6
7
|
## Overview
|
|
7
8
|
|
|
8
9
|
BBSTrader is a trading system suite developed for MetaTrader 5 (MT5) and IBKR platforms (comming soon), designed to offer a comprehensive set of tools for developping, backtesting, executing, and managing a wide array of trading strategies. With an emphasis on algorithmic and quantitative trading to provide traders with a robust platform for exploring and deploying sophisticated trading strategies.
|
|
@@ -10,7 +11,7 @@ BBSTrader is a trading system suite developed for MetaTrader 5 (MT5) and IBKR pl
|
|
|
10
11
|
`bbstrader` is comprised of several key modules, each focus on specific aspects of trading strategy development and execution:
|
|
11
12
|
|
|
12
13
|
- **Backtesting Module (btengine)** : Enables traders to rigorously test their trading strategies using historical data to evaluate performance before live deployment.
|
|
13
|
-
- **Trading Strategies Module**: A collection of predefined trading strategies, including ARIMA+GARCH models, Kalman Filters,
|
|
14
|
+
- **Trading Strategies Module**: A collection of predefined trading strategies, including ARIMA+GARCH models, Kalman Filters, and Simple Moving Averages, equipped with risk management through Hidden Markov Models.
|
|
14
15
|
- **MetaTrader5 Module (metatrader)**: Facilitates the direct execution of trading strategies on the MetaTrader 5 platform, supporting real-time trading across multiple financial instruments.
|
|
15
16
|
- **Modles Module**: Serves as a framework for implementing various types of financial models (risk managment models, Machine learing models etc).
|
|
16
17
|
- **Time serie Module (tseries)** designed for conducting advanced time series analysis in financial markets.
|
|
@@ -38,18 +39,22 @@ Then, you can install `bbstrader` using pip:
|
|
|
38
39
|
pip install bbstrader
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
|
|
42
42
|
## Examples
|
|
43
43
|
### Backtesting Module
|
|
44
44
|
```python
|
|
45
|
-
from bbstrader.
|
|
45
|
+
from bbstrader.trading.strategies import test_strategy
|
|
46
46
|
|
|
47
47
|
if __name__ == '__main__':
|
|
48
|
-
#
|
|
49
|
-
|
|
48
|
+
# Run backtesting for Stock Index Short Term Buy Only Strategy
|
|
49
|
+
test_strategy(strategy='sistbo')
|
|
50
|
+
|
|
50
51
|
```
|
|
51
52
|
### Backtesting Results
|
|
52
|
-

|
|
54
|
+

|
|
55
|
+

|
|
56
|
+

|
|
57
|
+

|
|
53
58
|
|
|
54
59
|
## Customization and Contribution
|
|
55
60
|
|
|
@@ -12,7 +12,6 @@ Features
|
|
|
12
12
|
|
|
13
13
|
- **Event-Driven Architecture**: Processes market data, generates signals, executes orders, and manages portfolio updates in response to events, closely mimicking live trading environments.
|
|
14
14
|
- **Historical Market Data Support**: Utilizes historical OHLCV data from CSV files, Yahoo finance and MT5 terminal allowing for the testing of strategies over various market conditions and time frames.
|
|
15
|
-
- **Strategy Implementation Flexibility**: Abstract base classes for strategies and other components enable users to define custom trading logic and data handling processes.
|
|
16
15
|
- **Performance Metrics Calculation**: Includes tools for calculating key performance indicators, such as `Sharpe Ratio`, `Sortino Ratio`, and `drawdowns`, to evaluate the effectiveness of trading strategies.
|
|
17
16
|
- **Visualization**: Generates plots of the `equity curve`, `returns`, `drawdowns`, and other metrics for comprehensive strategy `performance analysis`.
|
|
18
17
|
|
|
@@ -22,24 +21,29 @@ Components
|
|
|
22
21
|
- **Backtest**: Orchestrates the backtesting process, managing events and invoking components.
|
|
23
22
|
- **Event**: Abstract class for events, with implementations for market data, signals, fill and order events.
|
|
24
23
|
- **DataHandler**: Abstract class for market data handling, with an implementation for `HistoricalCSVHandler`, `MT5HistoricDataHandler`, `YFHistoricDataHandler`. We will add another data handling in the future such as MacroEconomic Data, Fundamental Data, TICK Data and Real-time Data.
|
|
25
|
-
- **Strategy**: Abstract class for trading strategies, allowing for custom signal generation logic.
|
|
26
24
|
- **Portfolio**: Manages positions and calculates performance metrics, responding to market data and signals.
|
|
27
25
|
- **ExecutionHandler**: Abstract class for order execution, with a simulated execution handler provided with an implementation for `SimulatedExecutionHandler`.
|
|
28
26
|
- **Performance**: Utility functions for calculating performance metrics and visualizing strategy performance.
|
|
29
27
|
|
|
30
|
-
Examples
|
|
31
|
-
|
|
32
|
-
>>> run_backtest(test_mode=True, test_strategy='ou', test_quantity=2000)
|
|
28
|
+
Examples
|
|
29
|
+
========
|
|
33
30
|
|
|
31
|
+
>>> from bbstrader.btengine import run_backtest
|
|
32
|
+
>>> from datetime import datetime
|
|
34
33
|
>>> run_backtest(
|
|
35
34
|
... symbol_list=['AAPL', 'GOOG'],
|
|
36
35
|
... start_date=datetime(2020, 1, 1),
|
|
37
|
-
... data_handler=
|
|
38
|
-
... strategy=
|
|
39
|
-
... exc_handler=
|
|
36
|
+
... data_handler=DataHandler,
|
|
37
|
+
... strategy=Strategy,
|
|
38
|
+
... exc_handler=ExecutionHandler,
|
|
40
39
|
... initial_capital=500000.0,
|
|
41
40
|
... heartbeat=1.0
|
|
42
41
|
... )
|
|
42
|
+
|
|
43
|
+
Notes
|
|
44
|
+
=====
|
|
45
|
+
|
|
46
|
+
See `bbstrader.btengine.backtest.run_backtest` for more details on the backtesting process and its parameters.
|
|
43
47
|
"""
|
|
44
48
|
from bbstrader.btengine.data import *
|
|
45
49
|
from bbstrader.btengine.event import *
|
|
@@ -47,4 +51,3 @@ from bbstrader.btengine.execution import *
|
|
|
47
51
|
from bbstrader.btengine.performance import *
|
|
48
52
|
from bbstrader.btengine.backtest import *
|
|
49
53
|
from bbstrader.btengine.portfolio import Portfolio
|
|
50
|
-
from bbstrader.btengine.strategy import Strategy
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import pprint
|
|
2
|
+
import queue
|
|
3
|
+
import time
|
|
4
|
+
import yfinance as yf
|
|
5
|
+
from queue import Queue
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from bbstrader.btengine.data import *
|
|
8
|
+
from bbstrader.btengine.execution import *
|
|
9
|
+
from bbstrader.btengine.portfolio import Portfolio
|
|
10
|
+
from bbstrader.btengine.event import SignalEvent
|
|
11
|
+
from bbstrader.btengine.strategy import Strategy
|
|
12
|
+
from typing import Literal, Optional, List
|
|
13
|
+
from tabulate import tabulate
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"Backtest",
|
|
17
|
+
"BacktestEngine",
|
|
18
|
+
"run_backtest"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
class Backtest(object):
|
|
22
|
+
"""
|
|
23
|
+
The `Backtest()` object encapsulates the event-handling logic and essentially
|
|
24
|
+
ties together all of the other classes.
|
|
25
|
+
|
|
26
|
+
The Backtest object is designed to carry out a nested while-loop event-driven system
|
|
27
|
+
in order to handle the events placed on the `Event` Queue object.
|
|
28
|
+
The outer while-loop is known as the "heartbeat loop" and decides the temporal resolution of
|
|
29
|
+
the backtesting system. In a live environment this value will be a positive number,
|
|
30
|
+
such as 600 seconds (every ten minutes). Thus the market data and positions
|
|
31
|
+
will only be updated on this timeframe.
|
|
32
|
+
|
|
33
|
+
For the backtester described here the "heartbeat" can be set to zero,
|
|
34
|
+
irrespective of the strategy frequency, since the data is already available by virtue of
|
|
35
|
+
the fact it is historical! We can run the backtest at whatever speed we like,
|
|
36
|
+
since the event-driven system is agnostic to when the data became available,
|
|
37
|
+
so long as it has an associated timestamp.
|
|
38
|
+
|
|
39
|
+
The inner while-loop actually processes the signals and sends them to the correct
|
|
40
|
+
component depending upon the event type. Thus the Event Queue is continually being
|
|
41
|
+
populated and depopulated with events. This is what it means for a system to be event-driven.
|
|
42
|
+
|
|
43
|
+
The initialisation of the Backtest object requires the full `symbol list` of traded symbols,
|
|
44
|
+
the `initial capital`, the `heartbeat` time in milliseconds, the `start datetime` stamp
|
|
45
|
+
of the backtest as well as the `DataHandler`, `ExecutionHandler`, `Strategy` objects
|
|
46
|
+
and additionnal `kwargs` based on the `ExecutionHandler`, the `DataHandler`, and the `Strategy` used.
|
|
47
|
+
|
|
48
|
+
A Queue is used to hold the events. The signals, orders and fills are counted.
|
|
49
|
+
For a `MarketEvent`, the `Strategy` object is told to recalculate new signals,
|
|
50
|
+
while the `Portfolio` object is told to reindex the time. If a `SignalEvent`
|
|
51
|
+
object is received the `Portfolio` is told to handle the new signal and convert it into a
|
|
52
|
+
set of `OrderEvents`, if appropriate. If an `OrderEvent` is received the `ExecutionHandler`
|
|
53
|
+
is sent the order to be transmitted to the broker (if in a real trading setting).
|
|
54
|
+
Finally, if a `FillEvent` is received, the Portfolio will update itself to be aware of
|
|
55
|
+
the new positions.
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
symbol_list: List[str],
|
|
62
|
+
initial_capital: float,
|
|
63
|
+
heartbeat: float,
|
|
64
|
+
start_date: datetime,
|
|
65
|
+
data_handler: DataHandler,
|
|
66
|
+
execution_handler: ExecutionHandler,
|
|
67
|
+
strategy: Strategy,
|
|
68
|
+
/,
|
|
69
|
+
**kwargs
|
|
70
|
+
):
|
|
71
|
+
"""
|
|
72
|
+
Initialises the backtest.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
symbol_list (List[str]): The list of symbol strings.
|
|
76
|
+
intial_capital (float): The starting capital for the portfolio.
|
|
77
|
+
heartbeat (float): Backtest "heartbeat" in seconds
|
|
78
|
+
start_date (datetime): The start datetime of the strategy.
|
|
79
|
+
data_handler (DataHandler) : Handles the market data feed.
|
|
80
|
+
execution_handler (ExecutionHandler) : Handles the orders/fills for trades.
|
|
81
|
+
strategy (Strategy): Generates signals based on market data.
|
|
82
|
+
kwargs : Additional parameters based on the `ExecutionHandler`,
|
|
83
|
+
the `DataHandler`, the `Strategy` used and the `Portfolio`.
|
|
84
|
+
"""
|
|
85
|
+
self.symbol_list = symbol_list
|
|
86
|
+
self.initial_capital = initial_capital
|
|
87
|
+
self.heartbeat = heartbeat
|
|
88
|
+
self.start_date = start_date
|
|
89
|
+
|
|
90
|
+
self.dh_cls = data_handler
|
|
91
|
+
self.eh_cls = execution_handler
|
|
92
|
+
self.strategy_cls = strategy
|
|
93
|
+
self.kwargs = kwargs
|
|
94
|
+
|
|
95
|
+
self.events = queue.Queue()
|
|
96
|
+
|
|
97
|
+
self.signals = 0
|
|
98
|
+
self.orders = 0
|
|
99
|
+
self.fills = 0
|
|
100
|
+
|
|
101
|
+
self._generate_trading_instances()
|
|
102
|
+
|
|
103
|
+
def _generate_trading_instances(self):
|
|
104
|
+
"""
|
|
105
|
+
Generates the trading instance objects from
|
|
106
|
+
their class types.
|
|
107
|
+
"""
|
|
108
|
+
print(
|
|
109
|
+
f"\nStarting Backtest on {self.symbol_list} "
|
|
110
|
+
f"with ${self.initial_capital} Initial Capital\n"
|
|
111
|
+
)
|
|
112
|
+
self.data_handler: DataHandler = self.dh_cls(
|
|
113
|
+
self.events, self.symbol_list, **self.kwargs
|
|
114
|
+
)
|
|
115
|
+
self.strategy: Strategy = self.strategy_cls(
|
|
116
|
+
bars=self.data_handler, events=self.events, **self.kwargs
|
|
117
|
+
)
|
|
118
|
+
self.portfolio = Portfolio(
|
|
119
|
+
self.data_handler,
|
|
120
|
+
self.events,
|
|
121
|
+
self.start_date,
|
|
122
|
+
self.initial_capital, **self.kwargs
|
|
123
|
+
)
|
|
124
|
+
self.execution_handler: ExecutionHandler = self.eh_cls(
|
|
125
|
+
self.events, **self.kwargs)
|
|
126
|
+
|
|
127
|
+
def _run_backtest(self):
|
|
128
|
+
"""
|
|
129
|
+
Executes the backtest.
|
|
130
|
+
"""
|
|
131
|
+
i = 0
|
|
132
|
+
while True:
|
|
133
|
+
i += 1
|
|
134
|
+
print(i)
|
|
135
|
+
# Update the market bars
|
|
136
|
+
if self.data_handler.continue_backtest == True:
|
|
137
|
+
self.data_handler.update_bars()
|
|
138
|
+
else:
|
|
139
|
+
break
|
|
140
|
+
|
|
141
|
+
# Handle the events
|
|
142
|
+
while True:
|
|
143
|
+
try:
|
|
144
|
+
event = self.events.get(False)
|
|
145
|
+
except queue.Empty:
|
|
146
|
+
break
|
|
147
|
+
else:
|
|
148
|
+
if event is not None:
|
|
149
|
+
if event.type == 'MARKET':
|
|
150
|
+
self.strategy.calculate_signals(event)
|
|
151
|
+
self.portfolio.update_timeindex(event)
|
|
152
|
+
|
|
153
|
+
elif event.type == 'SIGNAL':
|
|
154
|
+
self.signals += 1
|
|
155
|
+
self.portfolio.update_signal(event)
|
|
156
|
+
|
|
157
|
+
elif event.type == 'ORDER':
|
|
158
|
+
self.orders += 1
|
|
159
|
+
self.execution_handler.execute_order(event)
|
|
160
|
+
|
|
161
|
+
elif event.type == 'FILL':
|
|
162
|
+
self.fills += 1
|
|
163
|
+
self.portfolio.update_fill(event)
|
|
164
|
+
|
|
165
|
+
time.sleep(self.heartbeat)
|
|
166
|
+
|
|
167
|
+
def _output_performance(self):
|
|
168
|
+
"""
|
|
169
|
+
Outputs the strategy performance from the backtest.
|
|
170
|
+
"""
|
|
171
|
+
self.portfolio.create_equity_curve_dataframe()
|
|
172
|
+
|
|
173
|
+
print("\nCreating summary stats...")
|
|
174
|
+
stats = self.portfolio.output_summary_stats()
|
|
175
|
+
print("[======= Summary Stats =======]")
|
|
176
|
+
stat2 = {}
|
|
177
|
+
stat2['Signals'] = self.signals
|
|
178
|
+
stat2['Orders'] = self.orders
|
|
179
|
+
stat2['Fills'] = self.fills
|
|
180
|
+
stats.extend(stat2.items())
|
|
181
|
+
print(
|
|
182
|
+
tabulate(
|
|
183
|
+
stats,
|
|
184
|
+
headers=["Metric", "Value"],
|
|
185
|
+
tablefmt="outline"),
|
|
186
|
+
"\n"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
print("\nCreating equity curve...")
|
|
190
|
+
print("\n[======= EQUITY CURVE =======]")
|
|
191
|
+
print(
|
|
192
|
+
tabulate(
|
|
193
|
+
self.portfolio.equity_curve.tail(10),
|
|
194
|
+
headers="keys",
|
|
195
|
+
tablefmt="outline"),
|
|
196
|
+
"\n"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def simulate_trading(self):
|
|
200
|
+
"""
|
|
201
|
+
Simulates the backtest and outputs portfolio performance.
|
|
202
|
+
"""
|
|
203
|
+
self._run_backtest()
|
|
204
|
+
self._output_performance()
|
|
205
|
+
|
|
206
|
+
BacktestEngine = Backtest
|
|
207
|
+
|
|
208
|
+
def run_backtest(
|
|
209
|
+
symbol_list: List[str],
|
|
210
|
+
start_date: datetime,
|
|
211
|
+
data_handler: DataHandler,
|
|
212
|
+
strategy: Strategy,
|
|
213
|
+
exc_handler: Optional[ExecutionHandler] = None,
|
|
214
|
+
initial_capital: float = 100000.0,
|
|
215
|
+
heartbeat: float = 0.0,
|
|
216
|
+
**kwargs
|
|
217
|
+
):
|
|
218
|
+
"""
|
|
219
|
+
Runs a backtest simulation based on a `DataHandler`, `Strategy`, and `ExecutionHandler`.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
symbol_list (List[str]): List of symbol strings for the assets to be backtested.
|
|
223
|
+
|
|
224
|
+
start_date (datetime): Start date of the backtest.
|
|
225
|
+
|
|
226
|
+
data_handler (DataHandler): An instance of the `DataHandler` class, responsible for managing
|
|
227
|
+
and processing market data. Available options include `HistoricCSVDataHandler`,
|
|
228
|
+
`MT5HistoricDataHandler`, and `YFHistoricDataHandler`. Ensure that the `DataHandler`
|
|
229
|
+
instance is initialized before passing it to the function.
|
|
230
|
+
|
|
231
|
+
strategy (Strategy): The trading strategy to be employed during the backtest.
|
|
232
|
+
The strategy must be an instance of `Strategy` and should include the following attributes:
|
|
233
|
+
- `bars` (DataHandler): The `DataHandler` instance for the strategy.
|
|
234
|
+
- `events` (Queue): Queue instance for managing events.
|
|
235
|
+
- `symbol_list` (List[str]): List of symbols to trade.
|
|
236
|
+
- `mode` (str): 'live' or 'backtest'.
|
|
237
|
+
|
|
238
|
+
Additional parameters specific to the strategy should be passed in `**kwargs`.
|
|
239
|
+
The strategy class must implement a `calculate_signals` method to generate `SignalEvent`.
|
|
240
|
+
|
|
241
|
+
exc_handler (ExecutionHandler, optional): The execution handler for managing order executions.
|
|
242
|
+
If not provided, a `SimulatedExecutionHandler` will be used by default. This handler must
|
|
243
|
+
implement an `execute_order` method to process `OrderEvent` in the `Backtest` class.
|
|
244
|
+
|
|
245
|
+
initial_capital (float, optional): The initial capital for the portfolio in the backtest.
|
|
246
|
+
Default is 100,000.
|
|
247
|
+
|
|
248
|
+
heartbeat (float, optional): Time delay (in seconds) between iterations of the event-driven
|
|
249
|
+
backtest loop. Default is 0.0, allowing the backtest to run as fast as possible. This could
|
|
250
|
+
also be used as a time frame in live trading (e.g., 1m, 5m, 15m) with a live `DataHandler`.
|
|
251
|
+
|
|
252
|
+
**kwargs: Additional parameters passed to the `Backtest` instance, which may include strategy-specific,
|
|
253
|
+
data handler, portfolio, or execution handler options.
|
|
254
|
+
|
|
255
|
+
Notes:
|
|
256
|
+
This function generates three outputs:
|
|
257
|
+
- A performance summary saved as an HTML file.
|
|
258
|
+
- An equity curve of the portfolio saved as a CSV file.
|
|
259
|
+
- Monthly returns saved as a PNG image.
|
|
260
|
+
|
|
261
|
+
Example:
|
|
262
|
+
>>> from bbstrader.trading.strategies import StockIndexSTBOTrading
|
|
263
|
+
>>> from bbstrader.metatrader.utils import config_logger
|
|
264
|
+
>>> from bbstrader.datahandlers import MT5HistoricDataHandler
|
|
265
|
+
>>> from bbstrader.execution import MT5ExecutionHandler
|
|
266
|
+
>>> from datetime import datetime
|
|
267
|
+
>>>
|
|
268
|
+
>>> logger = config_logger('index_trade.log', console_log=True)
|
|
269
|
+
>>> symbol_list = ['[SP500]', 'GERMANY40', '[DJI30]', '[NQ100]']
|
|
270
|
+
>>> start = datetime(2010, 6, 1, 2, 0, 0)
|
|
271
|
+
>>> kwargs = {
|
|
272
|
+
... 'expected_returns': {'[NQ100]': 1.5, '[SP500]': 1.5, '[DJI30]': 1.0, 'GERMANY40': 1.0},
|
|
273
|
+
... 'quantities': {'[NQ100]': 15, '[SP500]': 30, '[DJI30]': 5, 'GERMANY40': 10},
|
|
274
|
+
... 'max_trades': {'[NQ100]': 3, '[SP500]': 3, '[DJI30]': 3, 'GERMANY40': 3},
|
|
275
|
+
... 'mt5_start': start,
|
|
276
|
+
... 'time_frame': '15m',
|
|
277
|
+
... 'strategy_name': 'SISTBO',
|
|
278
|
+
... }
|
|
279
|
+
>>> run_backtest(
|
|
280
|
+
... symbol_list=symbol_list,
|
|
281
|
+
... start_date=start,
|
|
282
|
+
... data_handler=MT5HistoricDataHandler(),
|
|
283
|
+
... strategy=StockIndexSTBOTrading(),
|
|
284
|
+
... exc_handler=MT5ExecutionHandler(),
|
|
285
|
+
... initial_capital=100000.0,
|
|
286
|
+
... heartbeat=0.0,
|
|
287
|
+
... **kwargs
|
|
288
|
+
... )
|
|
289
|
+
"""
|
|
290
|
+
if exc_handler is None:
|
|
291
|
+
execution_handler = SimulatedExecutionHandler
|
|
292
|
+
else:
|
|
293
|
+
execution_handler = exc_handler
|
|
294
|
+
engine = BacktestEngine(
|
|
295
|
+
symbol_list, initial_capital, heartbeat, start_date,
|
|
296
|
+
data_handler, execution_handler, strategy, **kwargs
|
|
297
|
+
)
|
|
298
|
+
engine.simulate_trading()
|
|
@@ -8,6 +8,7 @@ from queue import Queue
|
|
|
8
8
|
from abc import ABCMeta, abstractmethod
|
|
9
9
|
from bbstrader.metatrader.rates import Rates
|
|
10
10
|
from bbstrader.btengine.event import MarketEvent
|
|
11
|
+
from datetime import datetime
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
__all__ = [
|
|
@@ -260,7 +261,7 @@ class HistoricCSVDataHandler(BaseCSVDataHandler):
|
|
|
260
261
|
csv_dir = kwargs.get("csv_dir")
|
|
261
262
|
super().__init__(events, symbol_list, csv_dir)
|
|
262
263
|
|
|
263
|
-
|
|
264
|
+
|
|
264
265
|
class MT5HistoricDataHandler(BaseCSVDataHandler):
|
|
265
266
|
"""
|
|
266
267
|
Downloads historical data from MetaTrader 5 (MT5) and provides
|
|
@@ -281,19 +282,16 @@ class MT5HistoricDataHandler(BaseCSVDataHandler):
|
|
|
281
282
|
symbol_list (List[str]): A list of symbol strings to download data for.
|
|
282
283
|
**kwargs: Keyword arguments for data retrieval:
|
|
283
284
|
time_frame (str): MT5 time frame (e.g., 'D1' for daily).
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
If it set to `str`, it must be in 'YYYY-MM-DD' format and
|
|
287
|
-
session_duration (int | float): Number of trading hours per day.
|
|
285
|
+
mt5_start (datetime): Start date for historical data.
|
|
286
|
+
mt5_end (datetime): End date for historical data.
|
|
288
287
|
mt5_data (str): Directory for storing data (default: 'mt5_data').
|
|
289
288
|
|
|
290
289
|
Note:
|
|
291
290
|
Requires a working connection to an MT5 terminal.
|
|
292
291
|
"""
|
|
293
292
|
self.tf = kwargs.get('time_frame', 'D1')
|
|
294
|
-
self.
|
|
295
|
-
self.
|
|
296
|
-
self.sd = kwargs.get('session_duration', 6.5)
|
|
293
|
+
self.start = kwargs.get('mt5_start')
|
|
294
|
+
self.end = kwargs.get('mt5_end', datetime.now())
|
|
297
295
|
self.data_dir = kwargs.get('mt5_data', 'mt5_data')
|
|
298
296
|
self.symbol_list = symbol_list
|
|
299
297
|
csv_dir = self._download_data(self.data_dir)
|
|
@@ -304,8 +302,10 @@ class MT5HistoricDataHandler(BaseCSVDataHandler):
|
|
|
304
302
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
305
303
|
for symbol in self.symbol_list:
|
|
306
304
|
try:
|
|
307
|
-
rate = Rates(symbol, self.tf
|
|
308
|
-
data = rate.
|
|
305
|
+
rate = Rates(symbol=symbol, time_frame=self.tf)
|
|
306
|
+
data = rate.get_historical_data(
|
|
307
|
+
date_from=self.start, date_to=self.end
|
|
308
|
+
)
|
|
309
309
|
if data is None:
|
|
310
310
|
raise ValueError(f"No data found for {symbol}")
|
|
311
311
|
data.to_csv(data_dir / f'{symbol}.csv')
|
|
@@ -359,16 +359,29 @@ class YFHistoricDataHandler(BaseCSVDataHandler):
|
|
|
359
359
|
return cache_dir
|
|
360
360
|
|
|
361
361
|
|
|
362
|
-
# TODO # Get data from
|
|
362
|
+
# TODO # Get data from EODHD
|
|
363
|
+
# https://eodhd.com/
|
|
364
|
+
class EODHDHistoricDataHandler(BaseCSVDataHandler):
|
|
365
|
+
...
|
|
366
|
+
|
|
367
|
+
# TODO # Get data from FMP using Financialtoolkit API
|
|
368
|
+
# https://github.com/bbalouki/FinanceToolkit
|
|
363
369
|
class FMPHistoricDataHandler(BaseCSVDataHandler):
|
|
364
370
|
...
|
|
365
371
|
|
|
366
372
|
|
|
367
373
|
class BaseFMPDataHanler(object):
|
|
374
|
+
"""
|
|
375
|
+
This will serve as the base class for all other FMP data
|
|
376
|
+
that is not historical data and does not have an OHLC structure.
|
|
377
|
+
"""
|
|
368
378
|
...
|
|
369
379
|
|
|
370
380
|
|
|
371
381
|
class FMPFundamentalDataHandler(BaseFMPDataHanler):
|
|
372
382
|
...
|
|
373
383
|
|
|
374
|
-
# TODO Add other Handlers
|
|
384
|
+
# TODO Add other Handlers for FMP
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
# TODO Add data Handlers for Interactive Brokers
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
+
from typing import Literal
|
|
2
3
|
|
|
3
4
|
__all__ = [
|
|
4
5
|
"Event",
|
|
@@ -53,9 +54,10 @@ class SignalEvent(Event):
|
|
|
53
54
|
strategy_id: int,
|
|
54
55
|
symbol: str,
|
|
55
56
|
datetime: datetime,
|
|
56
|
-
signal_type:
|
|
57
|
+
signal_type: Literal['LONG', 'SHORT', 'EXIT'],
|
|
57
58
|
quantity: int | float = 100,
|
|
58
|
-
strength: int | float = 1.0
|
|
59
|
+
strength: int | float = 1.0,
|
|
60
|
+
price: int | float = None
|
|
59
61
|
):
|
|
60
62
|
"""
|
|
61
63
|
Initialises the SignalEvent.
|
|
@@ -66,10 +68,11 @@ class SignalEvent(Event):
|
|
|
66
68
|
|
|
67
69
|
symbol (str): The ticker symbol, e.g. 'GOOG'.
|
|
68
70
|
datetime (datetime): The timestamp at which the signal was generated.
|
|
69
|
-
signal_type (str): 'LONG' or 'SHORT'.
|
|
71
|
+
signal_type (str): 'LONG' or 'SHORT' or 'EXIT'.
|
|
70
72
|
quantity (int | float): An optional integer (or float) representing the order size.
|
|
71
73
|
strength (int | float): An adjustment factor "suggestion" used to scale
|
|
72
74
|
quantity at the portfolio level. Useful for pairs strategies.
|
|
75
|
+
price (int | float): An optional price to be used when the signal is generated.
|
|
73
76
|
"""
|
|
74
77
|
self.type = 'SIGNAL'
|
|
75
78
|
self.strategy_id = strategy_id
|
|
@@ -78,6 +81,7 @@ class SignalEvent(Event):
|
|
|
78
81
|
self.signal_type = signal_type
|
|
79
82
|
self.quantity = quantity
|
|
80
83
|
self.strength = strength
|
|
84
|
+
self.price = price
|
|
81
85
|
|
|
82
86
|
|
|
83
87
|
class OrderEvent(Event):
|
|
@@ -95,9 +99,10 @@ class OrderEvent(Event):
|
|
|
95
99
|
|
|
96
100
|
def __init__(self,
|
|
97
101
|
symbol: str,
|
|
98
|
-
order_type:
|
|
102
|
+
order_type: Literal['MKT', 'LMT'],
|
|
99
103
|
quantity: int | float,
|
|
100
|
-
direction:
|
|
104
|
+
direction: Literal['BUY', 'SELL'],
|
|
105
|
+
price: int | float = None
|
|
101
106
|
):
|
|
102
107
|
"""
|
|
103
108
|
Initialises the order type, setting whether it is
|
|
@@ -109,20 +114,22 @@ class OrderEvent(Event):
|
|
|
109
114
|
order_type (str): 'MKT' or 'LMT' for Market or Limit.
|
|
110
115
|
quantity (int | float): Non-negative number for quantity.
|
|
111
116
|
direction (str): 'BUY' or 'SELL' for long or short.
|
|
117
|
+
price (int | float): The price at which to order.
|
|
112
118
|
"""
|
|
113
119
|
self.type = 'ORDER'
|
|
114
120
|
self.symbol = symbol
|
|
115
121
|
self.order_type = order_type
|
|
116
122
|
self.quantity = quantity
|
|
117
123
|
self.direction = direction
|
|
124
|
+
self.price = price
|
|
118
125
|
|
|
119
126
|
def print_order(self):
|
|
120
127
|
"""
|
|
121
128
|
Outputs the values within the Order.
|
|
122
129
|
"""
|
|
123
130
|
print(
|
|
124
|
-
"Order: Symbol=%s, Type=%s, Quantity=%s, Direction=%s" %
|
|
125
|
-
(self.symbol, self.order_type, self.quantity, self.direction)
|
|
131
|
+
"Order: Symbol=%s, Type=%s, Quantity=%s, Direction=%s, Price=%s" %
|
|
132
|
+
(self.symbol, self.order_type, self.quantity, self.direction, self.price)
|
|
126
133
|
)
|
|
127
134
|
|
|
128
135
|
|
|
@@ -150,8 +157,8 @@ class FillEvent(Event):
|
|
|
150
157
|
symbol: str,
|
|
151
158
|
exchange: str,
|
|
152
159
|
quantity: int | float,
|
|
153
|
-
direction:
|
|
154
|
-
fill_cost: int | float,
|
|
160
|
+
direction: Literal['BUY', 'SELL'],
|
|
161
|
+
fill_cost: int | float | None,
|
|
155
162
|
commission: float | None = None
|
|
156
163
|
):
|
|
157
164
|
"""
|
|
@@ -168,8 +175,8 @@ class FillEvent(Event):
|
|
|
168
175
|
symbol (str): The instrument which was filled.
|
|
169
176
|
exchange (str): The exchange where the order was filled.
|
|
170
177
|
quantity (int | float): The filled quantity.
|
|
171
|
-
direction (str): The direction of fill ('
|
|
172
|
-
fill_cost (int | float):
|
|
178
|
+
direction (str): The direction of fill `('LONG', 'SHORT', 'EXIT')`
|
|
179
|
+
fill_cost (int | float): Price of the shares when filled.
|
|
173
180
|
commission (float | None): An optional commission sent from IB.
|
|
174
181
|
"""
|
|
175
182
|
self.type = 'FILL'
|