Skip to content

Commit

Permalink
cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
Gunther Schulz committed Mar 7, 2021
1 parent da76941 commit bed3e2c
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 92 deletions.
10 changes: 5 additions & 5 deletions backtradermql5/mt5chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
import math
import uuid

# TODO self test. add lrma (or similar) to the chart, one dirctely in MT5 and teh other plotted form BT and compare


class MTraderChart(bt.Indicator):

# Inherited Indicator class requires at least one line
lines = ("dummyline",)
plotlines = dict(dummyline=dict(_plotskip="True",))
plotlines = dict(
dummyline=dict(
_plotskip="True",
)
)
plotinfo = dict(plotskip=True)

# If False, plot will be output on the next live tick/bar or immediatly after
Expand All @@ -34,7 +36,6 @@ def __init__(self):
self.p.compression = self.data._compression

# Assumimg the data feed is resampled if it was cloned. Set self.p.resampled to False if the feed was cloned, but not resampled
print(type(self.data).__name__)
if type(self.data).__name__ == "DataClone" and self.p.resampled or type(self.data).__name__ == "DataClone":
self.p.d = self.data.p.dataname
self.p.store = self.data.p.dataname.o
Expand Down Expand Up @@ -63,7 +64,6 @@ def next(self):
line["from_date"] = line["last_date"].timestamp()
if not self.p.realtime:
line["values"].reverse()
print(line["values"])
self.p.store.push_chart_data(
self.p.chart_id,
self.p.mt_chart_id,
Expand Down
75 changes: 57 additions & 18 deletions backtradermql5/mt5store.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,11 @@ def __init__(self, *args, **kwargs):
self._orders_type = dict() # keeps order types

kwargs.update(
{"host": self.params.host, "debug": self.params.debug, "datatimeout": self.params.datatimeout,}
{
"host": self.params.host,
"debug": self.params.debug,
"datatimeout": self.params.datatimeout,
}
)
self.oapi = MTraderAPI(*args, **kwargs)

Expand Down Expand Up @@ -530,7 +534,12 @@ def order_create(self, order, stopside=None, takeside=None, **kwargs):
okwargs["magic"] = order.ref

okwargs.update(**kwargs) # anything from the user
self.q_ordercreate.put((order.ref, okwargs,))
self.q_ordercreate.put(
(
order.ref,
okwargs,
)
)

# notify orders of being submitted
self.broker._submit(order.ref)
Expand Down Expand Up @@ -665,7 +674,9 @@ def price_data(self, dataname, dtbegin, dtend, timeframe, compression, include_f
def config_server(self, symbol: str, timeframe: str, compression: int) -> None:
"""Set server terminal symbol and time frame"""
ret_val = self.oapi.construct_and_send(
action="CONFIG", symbol=symbol, chartTF=self.get_granularity(timeframe, compression),
action="CONFIG",
symbol=symbol,
chartTF=self.get_granularity(timeframe, compression),
)

if ret_val["error"]:
Expand Down Expand Up @@ -699,7 +710,6 @@ def cancel_order(self, oid, symbol):
print("Cancelling order: {}, on symbol: {}".format(oid, symbol))

conf = self.oapi.construct_and_send(action="TRADE", actionType="ORDER_CANCEL", symbol=symbol, id=oid)
print(conf)
# Error handling
if conf["error"]:
raise ServerDataError(conf)
Expand Down Expand Up @@ -790,7 +800,11 @@ def config_chart(self, chartId, symbol, timeframe, compression):
)

ret_val = self.oapi.construct_and_send(
action="CHART", actionType="OPEN", chartId=chartId, symbol=symbol, chartTF=chart_tf,
action="CHART",
actionType="OPEN",
chartId=chartId,
symbol=symbol,
chartTF=chart_tf,
)

if ret_val["error"]:
Expand All @@ -815,10 +829,16 @@ def chart_add_indicator(self, chart_id, indicator_id, chart_sub_window, short_na
if ret_val["error"]:
print(ret_val)
raise ChartError(ret_val["description"])
self.put_notification(ret_val["description"])
# self.put_notification(ret_val["description"])

def push_chart_data(
self, chart_id, mt_chart_id, chart_indicator_id, indicator_buffer_id, from_date, data,
self,
chart_id,
mt_chart_id,
chart_indicator_id,
indicator_buffer_id,
from_date,
data,
):
"""Pushes backtrader indicator values to be distributed to be drawn by JsonAPIIndicator instances"""

Expand All @@ -837,7 +857,11 @@ def chart_indicator_add_line(self, chart_id, chart_indicator_id, style):
"""Add line to be drawn by JsonAPIIndicator instances"""

self.oapi.chart_data_construct_and_send(
action="PLOT", actionType="ADDBUFFER", chartId=chart_id, chartIndicatorId=chart_indicator_id, style=style,
action="PLOT",
actionType="ADDBUFFER",
chartId=chart_id,
chartIndicatorId=chart_indicator_id,
style=style,
)

# def chart_add_graphic(self, chartId, chartIndicatorId, chartIndicatorSubWindow, style):
Expand Down Expand Up @@ -882,12 +906,14 @@ def config_indicator(self, symbol, timeframe, compression, name, id, params, lin
if ret_val["error"]:
print(ret_val)
raise IndicatorError(ret_val["description"])
self.put_notification(ret_val["description"])
# self.put_notification(ret_val["description"])

return ret_val

def indicator_data(
self, indicatorId, fromDate,
self,
indicatorId,
fromDate,
):
"""Recieves values from a MT5 indicator instance"""

Expand All @@ -899,15 +925,18 @@ def indicator_data(
)

ret_val = self.oapi.indicator_construct_and_send(
action="INDICATOR", actionType="REQUEST", id=indicatorId, fromDate=fromDate,
action="INDICATOR",
actionType="REQUEST",
id=indicatorId,
fromDate=fromDate,
)

if ret_val["error"]:
print(ret_val)
raise IndicatorError(ret_val["description"])
self.put_notification(ret_val["description"])
if ret_val["lastError"] == "4806":
self.put_notification("You have probably requested too many lines (MT5 indicator buffers).")
# self.put_notification(ret_val["description"])
# if ret_val["lastError"] == "4806":
# self.put_notification("You have probably requested too many lines (MT5 indicator buffers).")

return ret_val

Expand All @@ -919,10 +948,15 @@ def reset_server(self) -> None:
if ret_val["error"]:
print(ret_val)
raise ServerConfigError(ret_val["description"])
self.put_notification(ret_val["description"])
# self.put_notification(ret_val["description"])

def write_csv(
self, symbol: str, timeframe: str, compression: int = 1, fromdate: datetime = None, todate: datetime = None,
self,
symbol: str,
timeframe: str,
compression: int = 1,
fromdate: datetime = None,
todate: datetime = None,
) -> None:
"""Request MT5 to write history data to CSV a file"""

Expand Down Expand Up @@ -951,13 +985,18 @@ def write_csv(
print("Request CSV write with Fetching: {}, Timeframe: {}, Fromdate: {}".format(symbol, tf, date_begin))

ret_val = self.oapi.construct_and_send(
action="HISTORY", actionType="WRITE", symbol=symbol, chartTF=tf, fromDate=begin, toDate=end,
action="HISTORY",
actionType="WRITE",
symbol=symbol,
chartTF=tf,
fromDate=begin,
toDate=end,
)

if ret_val["error"]:
print(ret_val)
raise ServerConfigError(ret_val["description"])
self.put_notification(ret_val["description"])
# self.put_notification(ret_val["description"])
else:
self.put_notification(
f"Request to write CVS data for symbol {tf} and timeframe {tf} succeeded. Check MT5 EA logging for the exact output location ..."
Expand Down
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### March 6th
- flag mt5chart module as "experimental"
- redesign api for mt5chart module (see included exmaple)

### 30th April 2020

- add support for spreads
Expand Down
108 changes: 39 additions & 69 deletions examples/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
from backtradermql5.mt5indicator import getMTraderIndicator
from backtradermql5.mt5chart import MTraderChart, ChartIndicator
from datetime import datetime, timedelta
from regressionchannel3 import LinearRegression

# from regressionchannel3 import LinearRegression
# from ohlc import OHLC


class SmaCross(bt.SignalStrategy):
def __init__(self, store):
self.buy_order = None
self.live_data = False

# # Attach and retrieve values from the MT5 indicator "Examples/MACD"
self.mt5cma0 = getMTraderIndicator(
# Attach and retrieve values from the MT5 indicator "Examples/Custom Moving Average"
self.mt5cma = getMTraderIndicator(
# MTraderStorestore instance
store,
# Data feed to run the indicator calculations on
Expand All @@ -33,52 +35,57 @@ def __init__(self, store):
# Instantiating backtrader indicator Bollinger Bands and Moving Averages
# Important: This needs to come before instantiating a chart window
# with backtradermql5.mt5indicator.MTraderChart. Otherwise backtrader will fail.
self.bb0 = btind.BollingerBands(self.datas[0])
self.bb1 = btind.BollingerBands(self.datas[1])
self.sma0 = btind.MovingAverageSimple(self.datas[0])
self.sma1 = btind.MovingAverageSimple(self.datas[1])

self.lr0 = LinearRegression(self.datas[0], len=21)
self.lr1 = LinearRegression(self.datas[1], len=21)
self.bb = btind.BollingerBands(self.datas[0])
self.sma = btind.MovingAverageSimple(self.datas[0])

# -----> Experimental feature BEGIN
# The feaure to plot data to MT5 chart windows WILL plot false data at this point in time. More work is needed.
# Plot the backtrader BollingerBand and SMA indicators to a chart window in MT5

def addChart(chart, bb, sma, lr):
def addChart(chart, bb, sma):
# Instantiate new indicator and draw to the main window. The parameter idx=0 specifies wether to plot to the
# main window (idx=0) or a subwindow (idx=1 for the first subwindow, idx=2 for the second etc.).
indi0 = ChartIndicator(idx=0, shortname="Bollinger Bands")

# # Add line buffers
indi0.addline(
bb.top, style={"linelabel": "Top", "color": "clrBlue",},
bb.top,
style={
"linelabel": "Top",
"color": "clrBlue",
},
)
indi0.addline(
bb.mid, style={"linelabel": "Middle", "color": "clrYellow",},
bb.mid,
style={
"linelabel": "Middle",
"color": "clrYellow",
},
)
indi0.addline(
bb.bot, style={"linelabel": "Bottom", "color": "clrGreen",},
)
indi0.addline(
lr.linear_regression,
style={"linelabel": "linear_regression", "color": "clrYellow", "linewidth": 3, "blankforming": True},
bb.bot,
style={
"linelabel": "Bottom",
"color": "clrGreen",
},
)

# Add the indicator to the chart and draw the line buffers.
chart.addchartindicator(indi0)

# Instantiate second indicator to draw to the first sub-window and add line buffers
# # Instantiate second indicator to draw to the first sub-window and add line buffers
indi1 = ChartIndicator(idx=1, shortname="Simple Moving Average")
indi1.addline(
sma.sma, style={"linelabel": "SMA", "color": "clrBlue", "linestyle": "STYLE_DASH", "linewidth": 2},
sma.sma,
style={"linelabel": "SMA", "color": "clrBlue", "linestyle": "STYLE_DASH", "linewidth": 2},
)
chart.addchartindicator(indi1)

# Instantiate a new chart window and plot
chart0 = MTraderChart(self.datas[0])
addChart(chart0, self.bb0, self.sma0, self.lr0)
chart = MTraderChart(self.datas[0], realtime=False)
addChart(chart, self.bb, self.sma)

# Instantiate a second chart window and plot
chart1 = MTraderChart(self.datas[1], realtime=False)
addChart(chart1, self.bb1, self.sma1, self.lr1)
# Experimental feature END <-----

def next(self):
# Uncomment below to execute trades
Expand All @@ -101,8 +108,8 @@ def next(self):
print(
f"{data.datetime.datetime()} - {data._name} | Cash {cash} | O: {data.open[0]} H: {data.high[0]} L: {data.low[0]} C: {data.close[0]} V:{data.volume[0]}"
)
print(f"MT5 indicator 'Examples/Custom Moving Average': {self.mt5cma0.cma[0]}") # " {self.mt5macd.macd[0]}")
print("")
print(f"MT5 indicator 'Examples/Custom Moving Average': {self.mt5cma.cma[0]}")

def notify_data(self, data, status, *args, **kwargs):
dn = data._name
Expand All @@ -117,10 +124,10 @@ def notify_data(self, data, status, *args, **kwargs):

# If MetaTrader runs locally
# host = "localhost"
# If Metatrader runs at different address
# If Metatrader runs at differnt address
host = "192.168.56.124"

store = MTraderStore(host=host, debug=False, datatimeout=100)
store = MTraderStore(host=host, debug=False, datatimeout=10)

cerebro = bt.Cerebro()

Expand All @@ -129,54 +136,17 @@ def notify_data(self, data, status, *args, **kwargs):
broker = store.getbroker(use_positions=True)
cerebro.setbroker(broker)

start_date = datetime.now() - timedelta(hours=60)

def resample(
data=None, compression=None, boundoff=0,
):
cerebro.resampledata(
data,
name=f"RESAMPLED{compression}",
timeframe=bt.TimeFrame.Minutes,
compression=compression,
boundoff=boundoff,
)


# In backtesting mode (historical=True), the plots will be dsiplayed once all indicators have finished their calculations
# In live mode (defualt, historical=False), the plots will be displayed on the next tick/bar

start_date = datetime.now() - timedelta(hours=20)
data = store.getdata(
dataname="EURUSD",
name="TICKS",
timeframe=bt.TimeFrame.Ticks,
fromdate=start_date,
compression=1,
# useask=True, # For Tick data only: Ask price instead if the default bid price
# addspread=True, # For Candle data only: Add the spread value
# Specify the timezone of the trade server that MetaTrader connects to.
# More information at: https://www.backtrader.com/docu/timemgmt/
tz=pytz.timezone("UTC"),
historical=True,
)
resample(data, compression=1)

start_date = datetime.now() - timedelta(hours=60)
data = store.getdata(
dataname="EURGBP",
name="BARS",
timeframe=bt.TimeFrame.Minutes,
fromdate=start_date,
compression=1,
# useask=True, # For Tick data only: Ask price instead if the default bid price
# addspread=True, # For Candle data only: Add the spread value
tz=pytz.timezone("UTC"),
# tz=pytz.timezone("Europe/Berlin"),
# useask=True,
historical=True,
)
# When resampling bar data, use boundoff=1
# https://www.backtrader.com/docu/data-resampling/data-resampling/
resample(data, compression=5, boundoff=1)

cerebro.adddata(data)

cerebro.run(stdstats=False)

0 comments on commit bed3e2c

Please sign in to comment.