def is_in_jupyter() -> bool:
'''
Determine wheather is the environment Jupyter Notebook
https://blog.amedama.jp/entry/detect-jupyter-env
'''
if 'get_ipython' not in globals():
return False
env_name = get_ipython().__class__.__name__
if env_name == 'TerminalInteractiveShell':
return False
return True
print(is_in_jupyter())
if is_in_jupyter():
def set_stylesheet():
from IPython.display import display, HTML
css = get_ipython().getoutput('wget https://raw.githubusercontent.com/lapis-zero09/jupyter_notebook_tips/master/css/jupyter_notebook/monokai.css -q -O -')
css = "\n".join(css)
display(HTML('<style type="text/css">%s</style>'%css))
set_stylesheet()
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import collections
from datetime import datetime, timedelta
import time as _time
import json
import threading
import backtrader as bt
from backtrader.metabase import MetaParams
from backtrader.utils.py3 import queue, with_metaclass
from backtrader.utils import AutoDict
import kabusapi
kabusapi.Context
FIXME
from enum import Enum
class KabuSAPIEnv(Enum):
DEV = 'dev'
PROD = 'prod'
class MetaSingleton(MetaParams):
'''Metaclass to make a metaclassed class a singleton'''
def __init__(cls, name, bases, dct):
super(MetaSingleton, cls).__init__(name, bases, dct)
cls._singleton = None
def __call__(cls, *args, **kwargs):
if cls._singleton is None:
cls._singleton = (
super(MetaSingleton, cls).__call__(*args, **kwargs))
return cls._singleton
class KabuSAPIStore(with_metaclass(MetaSingleton, object)):
'''Singleton class wrapping to control the connections to Kabu STATION API.
Params:
- ``token`` (default:``None``): API access token
- ``account`` (default: ``None``): account id
- ``practice`` (default: ``False``): use the test environment
- ``account_tmout`` (default: ``10.0``): refresh period for account
value/cash refresh
'''
BrokerCls = None
DataCls = None
params = (
('url', 'localhost'),
('env', KabuSAPIEnv.DEV),
('port', None),
('password', None),
)
@classmethod
def getdata(cls, *args, **kwargs):
'''Returns ``DataCls`` with args, kwargs'''
return cls.DataCls(*args, **kwargs)
@classmethod
def getbroker(cls, *args, **kwargs):
'''Returns broker with *args, **kwargs from registered ``BrokerCls``'''
return cls.BrokerCls(*args, **kwargs)
def __init__(self):
def _getport() -> int:
if self.p.port:
return port
return 18081 if self.p.env == KabuSAPIEnv.DEV else 18080
def _init_kabusapi_client() -> kabusapi.Context:
url = self.p.url
port = self.p.port or _getport()
password = self.p.password
token = kabusapi.Context(url, port, password).token
self.kapi = kabusapi.Context(url, port, token=token)
super(KabuSAPIStore, self).__init__()
self.notifs = collections.deque()
self._env = None
self.broker = None
self.datas = list()
self._orders = collections.OrderedDict()
self._ordersrev = collections.OrderedDict()
self._transpend = collections.defaultdict(collections.deque)
_init_kabusapi_client()
self._cash = 0.0
self._value = 0.0
self._evt_acct = threading.Event()
def start(self, data=None, broker=None):
if data is None and broker is None:
self.cash = None
return
if data is not None:
self._env = data._env
self.datas.append(data)
if self.broker is not None:
self.broker.data_started(data)
elif broker is not None:
self.broker = broker
self.streaming_events()
self.broker_threads()
def stop(self):
if self.broker is not None:
self.q_ordercreate.put(None)
self.q_orderclose.put(None)
self.q_account.put(None)
def put_notification(self, msg, *args, **kwargs):
self.notifs.append((msg, args, kwargs))
def get_notifications(self):
'''Return the pending "store" notifications'''
self.notifs.append(None)
return [x for x in iter(self.notifs.popleft, None)]
def get_positions(self):
try:
positions = self.kapi.positions()
except (oandapy.OandaError, OandaRequestError,):
FIXME
return None
poslist = positions.get('positions', [])
return poslist
FIXME
def streaming_events(self, tmout=None):
kwargs = {'q': q, 'tmout': tmout}
t = threading.Thread(target=self._streaming_listener, kwargs=kwargs)
t.daemon = True
t.start()
FIXME
return q
FIXME
def _streaming_listener(msg):
'''
Ref: https://github.com/shirasublue/python-kabusapi/blob/master/sample/push_sample.py
'''
pass
FIXME
FIXME
def candles(self, dataname, dtbegin, dtend, timeframe, compression,
candleFormat, includeFirst):
kwargs = locals().copy()
kwargs.pop('self')
kwargs['q'] = q = queue.Queue()
t = threading.Thread(target=self._t_candles, kwargs=kwargs) FIXME
t.daemon = True
t.start()
return q
def _t_candles(self, dataname, dtbegin, dtend, timeframe, compression,
candleFormat, includeFirst, q):
FIXME
if granularity is None:
e = OandaTimeFrameError()
q.put(e.error_response)
return
dtkwargs = {}
if dtbegin is not None:
dtkwargs['start'] = int((dtbegin - self._DTEPOCH).total_seconds()) FIXME
if dtend is not None:
dtkwargs['end'] = int((dtend - self._DTEPOCH).total_seconds()) FIXME
try:
pass
FIXME
except oandapy.OandaError as e:
q.put(e.error_response)
q.put(None)
return
FIXME
for candle in response.get('candles', []):
q.put(candle)
q.put({})
def streaming_prices(self, dataname, tmout=None):
q = queue.Queue()
kwargs = {'q': q, 'dataname': dataname, 'tmout': tmout}
t = threading.Thread(target=self._t_streaming_prices, kwargs=kwargs) FIXME
t.daemon = True
t.start()
return q
FIXME
def _t_streaming_prices(self, dataname, q, tmout):
if tmout is not None:
_time.sleep(tmout)
FIXME
FIXME
FIXME
FIXME
FIXME
def get_cash(self):
return self._cash
def get_value(self):
return self._value
_ORDEREXECS = {
bt.Order.Market: 'market',
bt.Order.Limit: 'limit',
bt.Order.Stop: 'stop',
bt.Order.StopLimit: 'stop',
}
def broker_threads(self):
self.q_account = queue.Queue()
self.q_account.put(True)
t = threading.Thread(target=self._t_account)
t.daemon = True
t.start()
self.q_ordercreate = queue.Queue()
t = threading.Thread(target=self._t_order_create)
t.daemon = True
t.start()
self.q_orderclose = queue.Queue()
t = threading.Thread(target=self._t_order_cancel)
t.daemon = True
t.start()
self._evt_acct.wait(self.p.account_tmout)
def _t_account(self):
while True:
try:
msg = self.q_account.get(timeout=self.p.account_tmout)
if msg is None:
break
except queue.Empty:
pass
try:
accinfo = self.oapi.get_account(self.p.account)
except Exception as e:
self.put_notification(e)
continue
try:
self._cash = accinfo['marginAvail']
self._value = accinfo['balance']
except KeyError:
pass
self._evt_acct.set()
def order_create(self, order, stopside=None, takeside=None, **kwargs):
okwargs = dict()
okwargs['instrument'] = order.data._dataname
okwargs['units'] = abs(order.created.size)
okwargs['side'] = 'buy' if order.isbuy() else 'sell'
okwargs['type'] = self._ORDEREXECS[order.exectype]
if order.exectype != bt.Order.Market:
okwargs['price'] = order.created.price
if order.valid is None:
valid = datetime.utcnow() + timedelta(days=30)
else:
valid = order.data.num2date(order.valid)
okwargs['expiry'] = int((valid - self._DTEPOCH).total_seconds()) FIXME
if order.exectype == bt.Order.StopLimit:
okwargs['lowerBound'] = order.created.pricelimit
okwargs['upperBound'] = order.created.pricelimit
if order.exectype == bt.Order.StopTrail:
okwargs['trailingStop'] = order.trailamount
if stopside is not None:
okwargs['stopLoss'] = stopside.price
if takeside is not None:
okwargs['takeProfit'] = takeside.price
okwargs.update(**kwargs)
self.q_ordercreate.put((order.ref, okwargs,))
return order
_OIDSINGLE = ['orderOpened', 'tradeOpened', 'tradeReduced']
_OIDMULTIPLE = ['tradesClosed']
def _t_order_create(self):
while True:
msg = self.q_ordercreate.get()
if msg is None:
break
oref, okwargs = msg
try:
o = self.oapi.create_order(self.p.account, **okwargs)
except Exception as e:
self.put_notification(e)
self.broker._reject(oref)
return
oids = list()
for oidfield in self._OIDSINGLE:
if oidfield in o and 'id' in o[oidfield]:
oids.append(o[oidfield]['id'])
for oidfield in self._OIDMULTIPLE:
if oidfield in o:
for suboidfield in o[oidfield]:
oids.append(suboidfield['id'])
if not oids:
self.broker._reject(oref)
return
self._orders[oref] = oids[0]
self.broker._submit(oref)
if okwargs['type'] == 'market':
self.broker._accept(oref)
for oid in oids:
self._ordersrev[oid] = oref
tpending = self._transpend[oid]
tpending.append(None)
while True:
trans = tpending.popleft()
if trans is None:
break
self._process_transaction(oid, trans)
def order_cancel(self, order):
self.q_orderclose.put(order.ref)
return order
def _t_order_cancel(self):
while True:
oref = self.q_orderclose.get()
if oref is None:
break
oid = self._orders.get(oref, None)
if oid is None:
continue
try:
o = self.oapi.close_order(self.p.account, oid)
except Exception as e:
continue FIXME
self.broker._cancel(oref)
_X_ORDER_CREATE = ('STOP_ORDER_CREATE',
'LIMIT_ORDER_CREATE', 'MARKET_IF_TOUCHED_ORDER_CREATE',)
def _transaction(self, trans):
'''
ストリームイベントを拾って、ハンドルする。
Invoked from Streaming Events. May actually receive an event for an
oid which has not yet been returned after creating an order. Hence
store if not yet seen, else forward to processer
'''
ttype = trans['type']
if ttype == 'MARKET_ORDER_CREATE':
try:
oid = trans['tradeReduced']['id']
except KeyError:
try:
oid = trans['tradeOpened']['id']
except KeyError:
return
elif ttype in self._X_ORDER_CREATE:
oid = trans['id']
elif ttype == 'ORDER_FILLED':
oid = trans['orderId']
elif ttype == 'ORDER_CANCEL':
oid = trans['orderId']
elif ttype == 'TRADE_CLOSE':
oid = trans['id']
pid = trans['tradeId']
if pid in self._orders and False:
return
msg = ('Received TRADE_CLOSE for unknown order, possibly generated'
' over a different client or GUI')
self.put_notification(msg, trans)
return
else:
try:
oid = trans['id']
except KeyError:
oid = 'None'
msg = 'Received {} with oid {}. Unknown situation'
msg = msg.format(ttype, oid)
self.put_notification(msg, trans)
return
try:
oref = self._ordersrev[oid]
self._process_transaction(oid, trans)
except KeyError:
self._transpend[oid].append(trans)
_X_ORDER_FILLED = ('MARKET_ORDER_CREATE',
'ORDER_FILLED', 'TAKE_PROFIT_FILLED',
'STOP_LOSS_FILLED', 'TRAILING_STOP_FILLED',)
def _process_transaction(self, oid, trans):
try:
oref = self._ordersrev.pop(oid)
except KeyError:
return
ttype = trans['type']
if ttype in self._X_ORDER_FILLED:
size = trans['units']
if trans['side'] == 'sell':
size = -size
price = trans['price']
self.broker._fill(oref, size, price, ttype=ttype)
elif ttype in self._X_ORDER_CREATE:
self.broker._accept(oref)
self._ordersrev[oid] = oref
elif ttype in 'ORDER_CANCEL':
reason = trans['reason']
if reason == 'ORDER_FILLED':
pass
elif reason == 'TIME_IN_FORCE_EXPIRED':
self.broker._expire(oref)
elif reason == 'CLIENT_REQUEST':
self.broker._cancel(oref)
else:
self.broker._reject(oref)
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import collections
from copy import copy
from datetime import date, datetime, timedelta
import threading
from backtrader.feed import DataBase
from backtrader import (TimeFrame, num2date, date2num, BrokerBase,
Order, BuyOrder, SellOrder, OrderBase, OrderData)
from backtrader.utils.py3 import bytes, with_metaclass, MAXFLOAT
from backtrader.metabase import MetaParams
from backtrader.comminfo import CommInfoBase
from backtrader.position import Position
from backtrader.utils import AutoDict, AutoOrderedDict
from backtrader.comminfo import CommInfoBase
class OandaCommInfo(CommInfoBase):
def getvaluesize(self, size, price):
return abs(size) * price
def getoperationcost(self, size, price):
'''Returns the needed amount of cash an operation would cost'''
return abs(size) * price
class MetaOandaBroker(BrokerBase.__class__):
def __init__(cls, name, bases, dct):
'''Class has already been created ... register'''
super(MetaOandaBroker, cls).__init__(name, bases, dct)
KabuSAPIStore.BrokerCls = cls
class OandaBroker(with_metaclass(MetaOandaBroker, BrokerBase)):
'''Broker implementation for Oanda.
This class maps the orders/positions from Oanda to the
internal API of ``backtrader``.
Params:
- ``use_positions`` (default:``True``): When connecting to the broker
provider use the existing positions to kickstart the broker.
Set to ``False`` during instantiation to disregard any existing
position
'''
params = (
('use_positions', True),
('commission', OandaCommInfo(mult=1.0, stocklike=False)),
)
def __init__(self, **kwargs):
super(OandaBroker, self).__init__()
self.o = KabuSAPIStore(**kwargs)
self.orders = collections.OrderedDict()
self.notifs = collections.deque()
self.opending = collections.defaultdict(list)
self.brackets = dict()
self.startingcash = self.cash = 0.0
self.startingvalue = self.value = 0.0
self.positions = collections.defaultdict(Position)
def start(self):
super(OandaBroker, self).start()
self.o.start(broker=self)
self.startingcash = self.cash = cash = self.o.get_cash()
self.startingvalue = self.value = self.o.get_value()
if self.p.use_positions:
for p in self.o.get_positions():
print('position for instrument:', p['instrument'])
is_sell = p['side'] == 'sell'
size = p['units']
if is_sell:
size = -size
price = p['avgPrice']
self.positions[p['instrument']] = Position(size, price)
def data_started(self, data):
pos = self.getposition(data)
if pos.size < 0:
order = SellOrder(data=data,
size=pos.size, price=pos.price,
exectype=Order.Market,
simulated=True)
order.addcomminfo(self.getcommissioninfo(data))
order.execute(0, pos.size, pos.price,
0, 0.0, 0.0,
pos.size, 0.0, 0.0,
0.0, 0.0,
pos.size, pos.price)
order.completed()
self.notify(order)
elif pos.size > 0:
order = BuyOrder(data=data,
size=pos.size, price=pos.price,
exectype=Order.Market,
simulated=True)
order.addcomminfo(self.getcommissioninfo(data))
order.execute(0, pos.size, pos.price,
0, 0.0, 0.0,
pos.size, 0.0, 0.0,
0.0, 0.0,
pos.size, pos.price)
order.completed()
self.notify(order)
def stop(self):
super(OandaBroker, self).stop()
self.o.stop()
def getcash(self):
self.cash = cash = self.o.get_cash()
return cash
def getvalue(self, datas=None):
self.value = self.o.get_value()
return self.value
def getposition(self, data, clone=True):
pos = self.positions[data._dataname]
if clone:
pos = pos.clone()
return pos
def orderstatus(self, order):
o = self.orders[order.ref]
return o.status
def _submit(self, oref):
order = self.orders[oref]
order.submit(self)
self.notify(order)
for o in self._bracketnotif(order):
o.submit(self)
self.notify(o)
def _reject(self, oref):
order = self.orders[oref]
order.reject(self)
self.notify(order)
self._bracketize(order, cancel=True)
def _accept(self, oref):
order = self.orders[oref]
order.accept()
self.notify(order)
for o in self._bracketnotif(order):
o.accept(self)
self.notify(o)
def _cancel(self, oref):
order = self.orders[oref]
order.cancel()
self.notify(order)
self._bracketize(order, cancel=True)
def _expire(self, oref):
order = self.orders[oref]
order.expire()
self.notify(order)
self._bracketize(order, cancel=True)
def _bracketnotif(self, order):
pref = getattr(order.parent, 'ref', order.ref)
br = self.brackets.get(pref, None)
return br[-2:] if br is not None else []
def _bracketize(self, order, cancel=False):
pref = getattr(order.parent, 'ref', order.ref)
br = self.brackets.pop(pref, None)
if br is None:
return
if not cancel:
if len(br) == 3:
br = br[1:]
for o in br:
o.activate()
self.brackets[pref] = br
elif len(br) == 2:
oidx = br.index(order)
self._cancel(br[1 - oidx].ref)
else:
for o in br:
if o.alive():
self._cancel(o.ref)
def _fill(self, oref, size, price, ttype, **kwargs):
order = self.orders[oref]
if not order.alive():
pref = getattr(order.parent, 'ref', order.ref)
if pref not in self.brackets:
msg = ('Order fill received for {}, with price {} and size {} '
'but order is no longer alive and is not a bracket. '
'Unknown situation')
msg.format(order.ref, price, size)
self.put_notification(msg, order, price, size)
return
if ttype == 'STOP_LOSS_FILLED':
order = self.brackets[pref][-2]
elif ttype == 'TAKE_PROFIT_FILLED':
order = self.brackets[pref][-1]
else:
msg = ('Order fill received for {}, with price {} and size {} '
'but order is no longer alive and is a bracket. '
'Unknown situation')
msg.format(order.ref, price, size)
self.put_notification(msg, order, price, size)
return
data = order.data
pos = self.getposition(data, clone=False)
psize, pprice, opened, closed = pos.update(size, price)
comminfo = self.getcommissioninfo(data)
closedvalue = closedcomm = 0.0
openedvalue = openedcomm = 0.0
margin = pnl = 0.0
order.execute(data.datetime[0], size, price,
closed, closedvalue, closedcomm,
opened, openedvalue, openedcomm,
margin, pnl,
psize, pprice)
if order.executed.remsize:
order.partial()
self.notify(order)
else:
order.completed()
self.notify(order)
self._bracketize(order)
def _transmit(self, order):
oref = order.ref
pref = getattr(order.parent, 'ref', oref)
if order.transmit:
if oref != pref:
takeside = order
parent, stopside = self.opending.pop(pref)
for o in parent, stopside, takeside:
self.orders[o.ref] = o
self.brackets[pref] = [parent, stopside, takeside]
self.o.order_create(parent, stopside, takeside)
return takeside
else:
self.orders[order.ref] = order
return self.o.order_create(order)
self.opending[pref].append(order)
return order
def buy(self, owner, data,
size, price=None, plimit=None,
exectype=None, valid=None, tradeid=0, oco=None,
trailamount=None, trailpercent=None,
parent=None, transmit=True,
**kwargs):
order = BuyOrder(owner=owner, data=data,
size=size, price=price, pricelimit=plimit,
exectype=exectype, valid=valid, tradeid=tradeid,
trailamount=trailamount, trailpercent=trailpercent,
parent=parent, transmit=transmit)
order.addinfo(**kwargs)
order.addcomminfo(self.getcommissioninfo(data))
return self._transmit(order)
def sell(self, owner, data,
size, price=None, plimit=None,
exectype=None, valid=None, tradeid=0, oco=None,
trailamount=None, trailpercent=None,
parent=None, transmit=True,
**kwargs):
order = SellOrder(owner=owner, data=data,
size=size, price=price, pricelimit=plimit,
exectype=exectype, valid=valid, tradeid=tradeid,
trailamount=trailamount, trailpercent=trailpercent,
parent=parent, transmit=transmit)
order.addinfo(**kwargs)
order.addcomminfo(self.getcommissioninfo(data))
return self._transmit(order)
def cancel(self, order):
o = self.orders[order.ref]
if order.status == Order.Cancelled:
return
return self.o.order_cancel(order)
def notify(self, order):
self.notifs.append(order.clone())
def get_notification(self):
if not self.notifs:
return None
return self.notifs.popleft()
def next(self):
self.notifs.append(None)
from backtrader.feed import DataBase
class MetaOandaData(DataBase.__class__):
def __init__(cls, name, bases, dct):
'''Class has already been created ... register'''
super(MetaOandaData, cls).__init__(name, bases, dct)
KabuSAPIStore.DataCls = cls
class OandaData(with_metaclass(MetaOandaData, DataBase)): FIXME
pass
class TestStrategy(bt.Strategy):
def log(self, txt, dt=None):
''' Logging function for this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
self.dataclose = self.datas[0].close
def next(self):
self.log('Close, %.2f' % self.dataclose[0])
import os
from datetime import datetime
if __name__ == '__main__':
cerebro = bt.Cerebro()
password = os.environ.get('PASSWORD')
kabusapistore = KabuSAPIStore(password = password)
kabusapistore.BrokerCls = OandaBroker()
kabusapistore.DataCls = OandaBroker()
data = kabusapistore.getdata(dataname='EUR_USD',
compression=1,
backfill=False,
fromdate=datetime(2018, 1, 1),
todate=datetime(2019, 1, 1),
qcheck=0.5,
timeframe=bt.TimeFrame.Minutes,
backfill_start=False,
historical=False)
cerebro.adddata(data)
cerebro.broker.setcash(10000.0)
cerebro.addstrategy(TestStrategy)
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
cerebro.run()
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())