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'''
from IPython.core.debugger import Pdb; Pdb().set_trace()
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)
class OandaBroker(): FIXME
pass
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()
kabusapistoretore.DataCls = OandaBroker()
data = oandastore.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())