-
Notifications
You must be signed in to change notification settings - Fork 35
/
swigify_ib.i
396 lines (334 loc) · 12.9 KB
/
swigify_ib.i
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
/*
SWIG interface file for Interactive Brokers API.
*/
%module(directors="1", docstring="Python wrapper for Interactive Brokers TWS C++ API") swigibpy
/* Turn on auto-generated docstrings */
%feature("autodoc", "1");
/* auto convert std::string and typedefs to Python strings */
%include "std_string.i"
typedef std::string IBString;
/* auto convert std::vector to Python lists */
%include "std_vector.i"
/* use boost template to generate shared pointer handling */
%include <boost_shared_ptr.i>
/* Inclusions for generated cpp file */
%{
#include "shared/shared_ptr.h"
#include "shared/IBString.h"
#include "shared/EClient.h"
#include "shared/EClientSocketBase.h"
#include "src/EPosixClientSocket.h"
#include "shared/EWrapper.h"
#include "shared/CommissionReport.h"
#include "shared/CommonDefs.h"
#include "shared/Contract.h"
#include "shared/Execution.h"
#include "shared/Order.h"
#include "shared/OrderState.h"
#include "shared/ScannerSubscription.h"
#include "shared/TagValue.h"
%}
/* Shared Pointers */
%shared_ptr(ComboLeg)
%shared_ptr(std::vector<boost::shared_ptr<ComboLeg> >)
%shared_ptr(OrderComboLeg)
%shared_ptr(std::vector<boost::shared_ptr<OrderComboLeg> >)
%shared_ptr(TagValue)
%shared_ptr(std::vector<boost::shared_ptr<TagValue> >)
/* Vector (list) containers */
%template(ComboLegList) std::vector<boost::shared_ptr<ComboLeg> >;
%template(OrderComboLegList) std::vector<boost::shared_ptr<OrderComboLeg> >;
%template(TagValueList) std::vector<boost::shared_ptr<TagValue> >;
/*
TWS use their own shared_ptr class, need to fool SWIG into thinking
it's the standard boost::shared_ptr.
This means the generated cpp file must be post-processed to remove
references to boost (see setup.py).
*/
#define shared_ptr boost::shared_ptr
/*EWrapper will be subclassed in Python*/
%feature("director") EWrapper;
%feature("director:except") {
if ($error != NULL) {
if ( !( PyErr_ExceptionMatches(PyExc_SystemExit) ||
PyErr_ExceptionMatches(PyExc_SystemError) ||
PyErr_ExceptionMatches(PyExc_KeyboardInterrupt) ) )
{
PyObject *value = 0;
PyObject *traceback = 0;
PyErr_Fetch(&$error, &value, &traceback);
PyErr_NormalizeException(&$error, &value, &traceback);
{
if (value == NULL) {
value = Py_None;
}
if (traceback == NULL) {
traceback = Py_None;
}
swig::SwigVar_PyObject swig_method_name = SWIG_Python_str_FromChar((char *) "pyError");
swig::SwigVar_PyObject result = PyObject_CallMethodObjArgs(swig_get_self(), (PyObject *) swig_method_name, $error, value, traceback, NULL);
}
Py_XDECREF($error);
Py_XDECREF(value);
Py_XDECREF(traceback);
$error = PyErr_Occurred();
if ($error != NULL) {
PyErr_Print();
throw Swig::DirectorMethodException();
}
}
else
{
throw Swig::DirectorMethodException();
}
}
}
/* Exception handling */
%include exception.i
%exception {
// most errors should be propagated through to EWrapper->error,
// others should be added here as and when needed / encountered.
try {
$action
} catch(Swig::DirectorPureVirtualException &e) {
/* Call to pure virtual method, raise not implemented error */
PyErr_SetString(PyExc_NotImplementedError, "$decl not implemented");
SWIG_fail;
} catch(Swig::DirectorException &e) {
/* Fail if there is a problem in the director proxy transport */
SWIG_fail;
} catch(std::exception& e) {
/* Convert standard error to Exception */
PyErr_SetString(PyExc_Exception, const_cast<char*>(e.what()));
SWIG_fail;
} catch(...) {
/* Final catch all, results in runtime error */
PyErr_SetString(PyExc_RuntimeError, "Unknown error caught in Interactive Brokers SWIG wrapper...");
SWIG_fail;
}
}
/* Grab the header files to be wrapped */
%include "shared/CommissionReport.h"
%include "shared/CommonDefs.h"
%include "shared/Contract.h"
%include "shared/EClient.h"
%include "shared/EClientSocketBase.h"
%include "shared/Execution.h"
%include "shared/Order.h"
%include "shared/OrderState.h"
%include "shared/ScannerSubscription.h"
%include "shared/TagValue.h"
%pythonbegin %{
import warnings
import sys
import threading
import select
from traceback import print_exc, print_exception
%}
/* Customise EPosixClientSocket so that TWS is automatically polled for messages when we are connected to it */
%pythoncode %{
class TWSPoller(threading.Thread):
'''Continually polls TWS for any outstanding messages.
Loops indefinitely until killed or a system error occurs.
Uses socket select to poll for input and calls TWS's
`EClientSocketBase::checkMessages` function.
'''
MAX_BACKOFF = 5000
def __init__(self, tws, wrapper):
super(TWSPoller, self).__init__()
self.daemon = True
self._tws = tws
self._wrapper = wrapper
self._stop_evt = threading.Event()
self._connected_evt = threading.Event()
self.tws_connected(tws.isConnected())
def stop_poller(self):
self._stop_evt.set()
def tws_connected(self, flag):
if flag:
self._connected_evt.set()
else:
self._connected_evt.clear()
def run(self):
modules = sys.modules
try:
self._run()
except:
# ignore errors raised during interpreter shutdown.
if modules:
raise
def _run(self):
'''Continually poll TWS'''
stop = self._stop_evt
connected = self._connected_evt
tws = self._tws
fd = tws.fd()
pollfd = [fd]
while not stop.is_set():
while (not connected.is_set() or not tws.isConnected()) and not stop.is_set():
connected.clear()
backoff = 0
retries = 0
while not connected.is_set() and not stop.is_set():
if tws.reconnect_auto and not tws.reconnect():
if backoff < self.MAX_BACKOFF:
retries += 1
backoff = min(2**(retries + 1), self.MAX_BACKOFF)
connected.wait(backoff / 1000.)
else:
connected.wait(1)
fd = tws.fd()
pollfd = [fd]
if fd > 0:
try:
evtin, _evtout, evterr = select.select(pollfd, [], pollfd, 1)
except select.error:
connected.clear()
continue
else:
if fd in evtin:
try:
if not tws.checkMessages():
tws.eDisconnect(stop_polling=False)
continue
except (SystemExit, SystemError, KeyboardInterrupt):
break
except:
try:
self._wrapper.pyError(*sys.exc_info())
except:
print_exc()
elif fd in evterr:
connected.clear()
continue
%}
%feature("shadow") EPosixClientSocket::EPosixClientSocket(EWrapper *ptr) %{
def __init__(self, ewrapper, poll_auto=True, reconnect_auto=False):
'''Create an EPosixClientSocket to comunicate with Interactive Brokers.
Parameters
----------
ewrapper : EWrapper subclass to which responses will be dispatched.
poll_auto : boolean, if True automatically poll for messages with a
background thread. Default True
reconnect_auto : boolean, if True automatically reconnect to TWS if
the connection is lost. Default False
'''
_swigibpy.EPosixClientSocket_swiginit(self, $action(ewrapper))
# store a reference to EWrapper on the Python side (C++ member is protected so inaccessible from Python).
self._ewrapper = ewrapper
self._connect_lock = threading.Lock()
self.poller = None
self._poll_auto = poll_auto
self.reconnect_auto = reconnect_auto
self._connect_args = None
%}
%feature("shadow") EClientSocketBase::eConnect(const char *host, unsigned int port, int clientId=0, bool extraAuth=false) %{
def eConnect(self, host, port, clientId=0, extraAuth=False, **kwargs):
if "poll_auto" in kwargs:
warnings.warn("eConnect argument 'poll_auto' is deprecated, use 'poll_auto' arg in constructor instead", DeprecationWarning)
self.poll_auto = kwargs.pop('poll_auto')
with self._connect_lock:
success = $action(self, host, port, clientId, extraAuth)
if success:
self._connect_args = ((host, port, clientId, extraAuth), kwargs)
if self.isConnected():
self._startPolling()
if self.poller is not None:
self.poller.tws_connected(True)
return success
%}
%feature("shadow") EClientSocketBase::eDisconnect() %{
def eDisconnect(self, stop_polling=True):
if stop_polling:
self._stopPolling()
val = $action(self)
if self.poller is not None:
self.poller.tws_connected(False)
return val
%}
%extend EPosixClientSocket {
%pythoncode {
def reconnect(self):
if self._connect_args is None:
return
return self.eConnect(*self._connect_args[0], **self._connect_args[1])
def _startPolling(self):
if not self.poll_auto:
return
if self.poller is None or not self.poller.is_alive():
self.poller = TWSPoller(self, self._ewrapper)
self.poller.start()
def _stopPolling(self):
if self.poller is not None:
self.poller.stop_poller()
@property
def poll_auto(self):
return self._poll_auto
@poll_auto.setter
def poll_auto(self, val):
self._poll_auto = val
if val:
self._startPolling()
else:
self._stopPolling()
}
}
%include "src/EPosixClientSocket.h"
%feature("shadow") EWrapper::winError(const IBString &, int) %{
def winError(self, str, lastError):
'''Error in TWS API library'''
sys.stderr.write("TWS ERROR - %s: %s\n" % (lastError, str))
%}
%feature("shadow") EWrapper::error(const int, const int, const IBString) %{
def error(self, id, errorCode, errorString):
'''Error during communication with TWS'''
if errorCode == 165: # Historical data sevice message
sys.stderr.write("TWS INFO - %s: %s\n" % (errorCode, errorString))
elif errorCode >= 501 and errorCode < 600: # Socket read failed
sys.stderr.write("TWS CLIENT-ERROR - %s: %s\n" % (errorCode, errorString))
elif errorCode >= 100 and errorCode < 1100:
sys.stderr.write("TWS ERROR - %s: %s\n" % (errorCode, errorString))
elif errorCode >= 1100 and errorCode < 2100:
sys.stderr.write("TWS SYSTEM-ERROR - %s: %s\n" % (errorCode, errorString))
elif errorCode in (2104, 2106, 2108):
sys.stderr.write("TWS INFO - %s: %s\n" % (errorCode, errorString))
elif errorCode >= 2100 and errorCode <= 2110:
sys.stderr.write("TWS WARNING - %s: %s\n" % (errorCode, errorString))
else:
sys.stderr.write("TWS ERROR - %s: %s\n" % (errorCode, errorString))
%}
%extend EWrapper {
%pythoncode {
def pyError(self, type, value, traceback):
'''Handles an error thrown during invocation of an EWrapper method.
Arguments are those provided by sys.exc_info()
'''
sys.stderr.write("Exception thrown during EWrapper method dispatch:\n")
print_exception(type, value, traceback)
}
}
%include "shared/EWrapper.h"
%pythoncode %{
class EWrapperVerbose(EWrapper):
'''Implements all EWrapper methods and prints to standard out when a method
is invoked.
'''
def _print_call(self, name, *args, **kwargs):
argspec = []
if args:
argspec.append(', '.join(str(a) for a in args))
if kwargs:
argspec.append(', '.join('%s=%s' for k, v in kwargs.items()))
print('TWS call ignored - %s(%s)' % (name, ', '.join(argspec)))
class EWrapperQuiet(EWrapper):
'''Implements all EWrapper methods and ignores method calls.'''
def _ignore_call(self, *args, **kwargs):
pass
def _make_printer(name):
return lambda self, *a, **kw: self._print_call(name, *a, **kw)
for name, attr in EWrapper.__dict__.items():
if name[0] == '_' or not callable(attr) or name in ('error', 'winError', 'pyError'):
continue
setattr(EWrapperQuiet, name, EWrapperQuiet.__dict__['_ignore_call'])
setattr(EWrapperVerbose, name, _make_printer(name))
%}