File Coverage

blib/lib/Finance/InteractiveBrokers/TWS.pm
Criterion Covered Total %
statement 22 81 27.1
branch 0 6 0.0
condition n/a
subroutine 8 15 53.3
pod n/a
total 30 102 29.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Finance::InteractiveBrokers::TWS;
4              
5 2     2   3321 use version; $VERSION = qv('0.1.1');
  2         4586  
  2         9  
6              
7 2     2   133 use warnings;
  2         3  
  2         53  
8 2     2   18 use strict;
  2         4  
  2         42  
9 2     2   9 use File::Spec;
  2         3  
  2         48  
10 2     2   10274 use Data::Dumper;
  2         19392  
  2         177  
11              
12 2     2   1998 use Class::InsideOut qw(:std);
  2         14991  
  2         14  
13              
14             # Define class attributes
15             #
16             readonly api_path => my %api_path;
17             readonly IB_classes => my %IB_classes;
18             readonly api_spec => my %api_spec;
19             readonly java_src => my %java_src;
20             readonly jcode => my %jcode;
21             readonly eclient => my %eclient;
22              
23             sub new {
24              
25 0     0     my $self = register( shift );
26 0           my $callback = shift;
27              
28 0 0         $callback || die
29             "\n*** You MUST supply a callback to create a TWS object\n\n";
30              
31 0           $api_path {id $self} = $self->get_tws_api_install_path();
32 0           $IB_classes {id $self} = $self->get_list_ref_of_classes();
33 0           $api_spec {id $self} = $self->learn_EWrapper_spec();
34 0           $java_src {id $self} = $self->build_java_src();
35              
36 0           $self->compile_java_src();
37 0           $self->create_subroutines();
38              
39 0           my $jcode = Finance::InteractiveBrokers::TWS::Inline_Java->new($callback);
40 0           $jcode{id $self} = $jcode;
41              
42 0           my $eclient = $self->EClientSocket->new($jcode);
43 0           $eclient{id $self} = $eclient;
44              
45 0           $jcode->OpenCallbackStream();
46              
47 0           return $self;
48             }
49              
50             =begin build_java_src
51              
52             I need to build a Java class that looks like the following with a whole
53             bunch of duplicate methods, one for each event the TWS emits. Rather
54             than hard code the Java class, I build it on the fly by reading the
55             EWrapper as compiled by INLINE. Then I return the source which
56             looks like:
57              
58             import org.perl.inline.java.*;
59             import com.ib.client.*;
60              
61             class Inline_Java extends InlineJavaPerlCaller implements EWrapper {
62              
63             InlineJavaPerlObject perlobj;
64              
65             public Inline_Java(InlineJavaPerlObject PerlObj)
66             throws InlineJavaException {
67             perlobj = PerlObj;
68             }
69              
70             public void tickPrice(int tickerId, int field, double price,
71             int canAutoExecute)
72              
73             {
74             try {
75             perlobj.InvokeMethod("tickPrice", new Object [] {
76             tickerId, field, price, canAutoExecute });
77             }
78             catch (InlineJavaPerlException pe){ }
79             catch (InlineJavaException pe) { pe.printStackTrace() ;}
80             }
81             ...
82             ...
83             ...
84             }
85              
86             =end build_java_src
87              
88             =cut
89             sub build_java_src {
90              
91 0     0     my $self = shift;
92              
93 0           my $src = <<" END";
94             import org.perl.inline.java.*;
95             import com.ib.client.*;
96              
97             class Inline_Java extends InlineJavaPerlCaller implements EWrapper {
98              
99             InlineJavaPerlObject perlobj;
100              
101             public Inline_Java(InlineJavaPerlObject PerlObj)
102             throws InlineJavaException { perlobj = PerlObj; }
103              
104             END
105              
106 0           foreach my $method_def_ref (@{$self->api_spec()}) {
  0            
107              
108 0           my ($method_name, $parm_list_ref) = @$method_def_ref;
109            
110             # I need to remove the 'java.lang' from 'java.lang.String'
111             # and the 'com.ib.client' from 'com.ib.client.Contract'
112             # etc.. that gets stuck on some of the attributes
113 0           my @clean = map { (split/\./)[-1] } @{$parm_list_ref};
  0            
  0            
114              
115 0           my $str0 = join ',', map {$clean[$_]." var".$_} 0..$#clean;
  0            
116 0           my $str1 = join ',', map {"var".$_} 0..$#clean;
  0            
117            
118 0           $src .= sprintf("\tpublic void %s(%s) {
119             try {
120             perlobj.InvokeMethod(\"%s\", new Object [] {
121             %s
122             });
123             }
124             catch (InlineJavaPerlException pe){ }
125             catch (InlineJavaException pe) { pe.printStackTrace() ;}\n\n\t}",
126             $method_name, $str0, $method_name, $str1);
127             }
128              
129 0           $src .= '}';
130              
131 0           return $src;
132             }
133              
134              
135             =for compile_java_src
136             Take the Java source created in this module and compile it, also study
137             all the IB supplied classes, so we can use them.
138              
139             =cut
140             sub compile_java_src {
141              
142 0     0     my $self = shift;
143              
144             # Prepend 'com.ib.client' to each class, for proper pathing in STUDY
145 0           my @class_list = map {'com.ib.client.'.$_} @{$self->IB_classes()};
  0            
  0            
146              
147 0           Inline->bind(
148             Java => $self->java_src(),
149             AUTOSTUDY => 1,
150             STUDY => \@class_list,
151             );
152            
153 0           return 0;
154             }
155              
156             =begin create_subroutines
157              
158             I (at time of writing) create the following subroutines DYNAMICALLY:
159              
160             EClientErrors
161             AnyWrapper
162             Execution
163             EWrapperMsgGenerator
164             ExecutionFilter
165             EWrapper
166             EClientSocket
167             TickType
168             OrderState
169             EReader
170             ScannerSubscription
171             AnyWrapperMsgGenerator
172             ContractDetails
173             Order
174             ComboLeg
175             Util
176             EClientErrors$CodeMsgPair
177             Contract
178              
179             These are simple, convenience subs that call the IB supplied class. So that
180             the user (or me) can do:
181              
182             my $contract = $object->Contract(...);
183              
184             to create an IB contract instead of having to do
185            
186             my $contract =
187             Finance::InteractiveBrokers::TWS::com::ib::client::Contract->new(...);
188              
189              
190             The subroutines I create look like:
191              
192             sub Contract {
193             return __PACKAGE__.'::com::ib::client::Contract';
194             }
195              
196             =end create_subroutines
197              
198             =cut
199              
200             sub create_subroutines {
201              
202 0     0     my $self = shift;
203              
204             { # localize "no strict 'refs'" to this block
205 2     2   2435 no strict 'refs';
  2         4  
  2         807  
  0            
206            
207 0           foreach my $class_name (@{$self->IB_classes()}) {
  0            
208              
209 0           *{ $class_name } =
210 0     0     sub { return __PACKAGE__.'::com::ib::client::'.$class_name };
  0            
211              
212             }
213             }
214             }
215              
216             =for get_list_ref_of_classes
217             I read the list of files in the API directory whose name ends in
218             *.class, remove the .class from the name and return a list of class names
219              
220             =cut
221             sub get_list_ref_of_classes {
222              
223 0     0     my $self = shift;
224            
225 0 0         opendir(DIR, $self->api_path() ) || die
226             "can not opendir \'".$self->api_path(),"\': $!";
227              
228             # CAREFUL this grep uses a search and replace to remove ".class"
229             # from the filename in addition to the match
230 0           my @classes = grep { s/\.class// } readdir(DIR);
  0            
231              
232 0           closedir DIR;
233              
234 0           return \@classes;
235             }
236              
237             =for get_tws_api_install_path
238             get_tws_api_install_path, simply looks through the CLASSPATH environmental
239             variable and finds the path for the likely TWS installation, the way I do
240             it is probably not bullet proof, that is looking for a path with IBJts
241             or jts, but it works for me.
242              
243             =cut
244             sub get_tws_api_install_path {
245              
246 0     0     my $self = shift;
247              
248 0 0         defined $ENV{'CLASSPATH'} || die "\nCLASSPATH not set\n\n";
249 0           my ($API_base) = grep {/(IBJts)|(jts)/} split(/[:;]/, $ENV{CLASSPATH});
  0            
250              
251 0           my @path = File::Spec->splitdir($API_base);
252 0           push @path, qw[com ib client];
253 0           my $path = File::Spec->catfile(@path);
254              
255 0           return $path;
256             }
257              
258             =for learn_EWrapper_spec
259             I complile the IB supplied EWrapper with Inline::Java, and then plumb
260             the debths of the structure created to learn the methods within the EWrapper
261             and number and type of parameters to each method, so that I may later
262             use that info, to dynamically build my own Wrapper
263              
264             =cut
265             sub learn_EWrapper_spec {
266              
267             my $self = shift;
268              
269             use Inline (
270 0           Java => 'STUDY',
271             AUTOSTUDY => 1,
272             STUDY => ['com.ib.client.EWrapper'],
273 2     2   2258 );
  0            
274              
275             my $package_name = __PACKAGE__.'::com::ib::client::EWrapper';
276             my $inlines = (Inline::Java::__get_INLINES)[0]->[0]{ILSM}{data}[1];
277              
278             my $ewrapper_methods_ref = $inlines->{classes}{$package_name}{methods};
279              
280             my @spec = ();
281              
282             # Build a [[method_name, @parms], [method_name, @parms]]
283             while (my ($method_name, $value) = each %{$ewrapper_methods_ref}) {
284              
285             while (my ($key, $ivalue) = each %{$value}) {
286            
287             push @spec, [$method_name, $ivalue->{SIGNATURE}]
288             if $ivalue->{SIGNATURE};
289             }
290             }
291              
292             return \@spec;
293             }
294              
295             =for read_messages_for_x_sec
296             Call our implementation of EWrapper to process the messages emitted from
297             the TWS. When the messages are read it will trigger the code in the callback
298             supplied by the user.
299              
300             =cut
301             sub read_messages_for_x_sec {
302              
303             my ($self, $wait) = @_;
304              
305             $wait ||= .05;
306             my $jcode = $self->jcode();
307              
308             my $num_callbacks_processed = 0;
309             while ($jcode->WaitForCallback($wait)) {
310             $jcode->ProcessNextCallback();
311             $num_callbacks_processed++;
312             }
313              
314             return $num_callbacks_processed;
315             }
316              
317             1;
318              
319             __END__