ib-ruby 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
data/bin/HistoricToCSV ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env ruby -w
2
+ #
3
+ # Copyright (C) 2009 Wes Devauld
4
+ #
5
+ # This library is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU Lesser General Public License as
7
+ # published by the Free Software Foundation; either version 2.1 of the
8
+ # License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful, but
11
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18
+ # 02110-1301 USA
19
+ #
20
+ # TODO Fix the Historical command line client
21
+ #
22
+
23
+ require File.expand_path(
24
+ File.join(File.dirname(__FILE__), '..', 'lib', 'ib-ruby'))
25
+
26
+ ### Configurable Options
27
+
28
+ # if Quiet == false, status data will be printed to STDERR
29
+ Quiet = false
30
+
31
+ # How long to wait when no messages are received from TWS before
32
+ # exiting, in seconds
33
+ Timeout = 10
34
+
35
+ SymbolToRequest = IB::Symbols::Forex[:gbpusd]
36
+
37
+ ### end options
38
+
39
+
40
+ #
41
+ # Definition of what we want market data for. We have to keep track
42
+ # of what ticker id corresponds to what symbol ourselves, because the
43
+ # ticks don't include any other identifying information.
44
+ #
45
+ # The choice of ticker ids is, as far as I can tell, arbitrary.
46
+ #
47
+ # Note that as of 4/07 there is no historical data available for forex spot.
48
+ #
49
+ @market =
50
+ {
51
+ 123 => SymbolToRequest
52
+ }
53
+
54
+ # To determine when the timeout has passed.
55
+ @last_msg_time = Time.now.to_i + 2
56
+
57
+ # Connect to IB TWS.
58
+ ib = IB::IB.new
59
+
60
+ # Uncomment this for verbose debug messages:
61
+ # IB::IBLogger.level = Logger::Severity::DEBUG
62
+
63
+ #
64
+ # Now, subscribe to HistoricalData incoming events. The code
65
+ # passed in the block will be executed when a message of that type is
66
+ # received, with the received message as its argument. In this case,
67
+ # we just print out the data.
68
+ #
69
+ # Note that we have to look the ticker id of each incoming message
70
+ # up in local memory to figure out what it's for.
71
+ #
72
+ # (N.B. The description field is not from IB TWS. It is defined
73
+ # locally in forex.rb, and is just arbitrary text.)
74
+
75
+
76
+ ib.subscribe(IB::IncomingMessages::HistoricalData, lambda {|msg|
77
+
78
+ STDERR.puts @market[msg.data[:req_id]].description + ": " + msg.data[:item_count].to_s("F") + " items:" unless Quiet
79
+
80
+ msg.data[:history].each { |datum|
81
+
82
+ @last_msg_time = Time.now.to_i
83
+
84
+ STDERR.puts " " + datum.to_s("F") unless Quiet
85
+ STDOUT.puts "#{datum.date},#{datum.open.to_s("F")},#{datum.high.to_s("F")},#{datum.low.to_s("F")},#{datum.close.to_s("F")},#{datum.volume}"
86
+ }
87
+ })
88
+
89
+ # Now we actually request historical data for the symbols we're
90
+ # interested in. TWS will respond with a HistoricalData message,
91
+ # which will be received by the code above.
92
+
93
+ @market.each_pair {|id, contract|
94
+ msg = IB::OutgoingMessages::RequestHistoricalData.new({
95
+ :ticker_id => id,
96
+ :contract => contract,
97
+ :end_date_time => Time.now.to_ib,
98
+ :duration => (360).to_s, # how long before end_date_time to request in seconds - this means 1 day
99
+ :bar_size => IB::OutgoingMessages::RequestHistoricalData::BarSizes.index(:hour),
100
+ :what_to_show => :trades,
101
+ :use_RTH => 0,
102
+ :format_date => 2
103
+ })
104
+ ib.dispatch(msg)
105
+ }
106
+
107
+
108
+ while true
109
+ exit(0) if Time.now.to_i > @last_msg_time + Timeout
110
+ sleep 1
111
+ end
@@ -0,0 +1,301 @@
1
+ #!/usr/bin/env ruby -w
2
+ #
3
+ # Copyright (C) 2009 Wes Devauld
4
+ #
5
+ # This library is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU Lesser General Public License as
7
+ # published by the Free Software Foundation; either version 2.1 of the
8
+ # License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful, but
11
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18
+ # 02110-1301 USA
19
+ #
20
+
21
+ require File.expand_path(
22
+ File.join(File.dirname(__FILE__), '..', 'lib', 'ib-ruby'))
23
+
24
+ require 'rubygems'
25
+ require 'time'
26
+ require 'duration'
27
+ require 'getopt/long'
28
+
29
+ include Getopt
30
+
31
+ opt = Getopt::Long.getopts(
32
+ ["--help", BOOLEAN],
33
+ ["--end", REQUIRED],
34
+ ["--security", REQUIRED],
35
+ ["--duration", REQUIRED],
36
+ ["--barsize", REQUIRED],
37
+ ["--header",BOOLEAN],
38
+ ["--dateformat", REQUIRED],
39
+ ["--nonregularhours", BOOLEAN],
40
+ ["--verbose", BOOLEAN],
41
+ ["--veryverbose", BOOLEAN]
42
+ )
43
+
44
+ if opt["help"] || opt["security"].nil? || opt["security"].empty?
45
+ puts <<ENDHELP
46
+
47
+ ** RequestHistoricData.rb - Copyright (C) 2007-8 Paul Legato.
48
+
49
+ This library is free software; you can redistribute it and/or modify
50
+ it under the terms of the GNU Lesser General Public License as
51
+ published by the Free Software Foundation; either version 2.1 of the
52
+ License, or (at your option) any later version.
53
+
54
+ This library is distributed in the hope that it will be useful, but
55
+ WITHOUT ANY WARRANTY; without even the implied warranty of
56
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
57
+ Lesser General Public License for more details.
58
+
59
+ You should have received a copy of the GNU Lesser General Public
60
+ License along with this library; if not, write to the Free Software
61
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
62
+ 02110-1301 USA
63
+
64
+ The author and this software are not connected with Interactive
65
+ Brokers in any way, nor do they endorse us.
66
+
67
+ ************************************************************************************
68
+
69
+ >> YOUR USE OF THIS PROGRAM IS ENTIRELY AT YOUR OWN RISK. <<
70
+ >> IT MAY CONTAIN POTENTIALLY COSTLY BUGS, ERRORS, ETC., BOTH KNOWN AND UNKNOWN. <<
71
+ >> DO NOT USE THIS SOFTWARE IF YOU ARE UNWILLING TO ACCEPT ALL RISK IN DOING SO. <<
72
+
73
+ ************************************************************************************
74
+
75
+
76
+ This program requires a TWS running on localhost on the standard port
77
+ that uses API protocol version 15 or higher. Any modern TWS should
78
+ work. (Patches to make it work on an arbitrary host/port are welcome.)
79
+
80
+ ----------
81
+
82
+ One argument is required: --security, the security specification you want, in
83
+ "long serialized IB-Ruby" format. This is a colon-separated string of the format:
84
+
85
+ symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
86
+
87
+ Fields not needed for a particular security should be left blank (e.g. strike and right are only relevant for options.)
88
+
89
+ For example, to query the British pound futures contract trading on Globex expiring in September, 2008,
90
+ the correct command line is:
91
+
92
+ ./RequestHistoricData.rb --security GBP:FUT:200809:::62500:GLOBEX::USD:
93
+
94
+ Consult datatypes.rb for allowed values, and see also the examples in the symbols/ directory (load them in
95
+ irb and run security#serialize_ib_ruby(ib_version) to see the appropriate string.)
96
+
97
+ ***
98
+
99
+ Options:
100
+
101
+ --end is is the last time we want data for. The default is now.
102
+ This is eval'ed by Ruby, so you can use a Ruby expression, which must return a Time object.
103
+
104
+
105
+ --duration is how much historic data we want, in seconds, before --end's time.
106
+ The default is 86400 (seconds, which is 1 day.)
107
+ The TWS-imposed limit is 86400 (1 day per request.) Requests for more than 86400 seconds worth of historic data will fail.
108
+
109
+ --what determines what the data will be comprised of. This can be "trades", "midpoint", "bid", or "asked".
110
+ The default is "trades".
111
+
112
+ --barsize determines how long each bar will be.
113
+
114
+ Possible values (from the IB documentation):
115
+
116
+ 1 = 1 sec
117
+ 2 = 5 sec
118
+ 3 = 15 sec
119
+ 4 = 30 sec
120
+ 5 = 1 minute
121
+ 6 = 2 minutes
122
+ 7 = 5 minutes
123
+ 8 = 15 minutes
124
+ 9 = 30 minutes
125
+ 10 = 1 hour
126
+ 11 = 1 day
127
+
128
+ Values less than 4 do not appear to work for some securities.
129
+ The default is 8, 15 minutes.
130
+
131
+ --nonregularhours :
132
+ Normally, only data from the instrument's regular trading hours is returned.
133
+ If --nonregularhours is given, all data available during the time
134
+ span requested is returned, even data bars covering time
135
+ intervals where the market in question was illiquid. If
136
+
137
+
138
+ --dateformat : a --dateformat of 1 will cause the dates in the returned
139
+ messages with the historic data to be in a text format, like
140
+ "20050307 11:32:16". If you set it to 2 instead, you
141
+ will get an offset in seconds from the beginning of 1970, which
142
+ is the same format as the UNIX epoch time.
143
+
144
+ The default is 1 (human-readable time.)
145
+
146
+ --header : if present, prints a 1 line CSV header describing the fields in the CSV.
147
+
148
+ --veryverbose : if present, prints very verbose debugging info.
149
+ --verbose : if present, prints all messages received from IB, and print the data in human-readable
150
+ format.
151
+
152
+ Otherwise, in the default mode, prints only the historic data (and any errors), and prints the
153
+ data in CSV format.
154
+
155
+ ENDHELP
156
+ #' <- fix broken syntax highlighting in Aquamacs
157
+ exit
158
+
159
+ end
160
+
161
+ ### Parameters
162
+
163
+ # DURATION is how much historic data we want, in seconds, before END_DATE_TIME.
164
+ # (The 'duration' gem gives us methods like #hour on integers.)
165
+ DURATION = (opt["duration"] && opt["duration"].to_i) || 1.day
166
+
167
+ if DURATION > 86400
168
+ STDERR.puts("\nTWS does not accept a --duration longer than 86400 seconds (1 day.) Please try again with a smaller duration.\n\n")
169
+ exit(1)
170
+ end
171
+
172
+
173
+ # This is the last time we want data for.
174
+ END_DATE_TIME = (opt["end"] && eval(opt["end"]).to_ib) || Time.now.to_ib
175
+
176
+
177
+ # This can be :trades, :midpoint, :bid, or :asked
178
+ WHAT = (opt["what"] && opt["what"].to_sym) || :trades
179
+
180
+ # Possible bar size values:
181
+ # 1 = 1 sec
182
+ # 2 = 5 sec
183
+ # 3 = 15 sec
184
+ # 4 = 30 sec
185
+ # 5 = 1 minute
186
+ # 6 = 2 minutes
187
+ # 7 = 5 minutes
188
+ # 8 = 15 minutes
189
+ # 9 = 30 minutes
190
+ # 10 = 1 hour
191
+ # 11 = 1 day
192
+ #
193
+ # Values less than 4 do not appear to actually work; they are rejected by the server.
194
+ #
195
+ BAR_SIZE = (opt["barsize"] && opt["barsize"].to_i) || 8
196
+
197
+ # If REGULAR_HOURS_ONLY is set to 0, all data available during the time
198
+ # span requested is returned, even data bars covering time
199
+ # intervals where the market in question was illiquid. If useRTH
200
+ # has a non-zero value, only data within the "Regular Trading
201
+ # Hours" of the product in question is returned, even if the time
202
+ # span requested falls partially or completely outside of them.
203
+
204
+ REGULAR_HOURS_ONLY = opt["nonregularhours"] ? 0 : 1
205
+
206
+ # Using a DATE_FORMAT of 1 will cause the dates in the returned
207
+ # messages with the historic data to be in a text format, like
208
+ # "20050307 11:32:16". If you set :format_date to 2 instead, you
209
+ # will get an offset in seconds from the beginning of 1970, which
210
+ # is the same format as the UNIX epoch time.
211
+
212
+ DATE_FORMAT = (opt["dateformat"] && opt["dateformat"].to_i) || 1
213
+
214
+ VERYVERBOSE = !opt["veryverbose"].nil?
215
+ VERBOSE = !opt["verbose"].nil?
216
+
217
+ #
218
+ # Definition of what we want market data for. We have to keep track
219
+ # of what ticker id corresponds to what symbol ourselves, because the
220
+ # ticks don't include any other identifying information.
221
+ #
222
+ # The choice of ticker ids is, as far as I can tell, arbitrary.
223
+ #
224
+ # Note that as of 4/07 there is no historical data available for forex spot.
225
+ #
226
+ @market =
227
+ {
228
+ 123 => opt["security"]
229
+ }
230
+
231
+
232
+ # First, connect to IB TWS.
233
+ ib = IB::IB.new
234
+
235
+
236
+ # Default level is quiet, only warnings printed.
237
+ # IB::IBLogger.level = Logger::Severity::ERROR
238
+
239
+ # For verbose printing of each message:
240
+ # IB::IBLogger.level = Logger::Severity::INFO if VERBOSE
241
+
242
+ # For very verbose debug messages:
243
+ # IB::IBLogger.level = Logger::Severity::DEBUG if VERYVERBOSE
244
+
245
+ puts "datetime,open,high,low,close,volume,wap,has_gaps" if !opt["header"].nil?
246
+
247
+ lastMessageTime = Queue.new # for communicating with the reader thread.
248
+
249
+ #
250
+ # Subscribe to incoming HistoricalData events. The code passed in the
251
+ # block will be executed when a message of the subscribed type is
252
+ # received, with the received message as its argument. In this case,
253
+ # we just print out the data.
254
+ #
255
+ # Note that we have to look the ticker id of each incoming message
256
+ # up in local memory to figure out what security it relates to.
257
+ # The incoming message packet from TWS just identifies it by ticker id.
258
+ #
259
+ ib.subscribe(IB::IncomingMessages::HistoricalData, lambda {|msg|
260
+ STDERR.puts @market[msg.data[:req_id]].description + ": " + msg.data[:item_count].to_s("F") + " items:" if VERBOSE
261
+
262
+ msg.data[:history].each { |datum|
263
+ puts(if VERBOSE
264
+ datum.to_s
265
+ else
266
+ "#{datum.date},#{datum.open.to_s("F")},#{datum.high.to_s("F")},#{datum.low.to_s("F")}," +
267
+ "#{datum.close.to_s("F")},#{datum.volume},#{datum.wap.to_s("F")},#{datum.has_gaps}"
268
+ end
269
+ )
270
+ }
271
+ lastMessageTime.push(Time.now)
272
+ })
273
+
274
+ # Now we actually request historical data for the symbols we're
275
+ # interested in. TWS will respond with a HistoricalData message,
276
+ # which will be received by the code above.
277
+
278
+ @market.each_pair {|id, contract|
279
+ msg = IB::OutgoingMessages::RequestHistoricalData.new({
280
+ :ticker_id => id,
281
+ :contract => contract,
282
+ :end_date_time => END_DATE_TIME,
283
+ :duration => DURATION, # seconds == 1 hour
284
+ :bar_size => BAR_SIZE, # 1 minute bars
285
+ :what_to_show => WHAT,
286
+ :use_RTH => REGULAR_HOURS_ONLY,
287
+ :format_date => DATE_FORMAT
288
+ })
289
+ ib.dispatch(msg)
290
+ }
291
+
292
+
293
+ # A complication here is that IB does not send any indication when all historic data is done being delivered.
294
+ # So we have to guess - when there is no more new data for some period, we interpret that as "end of data" and exit.
295
+
296
+ while true
297
+ lastTime = lastMessageTime.pop # blocks until a message is ready on the queue
298
+ sleep 2 # .. wait ..
299
+ exit if lastMessageTime.empty? # if still no more messages after 2 more seconds, exit.
300
+ end
301
+
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env ruby -w
2
+ #
3
+ # Copyright (C) 2009 Wes Devauld
4
+ #
5
+ # This library is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU Lesser General Public License as
7
+ # published by the Free Software Foundation; either version 2.1 of the
8
+ # License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful, but
11
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18
+ # 02110-1301 USA
19
+ #
20
+
21
+ require File.expand_path(
22
+ File.join(File.dirname(__FILE__), '..', 'lib', 'ib-ruby'))
23
+
24
+ #
25
+ # Definition of what we want market data for. We have to keep track
26
+ # of what ticker id corresponds to what symbol ourselves, because the
27
+ # ticks don't include any other identifying information.
28
+ #
29
+ # The choice of ticker ids is, as far as I can tell, arbitrary.
30
+ #
31
+ @market =
32
+ {
33
+ 123 => IB::Symbols::Forex[:gbpusd],
34
+ 456 => IB::Symbols::Forex[:eurusd],
35
+ 789 => IB::Symbols::Forex[:usdcad]
36
+ }
37
+
38
+
39
+ # First, connect to IB TWS.
40
+ ib = IB::IB.new
41
+
42
+
43
+ #
44
+ # Now, subscribe to TickerPrice and TickerSize events. The code
45
+ # passed in the block will be executed when a message of that type is
46
+ # received, with the received message as its argument. In this case,
47
+ # we just print out the tick.
48
+ #
49
+ # Note that we have to look the ticker id of each incoming message
50
+ # up in local memory to figure out what it's for.
51
+ #
52
+ # (N.B. The description field is not from IB TWS. It is defined
53
+ # locally in forex.rb, and is just arbitrary text.)
54
+
55
+ ib.subscribe(IB::IncomingMessages::TickPrice, lambda {|msg|
56
+ puts @market[msg.data[:ticker_id]].description + ": " + msg.to_human
57
+ })
58
+
59
+ ib.subscribe(IB::IncomingMessages::TickSize, lambda {|msg|
60
+ puts @market[msg.data[:ticker_id]].description + ": " + msg.to_human
61
+ })
62
+
63
+
64
+ # Now we actually request market data for the symbols we're interested in.
65
+
66
+ @market.each_pair {|id, contract|
67
+ msg = IB::OutgoingMessages::RequestMarketData.new({
68
+ :ticker_id => id,
69
+ :contract => contract
70
+ })
71
+ ib.dispatch(msg)
72
+ }
73
+
74
+
75
+ puts "Main thread going to sleep. Press ^C to quit.."
76
+ while true
77
+ sleep 2
78
+ end
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env ruby -w
2
+ #
3
+ # Copyright (C) 2009 Wes Devauld
4
+ #
5
+ # This library is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU Lesser General Public License as
7
+ # published by the Free Software Foundation; either version 2.1 of the
8
+ # License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful, but
11
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18
+ # 02110-1301 USA
19
+ #
20
+
21
+ require File.expand_path(
22
+ File.join(File.dirname(__FILE__), '..', 'lib', 'ib-ruby'))
23
+
24
+ # First, connect to IB TWS.
25
+ ib = IB::IB.new
26
+
27
+ # Uncomment this for verbose debug messages:
28
+ # IB::IBLogger.level = Logger::Severity::DEBUG
29
+
30
+ # Define the symbols we're interested in.
31
+ @market =
32
+ {
33
+ 123 => IB::Symbols::Futures[:gbp],
34
+ 234 => IB::Symbols::Futures[:jpy]
35
+ }
36
+
37
+
38
+ # This method filters out non-:last type events, and filters out any
39
+ # sale < MIN_SIZE.
40
+ MIN_SIZE = 0
41
+
42
+ def showSales(msg)
43
+ return if msg.data[:type] != :last || msg.data[:size] < MIN_SIZE
44
+ puts @market[msg.data[:ticker_id]].description + ": " + msg.data[:size].to_s("F") + " at " + msg.data[:price].to_s("F")
45
+ end
46
+
47
+ def showSize(msg)
48
+ puts @market[msg.data[:ticker_id]].description + ": " + msg.to_human
49
+ end
50
+
51
+
52
+ #
53
+ # Now, subscribe to TickerPrice and TickerSize events. The code
54
+ # passed in the block will be executed when a message of that type is
55
+ # received, with the received message as its argument. In this case,
56
+ # we just print out the tick.
57
+ #
58
+ # Note that we have to look the ticker id of each incoming message
59
+ # up in local memory to figure out what it's for.
60
+ #
61
+ # (N.B. The description field is not from IB TWS. It is defined
62
+ # locally in forex.rb, and is just arbitrary text.)
63
+
64
+ ib.subscribe(IB::IncomingMessages::TickPrice, lambda {|msg|
65
+ showSales(msg)
66
+ })
67
+
68
+ ib.subscribe(IB::IncomingMessages::TickSize, lambda {|msg|
69
+ showSize(msg)
70
+ })
71
+
72
+
73
+ # Now we actually request market data for the symbols we're interested in.
74
+
75
+ @market.each_pair {|id, contract|
76
+ msg = IB::OutgoingMessages::RequestMarketData.new({
77
+ :ticker_id => id,
78
+ :contract => contract
79
+ })
80
+ ib.dispatch(msg)
81
+ }
82
+
83
+
84
+ puts "\n\n\t******** Press <Enter> to quit.. *********\n\n"
85
+
86
+ gets
87
+
88
+ puts "Unsubscribing from TWS market data.."
89
+
90
+ @market.each_pair {|id, contract|
91
+ msg = IB::OutgoingMessages::CancelMarketData.new({
92
+ :ticker_id => id,
93
+ })
94
+ ib.dispatch(msg)
95
+ }
96
+
97
+ puts "Done."
98
+
data/bin/ib-ruby ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(
4
+ File.join(File.dirname(__FILE__), %w[.. lib ib-ruby]))
5
+
6
+ # Put your code here
7
+
8
+ # EOF