-
-
Notifications
You must be signed in to change notification settings - Fork 985
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Backtest/Plot are not thread safe ? #125
Comments
If likely, the first thing I'd look at is the way we use Bokeh's global state: backtesting.py/backtesting/_plotting.py Lines 145 to 164 in c8f9cc1
backtesting.py/backtesting/_plotting.py Lines 68 to 75 in c8f9cc1
PR welcome! |
I see So... basically we need to reset bokeh's global state ? |
I was thinking. Isn't the problem because we're not inheriting a class "SmaCross"? Even if we reset the global state, it will still not be thread safe. (Correct me if I'm wrong) |
The state is already reset each time. I think we need to replace the use of Bokeh's global state ( I can't say for sure that that's the only critical section, but it's the obvious one and the plot-file-not-found error points to it as well. We are using backtesting.py/backtesting/backtesting.py Line 1068 in c8f9cc1
backtesting.py/backtesting/backtesting.py Lines 1110 to 1112 in c8f9cc1
|
Hi @kernc I did a test to validate that the backtest is not being affected import os
import threading
from uuid import uuid4
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import GOOG, SMA
from flask import Flask
from waitress import serve
app = Flask(__name__)
class SmaCross(Strategy):
n1 = 15
n2 = 30
def init(self):
setattr(self, "sma1", self.I(SMA, self.data.Close, self.n1))
setattr(self, "sma2", self.I(SMA, self.data.Close, self.n2))
def next(self):
if crossover(getattr(self, "sma1"), getattr(self, "sma2")):
self.buy()
elif crossover(getattr(self, "sma2"), getattr(self, "sma1")):
self.sell()
class SmaCrossBankrupt(Strategy):
# n1 and n2 inverted on bankrupt class...
n1 = 30
n2 = 15
def init(self):
setattr(self, "sma1", self.I(SMA, self.data.Close, self.n1))
setattr(self, "sma2", self.I(SMA, self.data.Close, self.n2))
def next(self):
if crossover(getattr(self, "sma1"), getattr(self, "sma2")):
self.buy()
elif crossover(getattr(self, "sma2"), getattr(self, "sma1")):
self.sell()
class Backtesting:
def __init__(self, strategy_id):
self.strategy_id = strategy_id
def compute(self):
if self.strategy_id == 1:
strategy_class = SmaCross
else:
strategy_class = SmaCrossBankrupt
bt = Backtest(GOOG, strategy_class, cash=10000, commission=.002, exclusive_orders=True)
x = bt.run()
file_uuid = str(uuid4())
filename = "/tmp/backtest_plot_" + file_uuid + ".html"
print("thread:", threading.get_ident(), "start creating file:", filename)
bt.plot(open_browser=False, filename=filename)
try:
f = open(filename)
some_raw_data = f.read()
f.close()
os.remove(filename)
except FileNotFoundError:
some_raw_data = ""
print("thread:", threading.get_ident(), "file:", filename, "not found!")
print("thread:", threading.get_ident(), "end creating file:", filename)
ret = "Return [%] {}\n".format(x.get("Return [%]"))
return ret
@app.route('/<int:strategy_id>', methods=['GET'])
def index(strategy_id):
return Backtesting(strategy_id).compute()
if __name__ == '__main__':
serve(app, host='0.0.0.0', port=9090, threads=10) Open multiple terminals with and at same time, multiples with another context
So, this confirms, the backtest is not affected. I'll check your suggestions and internally analyze how the plotting feature works... Thanks @kernc |
Expected Behavior
Run backtests and plots with multiple threads without concurrency problems.
Actual Behavior
Multithreaded execution results in data inconsistency. When plotting. (and backtesting maybe?)
Steps to Reproduce
Open two terminals and run on both (or use jmeter or something like that):
while true ; do curl localhost:9090 ; done
Check the logs
simplifying...
The "thread 1" starts creating the "A" file, then the "thread 2" starts creating the "B" file.
As we can see, there is a inconsistency due the threads usage (sorry for the flask example).
I suspect, the problem is the "SmaCross" and "Strategy" classes.
@kernc would you have suggestions, how we can fix this ?
Additional info
The text was updated successfully, but these errors were encountered: