-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Using a dict to store dynamic indicators #90
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
Comments
That's the root of the issue: backtesting.py/backtesting/backtesting.py Lines 692 to 697 in 0436e56
One way you could probably achieve this is: def init(self):
# Set attrs on strategy directly, e.g. self.sma1
# This will ensure they are picked up and run-through by Backtest
setattr(self, "sma1", self.I(SMA, self.data.Close, self.n1))
...
# Additionally, put them in a commonly accessible dict for later use elsewhere
self.timeseries["sma1"] = getattr(self, "sma1") |
Thanks @kernc I tried this, but no trades were made as well. from backtesting.test import GOOG
from backtesting.test import SMA
from backtesting import Strategy
from backtesting.lib import crossover
from backtesting import Backtest
class SmaCross(Strategy):
# Define the two MA lags as *class variables*
# for later optimization
n1 = 10
n2 = 20
timeseries = {}
def init(self):
# Precompute two moving averages
setattr(self, "sma1", self.I(SMA, self.data.Close, self.n1))
setattr(self, "sma2", self.I(SMA, self.data.Close, self.n2))
self.timeseries["sma1"] = getattr(self, "sma1")
self.timeseries["sma2"] = getattr(self, "sma2")
def next(self):
# If sma1 crosses above sma2, buy the asset
if crossover(self.timeseries["sma1"], self.timeseries["sma2"]):
self.buy()
# Else, if sma1 crosses below sma2, sell it
elif crossover(self.timeseries["sma2"], self.timeseries["sma1"]):
self.sell()
bt = Backtest(GOOG, SmaCross, cash=10000, commission=.002)
print(bt.run()) So, with your explanation, the better approach will be use the getattr and setattr like this (discarding the dicts approach) from backtesting.test import GOOG
from backtesting.test import SMA
from backtesting import Strategy
from backtesting.lib import crossover
from backtesting import Backtest
class SmaCross(Strategy):
# Define the two MA lags as *class variables*
# for later optimization
n1 = 10
n2 = 20
def init(self):
# Precompute two moving averages
setattr(self, "small sma", self.I(SMA, self.data.Close, self.n1))
setattr(self, "large sma", self.I(SMA, self.data.Close, self.n2))
def next(self):
# If sma1 crosses above sma2, buy the asset
if crossover(getattr(self, "small sma"), getattr(self, "large sma")):
self.buy()
# Else, if sma1 crosses below sma2, sell it
elif crossover(getattr(self, "large sma"), getattr(self, "small sma")):
self.sell()
bt = Backtest(GOOG, SmaCross, cash=10000, commission=.002)
print(bt.run()) Output
Thanks @kernc |
Ah, right, indicators are copied when sliced before each backtesting.py/backtesting/backtesting.py Lines 711 to 713 in 0436e56
This wouldn't update self.timeseries entries in-place. I'll think about it.
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Hi @kernc
Unfortunately the native optimization will not work with this approach.
Gives
|
Nobody claims you can |
Indeed, I was just wondering, because with kwargs the strategy cannot be dynamic :( The attributes cannot be dynamic for optimizations The var names cannot be dynamic
The existence as well...
Then, I will try to adapt the backtesting.py using something like this
Then replacing kwargs inside of the optimize() function from "optimization" dict would you have another idea? Thanks |
Something like this might work: stats = bt.optimize(optimization_dict=[
{"SMA_close_10": 5},
{"SMA_close_10": 10},
{"SMA_close_10": 15},
{"SMA_close_10": 20},
{"SMA_close_10": 25},
], maximize='Equity Final [$]') Where |
@wesleywilian Did that work? |
Hi @kernc sorry for late response. The code below from backtesting.test import GOOG
from backtesting.test import SMA
from backtesting import Strategy
from backtesting.lib import crossover
from backtesting import Backtest
class SmaCross(Strategy):
# Define the two MA lags as *class variables*
# for later optimization
def init(self):
# Precompute two moving averages
setattr(self, "small sma", self.I(SMA, self.data.Close, 10))
setattr(self, "large sma", self.I(SMA, self.data.Close, 20))
def next(self):
# If sma1 crosses above sma2, buy the asset
if crossover(getattr(self, "small sma"), getattr(self, "large sma")):
self.buy()
# Else, if sma1 crosses below sma2, sell it
elif crossover(getattr(self, "large sma"), getattr(self, "small sma")):
self.sell()
bt = Backtest(GOOG, SmaCross, cash=10000, commission=.002)
#print(bt.run())
stats = bt.optimize(optimization_dict=[
{"SMA_close_10": 5},
{"SMA_close_10": 10},
{"SMA_close_10": 15},
{"SMA_close_10": 20},
{"SMA_close_10": 25},
], maximize='Equity Final [$]') Result in
Then I tried from backtesting.test import GOOG
from backtesting.test import SMA
from backtesting import Strategy
from backtesting.lib import crossover
from backtesting import Backtest
class SmaCross(Strategy):
# Define the two MA lags as *class variables*
# for later optimization
def init(self):
# Precompute two moving averages
setattr(self, "SMA_close_10", self.I(SMA, self.data.Close, 30))
setattr(self, "SMA_close_30", self.I(SMA, self.data.Close, 50))
def next(self):
# If SMA_close_10 crosses above SMA_close_30, buy the asset
if crossover(getattr(self, "SMA_close_10"), getattr(self, "SMA_close_30")):
self.buy()
# Else, if SMA_close_10 crosses below SMA_close_30, sell it
elif crossover(getattr(self, "SMA_close_30"), getattr(self, "SMA_close_10")):
self.sell()
bt = Backtest(GOOG, SmaCross, cash=10000, commission=.002)
#print(bt.run())
# dynamic optimizing...
stats = bt.optimize(optimization_dict={"SMA_close_10": range(5, 30, 5),
"SMA_close_30": range(10, 70, 5)},
maximize='Equity Final [$]')
print(stats) Changing backtesting.py, adding the content below in optimize() (line ~769) if not kwargs:
raise ValueError('Need some strategy parameters to optimize')
# added this
dynamic = kwargs.get("optimization_dict")
if dynamic:
kwargs = dynamic Result
Then I tried again, change backtesting.py (line ~769) if not kwargs:
raise ValueError('Need some strategy parameters to optimize')
dynamic = kwargs.get("optimization_dict")
if dynamic:
kwargs = dynamic
for key in kwargs.keys():
setattr(self._strategy, key, kwargs[key]) Result
But the optimization doesn't work yet.
No matter the range specified in the dict. The result of the optimization is always based on the parameters specified in the init. 30 and 50. def init(self):
# Precompute two moving averages
setattr(self, "SMA_close_10", self.I(SMA, self.data.Close, 30))
setattr(self, "SMA_close_30", self.I(SMA, self.data.Close, 50)) The what could I be doing wrong? Thanks |
This works: from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import GOOG, SMA
class SmaCross(Strategy):
n1 = 15 # <-- this is required
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()
bt = Backtest(GOOG, SmaCross, cash=10000, commission=.002, exclusive_orders=True)
bt.optimize(n1=range(5, 30, 5),
n2=range(10, 70, 5),
constraint=lambda p: p.n1 < p.n2) You need the initially defined optimization class variables ( If those are also dynamic and based on some algorithm, something like this should work: class SmaCross(Strategy):
# No variables declared here at this time
def init(self):
...
def prepare_optimization(cls: type) -> dict:
# Set the class variables upon the class
setattr(cls, 'n1', 1)
setattr(cls, 'n2', 2)
# Construct and return optimization kwargs
return {'n1': range(5, 30, 5),
'n2': range(10, 70, 5),
'constraint': lambda p: p['n1'] < p['n2']}
opt_kwargs = prepare_optimization(SmaCross)
assert getattr(SmaCross, 'n1') == 1
...
bt.optimize(**opt_kwargs) |
The code for future references: from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import GOOG, SMA
class SmaCross(Strategy):
def init(self):
setattr(self, "sma1", self.I(SMA, self.data.Close, getattr(self, "n1")))
setattr(self, "sma2", self.I(SMA, self.data.Close, getattr(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()
def prepare_optimization(cls: type) -> dict:
# Set the class variables upon the class
setattr(cls, 'n1', 1)
setattr(cls, 'n2', 2)
# Construct and return optimization kwargs
return {'n1': range(5, 30, 5),
'n2': range(10, 70, 5),
'constraint': lambda p: p['n1'] < p['n2'],
'maximize': 'Equity Final [$]'}
bt = Backtest(GOOG, SmaCross, cash=10000, commission=.002, exclusive_orders=True)
opt_kwargs = prepare_optimization(SmaCross)
x = bt.optimize(**opt_kwargs)
print(x.to_string()) The result:
Now native optimization is really working. 😃 With that we can construct dynamic strategies from a json. Example below (example for dynamic timeseries only. Not for strategy logic (yet)): Some json like this {"timeseries": [
{
"name": "SMA_close_10",
"function": "sma",
"parameters": {
"source": "close",
"timeperiod": 10
}
},
{
"name": "SMA_close_30",
"function": "sma",
"parameters": {
"source": "close",
"timeperiod": 30
}
}
]} @kernc Your help was great, I appreciate it. Thank you 😁 |
Expected Behavior
Calculate the strategy and display the result:
Actual Behavior
No trades were made.
Steps to Reproduce
Additional info
Thanks
The text was updated successfully, but these errors were encountered: