ib-api 972.5 → 972.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +2 -2
- data/Gemfile.lock +27 -27
- data/README.md +21 -13
- data/VERSION +1 -1
- data/api.gemspec +2 -0
- data/bin/console +1 -1
- data/lib/extensions/class-extensions.rb +9 -0
- data/lib/ib/base_properties.rb +22 -1
- data/lib/ib/connection.rb +98 -84
- data/lib/ib/constants.rb +5 -2
- data/lib/ib/errors.rb +3 -1
- data/lib/ib/messages/incoming/alert.rb +4 -4
- data/lib/ib/messages/incoming/ticks.rb +1 -1
- data/lib/ib/messages/outgoing/place_order.rb +3 -3
- data/lib/ib/messages/outgoing/request_marketdata.rb +1 -1
- data/lib/ib/socket.rb +2 -2
- data/lib/logging.rb +1 -1
- data/lib/models/ib/contract.rb +63 -4
- data/lib/models/ib/index.rb +1 -1
- data/lib/models/ib/option_detail.rb +13 -0
- data/lib/models/ib/order.rb +17 -0
- data/lib/models/ib/portfolio_value.rb +34 -12
- data/lib/models/ib/spread.rb +33 -18
- data/lib/requires.rb +2 -1
- metadata +31 -4
- data/lib/ib/logger.rb +0 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f7b56c267d93f567d8e601e801d98a90018fddb5e87f876c8eef2d89bd1a3e45
|
|
4
|
+
data.tar.gz: 23da0add2599afc4928e7008a9cf4bf3ed9f2c93a1cfd7d4e0bc8eeb3ebc6d4f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 57bf9b6cf7c2350e60c62b1d574e46a8d9fb0be5ec3ec2fc225ca9208ae0fa913fa15ad890715b6675f10b67248e04c60945008022368935339ac41a468b9d79
|
|
7
|
+
data.tar.gz: 5b1d1168ae20163ea5ab1693bce66c731183ffb63db81640458290810c2e225973bd10a51a6efa8f326cdd8dadc2747b02ba4a07529d3d7226b577d901cee1e9
|
data/Gemfile
CHANGED
|
@@ -5,8 +5,8 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
|
|
5
5
|
# Specify your gem's dependencies in api.gemspec
|
|
6
6
|
gemspec
|
|
7
7
|
|
|
8
|
-
gem 'value_semantics' # models
|
|
9
|
-
|
|
8
|
+
# gem 'value_semantics' # models, future plans
|
|
9
|
+
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
gem 'rspec'
|
data/Gemfile.lock
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
GIT
|
|
2
2
|
remote: https://github.com/ohler55/ox.git
|
|
3
|
-
revision:
|
|
3
|
+
revision: 67ce6ecb45a0d1354e1f8ed9a155826ba986e21e
|
|
4
4
|
specs:
|
|
5
|
-
ox (2.
|
|
5
|
+
ox (2.13.4)
|
|
6
6
|
|
|
7
7
|
PATH
|
|
8
8
|
remote: .
|
|
9
9
|
specs:
|
|
10
|
-
ib-api (972.
|
|
10
|
+
ib-api (972.2)
|
|
11
11
|
activemodel
|
|
12
12
|
activesupport (>= 6.0)
|
|
13
13
|
|
|
14
14
|
GEM
|
|
15
15
|
remote: https://rubygems.org/
|
|
16
16
|
specs:
|
|
17
|
-
activemodel (6.1.
|
|
18
|
-
activesupport (= 6.1.
|
|
19
|
-
activesupport (6.1.
|
|
17
|
+
activemodel (6.1.2.1)
|
|
18
|
+
activesupport (= 6.1.2.1)
|
|
19
|
+
activesupport (6.1.2.1)
|
|
20
20
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
21
21
|
i18n (>= 1.6, < 2)
|
|
22
22
|
minitest (>= 5.1)
|
|
@@ -25,7 +25,7 @@ GEM
|
|
|
25
25
|
coderay (1.1.3)
|
|
26
26
|
concurrent-ruby (1.1.8)
|
|
27
27
|
diff-lcs (1.4.4)
|
|
28
|
-
ffi (1.
|
|
28
|
+
ffi (1.13.1)
|
|
29
29
|
formatador (0.2.5)
|
|
30
30
|
guard (2.16.2)
|
|
31
31
|
formatador (>= 0.2.4)
|
|
@@ -41,52 +41,52 @@ GEM
|
|
|
41
41
|
guard (~> 2.1)
|
|
42
42
|
guard-compat (~> 1.1)
|
|
43
43
|
rspec (>= 2.99.0, < 4.0)
|
|
44
|
-
i18n (1.8.
|
|
44
|
+
i18n (1.8.9)
|
|
45
45
|
concurrent-ruby (~> 1.0)
|
|
46
|
-
listen (3.
|
|
46
|
+
listen (3.2.1)
|
|
47
47
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
48
48
|
rb-inotify (~> 0.9, >= 0.9.10)
|
|
49
49
|
lumberjack (1.2.8)
|
|
50
50
|
method_source (1.0.0)
|
|
51
|
-
minitest (5.14.
|
|
51
|
+
minitest (5.14.3)
|
|
52
52
|
nenv (0.3.0)
|
|
53
53
|
notiffany (0.1.3)
|
|
54
54
|
nenv (~> 0.1)
|
|
55
55
|
shellany (~> 0.0)
|
|
56
|
-
pry (0.
|
|
56
|
+
pry (0.13.1)
|
|
57
57
|
coderay (~> 1.1)
|
|
58
58
|
method_source (~> 1.0)
|
|
59
|
-
rake (13.0.
|
|
60
|
-
rb-fsevent (0.
|
|
59
|
+
rake (13.0.1)
|
|
60
|
+
rb-fsevent (0.10.4)
|
|
61
61
|
rb-inotify (0.10.1)
|
|
62
62
|
ffi (~> 1.0)
|
|
63
|
-
rspec (3.
|
|
64
|
-
rspec-core (~> 3.
|
|
65
|
-
rspec-expectations (~> 3.
|
|
66
|
-
rspec-mocks (~> 3.
|
|
63
|
+
rspec (3.9.0)
|
|
64
|
+
rspec-core (~> 3.9.0)
|
|
65
|
+
rspec-expectations (~> 3.9.0)
|
|
66
|
+
rspec-mocks (~> 3.9.0)
|
|
67
67
|
rspec-collection_matchers (1.2.0)
|
|
68
68
|
rspec-expectations (>= 2.99.0.beta1)
|
|
69
|
-
rspec-core (3.
|
|
70
|
-
rspec-support (~> 3.
|
|
71
|
-
rspec-expectations (3.
|
|
69
|
+
rspec-core (3.9.3)
|
|
70
|
+
rspec-support (~> 3.9.3)
|
|
71
|
+
rspec-expectations (3.9.2)
|
|
72
72
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
73
|
-
rspec-support (~> 3.
|
|
73
|
+
rspec-support (~> 3.9.0)
|
|
74
74
|
rspec-its (1.3.0)
|
|
75
75
|
rspec-core (>= 3.0.0)
|
|
76
76
|
rspec-expectations (>= 3.0.0)
|
|
77
|
-
rspec-mocks (3.
|
|
77
|
+
rspec-mocks (3.9.1)
|
|
78
78
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
79
|
-
rspec-support (~> 3.
|
|
80
|
-
rspec-support (3.
|
|
79
|
+
rspec-support (~> 3.9.0)
|
|
80
|
+
rspec-support (3.9.3)
|
|
81
81
|
shellany (0.0.1)
|
|
82
|
-
thor (1.1
|
|
82
|
+
thor (1.0.1)
|
|
83
83
|
tzinfo (2.0.4)
|
|
84
84
|
concurrent-ruby (~> 1.0)
|
|
85
85
|
value_semantics (3.6.0)
|
|
86
86
|
zeitwerk (2.4.2)
|
|
87
87
|
|
|
88
88
|
PLATFORMS
|
|
89
|
-
|
|
89
|
+
ruby
|
|
90
90
|
|
|
91
91
|
DEPENDENCIES
|
|
92
92
|
bundler
|
|
@@ -101,4 +101,4 @@ DEPENDENCIES
|
|
|
101
101
|
value_semantics
|
|
102
102
|
|
|
103
103
|
BUNDLED WITH
|
|
104
|
-
|
|
104
|
+
1.17.3
|
data/README.md
CHANGED
|
@@ -3,6 +3,7 @@ Ruby interface to Interactive Brokers' TWS API
|
|
|
3
3
|
|
|
4
4
|
Reimplementation of the basic functions of ib-ruby
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
__Documentation: [https://ib-ruby.github.io/ib-doc/](https://ib-ruby.github.io/ib-doc/)__ (_work in progress_)
|
|
7
8
|
|
|
8
9
|
----
|
|
@@ -20,7 +21,7 @@ $ gem install ib-api
|
|
|
20
21
|
|
|
21
22
|
In its plain vanilla usage, it just exchanges messages with the TWS. Any response is stored in the `recieved-Array`.
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
It needs just a few lines of code to place an order
|
|
24
25
|
|
|
25
26
|
```ruby
|
|
26
27
|
require 'ib-api'
|
|
@@ -64,29 +65,36 @@ or occationally
|
|
|
64
65
|
|
|
65
66
|
```ruby
|
|
66
67
|
# first define actions
|
|
67
|
-
|
|
68
|
+
q = Queue.new # Initialize as Queue
|
|
69
|
+
request_id = nil # declare variable
|
|
70
|
+
a = ib.subscribe(:Alert, :ContractData, :ContractDataEnd ) do |msg|
|
|
68
71
|
case msg
|
|
69
72
|
when Messages::Incoming::Alert
|
|
70
|
-
if msg.code == 200 # No security found
|
|
71
|
-
# do someting
|
|
72
|
-
end
|
|
73
|
+
q.close if msg.code == 200 # No security found
|
|
73
74
|
when Messages::Incoming::ContractData # security returned
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
q.push msg.contract if msg.request_id == request_id
|
|
76
|
+
when Messages::Incoming::ContractDataEnd
|
|
77
|
+
q.close if msg.request_id == request_id
|
|
76
78
|
end # case
|
|
77
79
|
end
|
|
78
80
|
# perform request
|
|
79
|
-
ib.send_message :RequestContractData, :contract =>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
request_id = ib.send_message :RequestContractData, :contract => Stock.new(symbol: 'T')
|
|
82
|
+
|
|
83
|
+
while contract = q.pop
|
|
84
|
+
puts contract.as_table
|
|
85
|
+
end
|
|
86
|
+
┌───────┬────────┬──────────┬──────────┬────────┬────────────┬───────────────┬───────┬────────┬──────────┐
|
|
87
|
+
│ │ symbol │ con_id │ exchange │ expiry │ multiplier │ trading-class │ right │ strike │ currency │
|
|
88
|
+
╞═══════╪════════╪══════════╪══════════╪════════╪════════════╪═══════════════╪═══════╪════════╪══════════╡
|
|
89
|
+
│ Stock │ T │ 37018770 │ SMART │ │ │ T │ │ │ USD │
|
|
90
|
+
└───────┴────────┴──────────┴──────────┴────────┴────────────┴───────────────┴───────┴────────┴──────────┘
|
|
91
|
+
|
|
84
92
|
ib.unsubscribe a # release subscriptions
|
|
85
93
|
|
|
86
94
|
```
|
|
87
95
|
## Minimal TWS-Version
|
|
88
96
|
|
|
89
|
-
`ib-api` is tested via the _stable IB-Gateway_ (Version
|
|
97
|
+
`ib-api` is tested via the _stable IB-Gateway_ (Version 10.12) and should work with any current tws-installation.
|
|
90
98
|
|
|
91
99
|
## Tests
|
|
92
100
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
972.5
|
|
1
|
+
972.5.1
|
data/api.gemspec
CHANGED
data/bin/console
CHANGED
|
@@ -76,7 +76,7 @@ read_yml = -> (key) do
|
|
|
76
76
|
|
|
77
77
|
c.subscribe( :OpenOrder){ |msg| "Open Order detected and stored: C.received[:OpenOrders] " }
|
|
78
78
|
end
|
|
79
|
-
C.logger.level = Logger::
|
|
79
|
+
#C.logger.level = Logger::FATAL
|
|
80
80
|
unless C.received[:OpenOrder].blank?
|
|
81
81
|
puts "------------------------------- OpenOrders ----------------------------------"
|
|
82
82
|
puts C.received[:OpenOrder].to_human.join "\n"
|
|
@@ -5,10 +5,19 @@ module CoreExtensions
|
|
|
5
5
|
self.each_with_object(Hash.new(0)) { |element, counter| counter[element] += 1 }.sort_by{|k,v| -v}.to_h
|
|
6
6
|
end
|
|
7
7
|
end
|
|
8
|
+
|
|
9
|
+
module TablePresenter
|
|
10
|
+
def as_table(&b)
|
|
11
|
+
the_table_header = first.table_header(&b)
|
|
12
|
+
the_table_rows = map &:table_row
|
|
13
|
+
Terminal::Table.new headings: the_table_header, rows: the_table_rows , style: { border: :unicode }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
8
16
|
end
|
|
9
17
|
end
|
|
10
18
|
|
|
11
19
|
Array.include CoreExtensions::Array::DuplicatesCounter
|
|
20
|
+
Array.include CoreExtensions::Array::TablePresenter
|
|
12
21
|
|
|
13
22
|
|
|
14
23
|
|
data/lib/ib/base_properties.rb
CHANGED
|
@@ -17,12 +17,33 @@ module IB
|
|
|
17
17
|
end.compact.sort.join(' ') + ">"
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
def table_header
|
|
21
|
+
[ self.class.to_s.demodulize ] + content_attributes.keys
|
|
22
|
+
end
|
|
23
|
+
def table_row
|
|
24
|
+
[ self.class.to_s.demodulize ] + content_attributes.values
|
|
25
|
+
end
|
|
26
|
+
# the optional block specifies a title
|
|
27
|
+
# i.e.
|
|
28
|
+
#s = Symbols::Spreads.stoxx_dez
|
|
29
|
+
# puts s.portfolio_value( U).as_table{ s.description[1..-2] }
|
|
30
|
+
#┌───────────┬─────────────────────────────────────────────┬─────┬────────┬─────────┬──────────┬────────────┬──────────┐
|
|
31
|
+
#│ │ Straddle ESTX50(4200.0)[Dec 2021] │ pos │ entry │ market │ value │ unrealized │ realized │
|
|
32
|
+
#╞═══════════╪═════════════════════════════════════════════╪═════╪════════╪═════════╪══════════╪════════════╪══════════╡
|
|
33
|
+
#│ U7274612 │ Option: ESTX50 20211217 put 4200.0 DTB EUR │ -4 │ 179.85 │ 169.831 │ -6793.22 │ 400.78 │ │
|
|
34
|
+
#│ U7274612 │ Option: ESTX50 20211217 call 4200.0 DTB EUR │ -4 │ 97.85 │ 131.438 │ -5257.51 │ -1343.51 │ │
|
|
35
|
+
#└───────────┴─────────────────────────────────────────────┴─────┴────────┴─────────┴──────────┴────────────┴──────────┘
|
|
36
|
+
##
|
|
37
|
+
def as_table &b
|
|
38
|
+
Terminal::Table.new headings: table_header(&b), rows: [table_row ], style: { border: :unicode }
|
|
39
|
+
end
|
|
40
|
+
|
|
20
41
|
# Comparison support
|
|
21
42
|
def content_attributes
|
|
22
43
|
#NoMethodError if a Hash is assigned to an attribute
|
|
23
44
|
Hash[attributes.reject do |(attr, _)|
|
|
24
45
|
attr.to_s =~ /(_count)\z/ ||
|
|
25
|
-
[:created_at, :type,
|
|
46
|
+
[:created_at, :type, :updated_at,
|
|
26
47
|
:id, :order_id, :contract_id].include?(attr.to_sym)
|
|
27
48
|
end]
|
|
28
49
|
end
|
data/lib/ib/connection.rb
CHANGED
|
@@ -35,76 +35,81 @@ module IB
|
|
|
35
35
|
alias next_order_id= next_local_id=
|
|
36
36
|
|
|
37
37
|
def initialize host: '127.0.0.1',
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# redis: false, # future plans
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
# API messages sent at a higher rate than 50/second can now be paced by TWS at the 50/second rate instead of potentially causing a disconnection. This is now done automatically by the RTD Server API and can be done with other API technologies by invoking SetConnectOptions("+PACEAPI") prior to eConnect.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# A couple of locks to avoid race conditions in JRuby
|
|
62
|
-
@subscribe_lock = Mutex.new
|
|
63
|
-
@receive_lock = Mutex.new
|
|
64
|
-
@message_lock = Mutex.new
|
|
65
|
-
|
|
66
|
-
@connected = false
|
|
67
|
-
self.next_local_id = nil
|
|
68
|
-
|
|
69
|
-
# self.subscribe(:Alert) do |msg|
|
|
70
|
-
# puts msg.to_human
|
|
71
|
-
# end
|
|
38
|
+
port: '4002', # IB Gateway connection (default --> demo) 4001: production
|
|
39
|
+
#:port => '7497', # TWS connection --> demo 7496: production
|
|
40
|
+
connect: true, # Connect at initialization
|
|
41
|
+
received: true, # Keep all received messages in a @received Hash
|
|
42
|
+
# redis: false, # future plans
|
|
43
|
+
logger: nil,
|
|
44
|
+
client_id: rand( 1001 .. 9999 ) ,
|
|
45
|
+
client_version: IB::Messages::CLIENT_VERSION, # lib/ib/server_versions.rb
|
|
46
|
+
optional_capacities: "", # TWS-Version 974: "+PACEAPI"
|
|
47
|
+
#server_version: IB::Messages::SERVER_VERSION, # lib/messages.rb
|
|
48
|
+
**any_other_parameters_which_are_ignored
|
|
49
|
+
# V 974 release motes
|
|
50
|
+
# API messages sent at a higher rate than 50/second can now be paced by TWS at the 50/second rate instead of potentially causing a disconnection. This is now done automatically by the RTD Server API and can be done with other API technologies by invoking SetConnectOptions("+PACEAPI") prior to eConnect.
|
|
51
|
+
|
|
52
|
+
self.class.configure_logger logger
|
|
53
|
+
# convert parameters into instance-variables and assign them
|
|
54
|
+
method(__method__).parameters.each do |type, k|
|
|
55
|
+
next unless type == :key ## available: key , keyrest
|
|
56
|
+
next if k.to_s == 'logger'
|
|
57
|
+
v = eval(k.to_s)
|
|
58
|
+
instance_variable_set("@#{k}", v) unless v.nil?
|
|
59
|
+
end
|
|
72
60
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
61
|
+
# A couple of locks to avoid race conditions in JRuby
|
|
62
|
+
@subscribe_lock = Mutex.new
|
|
63
|
+
@receive_lock = Mutex.new
|
|
64
|
+
@message_lock = Mutex.new
|
|
76
65
|
|
|
77
|
-
|
|
78
|
-
self.
|
|
79
|
-
self.next_local_id = msg.local_id
|
|
80
|
-
self.logger.info { "Got next valid order id: #{next_local_id}." }
|
|
81
|
-
end
|
|
66
|
+
@connected = false
|
|
67
|
+
self.next_local_id = nil
|
|
82
68
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
69
|
+
# TWS always sends NextValidId message at connect -subscribe save this id
|
|
70
|
+
self.subscribe(:NextValidId) do |msg|
|
|
71
|
+
self.logger.progname = "Connection#connect"
|
|
72
|
+
self.next_local_id = msg.local_id
|
|
73
|
+
self.logger.info { "Got next valid order id: #{next_local_id}." }
|
|
74
|
+
end
|
|
75
|
+
#
|
|
76
|
+
# this block is executed before tws-communication is established
|
|
77
|
+
# Its intended for globally available subscriptions of tws-messages
|
|
78
|
+
yield self if block_given?
|
|
79
|
+
|
|
80
|
+
if connect
|
|
81
|
+
disconnect if connected?
|
|
82
|
+
update_next_order_id
|
|
83
|
+
Kernel.exit if self.next_local_id.nil? # emergency exit.
|
|
84
|
+
# update_next_order_id should have raised an error
|
|
85
|
+
end
|
|
86
|
+
Connection.current = self
|
|
87
|
+
end
|
|
93
88
|
|
|
94
89
|
# read actual order_id and
|
|
95
90
|
# connect if not connected
|
|
96
91
|
def update_next_order_id
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
92
|
+
q = Queue.new
|
|
93
|
+
subscription = subscribe(:NextValidId){ |msg| q.push msg.local_id }
|
|
94
|
+
unless connected?
|
|
95
|
+
connect() # connect implies requesting NextValidId
|
|
96
|
+
else
|
|
97
|
+
send_message :RequestIds
|
|
98
|
+
end
|
|
99
|
+
th = Thread.new { sleep 5; q.close }
|
|
100
|
+
local_id = q.pop
|
|
101
|
+
if q.closed?
|
|
102
|
+
error "Could not get NextValidID", :reader
|
|
103
|
+
else
|
|
104
|
+
th.kill
|
|
105
|
+
end
|
|
106
|
+
unsubscribe subscription
|
|
107
|
+
local_id # return next_id
|
|
108
|
+
end
|
|
105
109
|
|
|
106
110
|
### Working with connection
|
|
107
|
-
|
|
111
|
+
#
|
|
112
|
+
### connect can be called directly. but is mostly called through update_next_order_id
|
|
108
113
|
def connect
|
|
109
114
|
logger.progname='IB::Connection#connect'
|
|
110
115
|
if connected?
|
|
@@ -123,10 +128,6 @@ module IB
|
|
|
123
128
|
@local_connect_time = Time.now
|
|
124
129
|
end
|
|
125
130
|
|
|
126
|
-
# Sending (arbitrary) client ID to identify subsequent communications.
|
|
127
|
-
# The client with a client_id of 0 can manage the TWS-owned open orders.
|
|
128
|
-
# Other clients can only manage their own open orders.
|
|
129
|
-
|
|
130
131
|
# V100 initial handshake
|
|
131
132
|
# Parameters borrowed from the python client
|
|
132
133
|
start_api = 71
|
|
@@ -134,14 +135,11 @@ module IB
|
|
|
134
135
|
# optcap = @optional_capacities.empty? ? "" : " "+ @optional_capacities
|
|
135
136
|
socket.send_messages start_api, version, @client_id , @optional_capacities
|
|
136
137
|
@connected = true
|
|
137
|
-
logger.
|
|
138
|
+
logger.fatal{ "Connected to server, version: #{@server_version}, " +
|
|
139
|
+
"using client-id: #{client_id},\n connection time: " +
|
|
138
140
|
"#{@local_connect_time} local, " +
|
|
139
|
-
"#{@remote_connect_time} remote."}
|
|
141
|
+
"#{@remote_connect_time} remote." }
|
|
140
142
|
|
|
141
|
-
# if the client_id is wrong or the port is not accessible the first read attempt fails
|
|
142
|
-
# get the first message and proceed if something reasonable is recieved
|
|
143
|
-
the_message = process_message # recieve next_order_id
|
|
144
|
-
error "Check Port/Client_id ", :reader if the_message == " "
|
|
145
143
|
start_reader
|
|
146
144
|
end
|
|
147
145
|
|
|
@@ -258,6 +256,7 @@ module IB
|
|
|
258
256
|
#
|
|
259
257
|
# wait_for depends heavyly on Connection#received. If collection of messages through recieved
|
|
260
258
|
# is turned off, wait_for loses most of its functionality
|
|
259
|
+
|
|
261
260
|
def wait_for *args, &block
|
|
262
261
|
timeout = args.find { |arg| arg.is_a? Numeric } # extract timeout from args
|
|
263
262
|
end_time = Time.now + (timeout || 1) # default timeout 1 sec
|
|
@@ -282,12 +281,12 @@ module IB
|
|
|
282
281
|
# Process incoming messages during *poll_time* (200) msecs, nonblocking
|
|
283
282
|
def process_messages poll_time = 50 # in msec
|
|
284
283
|
time_out = Time.now + poll_time/1000.0
|
|
284
|
+
begin
|
|
285
285
|
while (time_left = time_out - Time.now) > 0
|
|
286
286
|
# If socket is readable, process single incoming message
|
|
287
287
|
#process_message if select [socket], nil, nil, time_left
|
|
288
288
|
# the following checks for shutdown of TWS side; ensures we don't run in a spin loop.
|
|
289
289
|
# unfortunately, it raises Errors in windows environment
|
|
290
|
-
# disabled for now
|
|
291
290
|
if select [socket], nil, nil, time_left
|
|
292
291
|
# # Peek at the message from the socket; if it's blank then the
|
|
293
292
|
# # server side of connection (TWS) has likely shut down.
|
|
@@ -303,6 +302,16 @@ module IB
|
|
|
303
302
|
sleep(0.1) if socket_likely_shutdown
|
|
304
303
|
end
|
|
305
304
|
end
|
|
305
|
+
rescue Errno::ECONNRESET => e
|
|
306
|
+
logger.fatal e.message
|
|
307
|
+
if e.message =~ /Connection reset by peer/
|
|
308
|
+
logger.fatal "Is another client listening on the same port?"
|
|
309
|
+
error "try reconnecting with a different client-id", :reader
|
|
310
|
+
else
|
|
311
|
+
logger.fatal "Aborting"
|
|
312
|
+
Kernel.exit
|
|
313
|
+
end
|
|
314
|
+
end
|
|
306
315
|
end
|
|
307
316
|
|
|
308
317
|
### Sending Outgoing messages to IB
|
|
@@ -374,15 +383,20 @@ module IB
|
|
|
374
383
|
# If you don't start reader, you should manually poll @socket for messages
|
|
375
384
|
# or use #process_messages(msec) API.
|
|
376
385
|
def start_reader
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
+
if @reader_running
|
|
387
|
+
@reader_thread
|
|
388
|
+
elsif connected?
|
|
389
|
+
begin
|
|
390
|
+
Thread.abort_on_exception = true
|
|
391
|
+
@reader_running = true
|
|
392
|
+
@reader_thread = Thread.new { process_messages while @reader_running }
|
|
393
|
+
rescue Errno::ECONNRESET => e
|
|
394
|
+
logger.fatal e.message
|
|
395
|
+
Kernel.exit
|
|
396
|
+
end
|
|
397
|
+
else
|
|
398
|
+
error "Could not start reader, not connected!", :reader, true
|
|
399
|
+
end
|
|
386
400
|
end
|
|
387
401
|
|
|
388
402
|
protected
|
|
@@ -403,18 +417,18 @@ module IB
|
|
|
403
417
|
msg_id = the_decoded_message.shift.to_i
|
|
404
418
|
|
|
405
419
|
# Debug:
|
|
406
|
-
|
|
420
|
+
# logger.debug { "Got message #{msg_id} (#{Messages::Incoming::Classes[msg_id]})"}
|
|
407
421
|
|
|
408
422
|
# Create new instance of the appropriate message type,
|
|
409
423
|
# and have it read the message from socket.
|
|
410
424
|
# NB: Failure here usually means unsupported message type received
|
|
411
425
|
logger.error { "Got unsupported message #{msg_id}" } unless Messages::Incoming::Classes[msg_id]
|
|
412
|
-
error "Something strange happened - Reader has to be restarted" , :reader if msg_id.to_i.zero?
|
|
426
|
+
error "Something strange happened - Reader has to be restarted" , :reader, true if msg_id.to_i.zero?
|
|
413
427
|
msg = Messages::Incoming::Classes[msg_id].new(the_decoded_message)
|
|
414
428
|
|
|
415
429
|
# Deliver message to all registered subscribers, alert if no subscribers
|
|
416
430
|
# Ruby 2.0 and above: Hashes are ordered.
|
|
417
|
-
# Thus first declared subscribers of
|
|
431
|
+
# Thus first declared subscribers of a class are executed first
|
|
418
432
|
@subscribe_lock.synchronize do
|
|
419
433
|
subscribers[msg.class].each { |_, subscriber| subscriber.call(msg) }
|
|
420
434
|
end
|
data/lib/ib/constants.rb
CHANGED
|
@@ -21,7 +21,11 @@ module IB
|
|
|
21
21
|
'15 mins' =>:min15,
|
|
22
22
|
'30 mins' =>:min30,
|
|
23
23
|
'1 hour' =>:hour1,
|
|
24
|
-
'
|
|
24
|
+
'4 hours' =>:hour4,
|
|
25
|
+
'8 hours' =>:hour8,
|
|
26
|
+
'1 day' => :day1,
|
|
27
|
+
'1 week' => :week1,
|
|
28
|
+
'1 month' => :month1,
|
|
25
29
|
}.freeze
|
|
26
30
|
|
|
27
31
|
# Enumeration of data types.
|
|
@@ -199,7 +203,6 @@ module IB
|
|
|
199
203
|
'PEG MID' => :pegged_to_midpoint, # Pegged-to-Midpoint
|
|
200
204
|
'PEG BENCH' => :pegged_to_benchmark, # Pegged-to-Benmchmark # Vers. 102
|
|
201
205
|
'VWAP' => :vwap, # VWAP-Guaranted
|
|
202
|
-
'OCA' => :one_cancels_all, # One-Cancels-All
|
|
203
206
|
'VOL' => :volatility, # Volatility
|
|
204
207
|
'SCALE' => :scale, # Scale
|
|
205
208
|
'NONE' => :none, # Used to indicate no hedge in :delta_neutral_order_type
|
data/lib/ib/errors.rb
CHANGED
|
@@ -38,7 +38,9 @@ def error message, type=:standard, backtrace=nil
|
|
|
38
38
|
IB::FlexError.new message
|
|
39
39
|
when :reader
|
|
40
40
|
IB::TransmissionError.new message
|
|
41
|
+
when :verify
|
|
42
|
+
IB::VerifyError.new message
|
|
41
43
|
end
|
|
42
|
-
e.set_backtrace(
|
|
44
|
+
e.set_backtrace(caller) if backtrace
|
|
43
45
|
raise e
|
|
44
46
|
end
|
|
@@ -4,10 +4,10 @@ module IB
|
|
|
4
4
|
|
|
5
5
|
# Called Error in Java code, but in fact this type of messages also
|
|
6
6
|
# deliver system alerts and additional (non-error) info from TWS.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
Alert = def_message([4, 2],
|
|
8
|
+
[:error_id, :int],
|
|
9
|
+
[:code, :int],
|
|
10
|
+
[:message, :string])
|
|
11
11
|
class Alert
|
|
12
12
|
# Is it an Error message?
|
|
13
13
|
def error?
|
|
@@ -74,9 +74,9 @@ module IB
|
|
|
74
74
|
order.all_or_none || false,
|
|
75
75
|
order.min_quantity || "",
|
|
76
76
|
order.percent_offset || '',
|
|
77
|
-
order.etrade_only || false,
|
|
78
|
-
order.firm_quote_only || false,
|
|
79
|
-
order.nbbo_price_cap || "",
|
|
77
|
+
false, # was: order.etrade_only || false, desupported in TWS > 981
|
|
78
|
+
false, # was: order.firm_quote_only || false, desupported in TWS > 981
|
|
79
|
+
order.nbbo_price_cap || "", ## desupported in TWS > 981, too. maybe we have to insert a hard-coded "" here
|
|
80
80
|
order[:auction_strategy],
|
|
81
81
|
order.starting_price,
|
|
82
82
|
order.stock_ref_price || "",
|
data/lib/ib/socket.rb
CHANGED
|
@@ -154,7 +154,7 @@ module IB
|
|
|
154
154
|
def send_messages *data
|
|
155
155
|
self.syswrite prepare_message(data)
|
|
156
156
|
rescue Errno::ECONNRESET => e
|
|
157
|
-
Connection.logger.
|
|
157
|
+
Connection.logger.fatal{ "Data not accepted by IB \n
|
|
158
158
|
#{data.inspect} \n
|
|
159
159
|
Backtrace:\n "}
|
|
160
160
|
Connection.logger.error e.backtrace
|
|
@@ -172,7 +172,7 @@ module IB
|
|
|
172
172
|
end while buffer.size == 4096
|
|
173
173
|
complete_message_buffer.join('')
|
|
174
174
|
rescue Errno::ECONNRESET => e
|
|
175
|
-
Connection.logger.
|
|
175
|
+
Connection.logger.fatal{ "Data Buffer is not filling \n
|
|
176
176
|
The Buffer: #{buffer.inspect} \n
|
|
177
177
|
Backtrace:\n
|
|
178
178
|
#{e.backtrace.join("\n") } " }
|
data/lib/logging.rb
CHANGED
|
@@ -34,7 +34,7 @@ module Support
|
|
|
34
34
|
@logger.level = Logger::INFO
|
|
35
35
|
@logger.formatter = proc do |severity, datetime, progname, msg|
|
|
36
36
|
# "#{datetime.strftime("%d.%m.(%X)")}#{"%5s" % severity}->#{msg}\n"
|
|
37
|
-
"#{"%
|
|
37
|
+
"#{"%1s" % severity[0]}: #{msg}\n"
|
|
38
38
|
end
|
|
39
39
|
@logger.debug "------------------------------ start logging ----------------------------"
|
|
40
40
|
end # branch
|
data/lib/models/ib/contract.rb
CHANGED
|
@@ -235,10 +235,46 @@ module IB
|
|
|
235
235
|
|
|
236
236
|
# creates a new Contract substituting attributes by the provided key-value pairs.
|
|
237
237
|
#
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
238
|
+
# for convenience
|
|
239
|
+
# con_id, local_symbol and last_trading_day are resetted,
|
|
240
|
+
# the link to contract-details is savaged
|
|
241
|
+
#
|
|
242
|
+
# Example
|
|
243
|
+
# ge = Stock.new( symbol: :ge).verify.first
|
|
244
|
+
# f = ge.merge symbol: :f
|
|
245
|
+
#
|
|
246
|
+
# c = Contract.new( con_id: 428520002, exchange: 'Globex')
|
|
247
|
+
#puts c.verify.as_table
|
|
248
|
+
#┌────────┬────────┬───────────┬──────────┬──────────┬────────────┬───────────────┬───────┬────────┬──────────┐
|
|
249
|
+
#│ │ symbol │ con_id │ exchange │ expiry │ multiplier │ trading-class │ right │ strike │ currency │
|
|
250
|
+
#╞════════╪════════╪═══════════╪══════════╪══════════╪════════════╪═══════════════╪═══════╪════════╪══════════╡
|
|
251
|
+
#│ Future │ NQ │ 428520002 │ GLOBEX │ 20210917 │ 20 │ NQ │ │ │ USD │
|
|
252
|
+
#└────────┴────────┴───────────┴──────────┴──────────┴────────────┴───────────────┴───────┴────────┴──────────┘
|
|
253
|
+
# d= c.merge symbol: :es, trading_class: '', multiplier: 50
|
|
254
|
+
# puts d.verify.as_table
|
|
255
|
+
#┌────────┬────────┬───────────┬──────────┬──────────┬────────────┬───────────────┬───────┬────────┬──────────┐
|
|
256
|
+
#│ │ symbol │ con_id │ exchange │ expiry │ multiplier │ trading-class │ right │ strike │ currency │
|
|
257
|
+
#╞════════╪════════╪═══════════╪══════════╪══════════╪════════════╪═══════════════╪═══════╪════════╪══════════╡
|
|
258
|
+
#│ Future │ ES │ 428520022 │ GLOBEX │ 20210917 │ 50 │ ES │ │ │ USD │
|
|
259
|
+
#│ Future │ ES │ 446091461 │ GLOBEX │ 20211217 │ 50 │ ES │ │ │ USD │
|
|
260
|
+
#│ Future │ ES │ 461318816 │ GLOBEX │ 20220318 │ 50 │ ES │ │ │ USD │
|
|
261
|
+
#│ Future │ ES │ 477836957 │ GLOBEX │ 20220617 │ 50 │ ES │ │ │ USD │
|
|
262
|
+
#│ Future │ ES │ 495512551 │ GLOBEX │ 20221216 │ 50 │ ES │ │ │ USD │
|
|
263
|
+
#│ Future │ ES │ 495512552 │ GLOBEX │ 20231215 │ 50 │ ES │ │ │ USD │
|
|
264
|
+
#│ Future │ ES │ 495512557 │ GLOBEX │ 20241220 │ 50 │ ES │ │ │ USD │
|
|
265
|
+
#│ Future │ ES │ 495512563 │ GLOBEX │ 20251219 │ 50 │ ES │ │ │ USD │
|
|
266
|
+
#│ Future │ ES │ 495512566 │ GLOBEX │ 20220916 │ 50 │ ES │ │ │ USD │
|
|
267
|
+
#│ Future │ ES │ 495512569 │ GLOBEX │ 20230616 │ 50 │ ES │ │ │ USD │
|
|
268
|
+
#│ Future │ ES │ 495512572 │ GLOBEX │ 20230317 │ 50 │ ES │ │ │ USD │
|
|
269
|
+
#│ Future │ ES │ 497222760 │ GLOBEX │ 20230915 │ 50 │ ES │ │ │ USD │
|
|
270
|
+
#└────────┴────────┴───────────┴──────────┴──────────┴────────────┴───────────────┴───────┴────────┴──────────┘
|
|
271
|
+
|
|
272
|
+
def merge **new_attributes
|
|
273
|
+
|
|
274
|
+
resetted_attributes = [:con_id, :local_symbol, :contract_detail]
|
|
275
|
+
## last_trading_day / expiry needs special treatment
|
|
276
|
+
resetted_attributes << :last_trading_day if new_attributes.keys.include? :expiry
|
|
277
|
+
self.class.new attributes.reject{|k,_| resetted_attributes.include? k}.merge(new_attributes)
|
|
242
278
|
end
|
|
243
279
|
|
|
244
280
|
# Contract comparison
|
|
@@ -376,6 +412,29 @@ In places where these terms are used to indicate a concept, we have left them as
|
|
|
376
412
|
Hash.new
|
|
377
413
|
end
|
|
378
414
|
|
|
415
|
+
|
|
416
|
+
def table_header( &b )
|
|
417
|
+
if block_given?
|
|
418
|
+
[ yield(self) , 'symbol', 'con_id', 'exchange', 'expiry','multiplier', 'trading-class' , 'right', 'strike', 'currency' ]
|
|
419
|
+
else
|
|
420
|
+
[ '', 'symbol', 'con_id', 'exchange', 'expiry','multiplier', 'trading-class' , 'right', 'strike', 'currency' ]
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def table_row
|
|
425
|
+
[ self.class.to_s.demodulize, symbol,
|
|
426
|
+
{ value: con_id.zero? ? '' : con_id , alignment: :right},
|
|
427
|
+
{ value: exchange, alignment: :center},
|
|
428
|
+
expiry,
|
|
429
|
+
{ value: multiplier.zero?? "" : multiplier, alignment: :center},
|
|
430
|
+
{ value: trading_class, alignment: :center},
|
|
431
|
+
{value: right == :none ? "": right, alignment: :center },
|
|
432
|
+
{ value: strike.zero? ? "": strike, alignment: :right},
|
|
433
|
+
{ value: currency, alignment: :center} ]
|
|
434
|
+
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
|
|
379
438
|
end # class Contract
|
|
380
439
|
|
|
381
440
|
|
data/lib/models/ib/index.rb
CHANGED
|
@@ -4,7 +4,7 @@ module IB
|
|
|
4
4
|
validates_format_of :sec_type, :with => /\Aind\z/,
|
|
5
5
|
:message => "should be a Index"
|
|
6
6
|
def default_attributes
|
|
7
|
-
super.merge :sec_type =>
|
|
7
|
+
super.merge :sec_type => 'IND'
|
|
8
8
|
end
|
|
9
9
|
def to_human
|
|
10
10
|
"<Index: " + [symbol, currency].join(" ") + " (#{description}) >"
|
|
@@ -67,5 +67,18 @@ module IB
|
|
|
67
67
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
+
def table_header
|
|
71
|
+
[ 'Greeks', 'price', 'impl. vola', 'dividend', 'delta','gamma', 'vega' , 'theta']
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def table_row
|
|
75
|
+
outstr= ->( item ) { { value: item.nil? ? "--" : sprintf("%g" , item) , alignment: :right } }
|
|
76
|
+
outprice= ->( item ) { { value: item.nil? ? "--" : sprintf("%7.2f" , item) , alignment: :right } }
|
|
77
|
+
option_short = ->{"#{option.right} #{option.symbol}#{ "/"+ option.trading_class unless option.trading_class == option.symbol } #{option.expiry} #{option.strike}"}
|
|
78
|
+
[ option_short[], outprice[ option_price ], outprice[ implied_volatility ],
|
|
79
|
+
outprice[ pv_dividend ],
|
|
80
|
+
outprice[ delta ], outprice[ gamma ], outprice[ vega ] , outprice[ theta ] ]
|
|
81
|
+
end
|
|
82
|
+
|
|
70
83
|
end # class
|
|
71
84
|
end # module
|
data/lib/models/ib/order.rb
CHANGED
|
@@ -514,6 +514,23 @@ Format of serialisation
|
|
|
514
514
|
(account ? "/#{account}" : '') +
|
|
515
515
|
(commission ? " fee #{commission}" : '') + ">"
|
|
516
516
|
end
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def table_header
|
|
520
|
+
[ 'account','status' ,'', 'Type', 'tif', 'action', 'amount','price' , 'id/fee' ]
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def table_row
|
|
524
|
+
[ account, order_ref.present? ? order_ref.to_s : status,
|
|
525
|
+
contract.to_human[1..-2],
|
|
526
|
+
self[:order_type] ,
|
|
527
|
+
self[:tif],
|
|
528
|
+
action,
|
|
529
|
+
total_quantity,
|
|
530
|
+
(limit_price ? "#{limit_price} " : '') + ((aux_price && aux_price != 0) ? "/#{aux_price}" : '') ,
|
|
531
|
+
commission ? " fee #{commission}" : local_id ]
|
|
532
|
+
end
|
|
533
|
+
|
|
517
534
|
def serialize_rabbit
|
|
518
535
|
{ 'Contract' => contract.present? ? contract.serialize( :option, :trading_class ): '' ,
|
|
519
536
|
'Order' => self,
|
|
@@ -47,18 +47,40 @@ class PortfolioValue < IB::Model
|
|
|
47
47
|
end
|
|
48
48
|
alias to_s to_human
|
|
49
49
|
|
|
50
|
+
def table_header
|
|
51
|
+
if block_given?
|
|
52
|
+
[ '' , yield , 'pos', 'entry', 'market', 'value', 'unrealized', 'realized' ]
|
|
53
|
+
else
|
|
54
|
+
[ '' , '', 'pos', 'entry', 'market', 'value', 'unrealized', 'realized' ]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def table_row
|
|
59
|
+
outprice= ->( item ) { { value: item.nil? ? "--" : item , alignment: :right } }
|
|
60
|
+
|
|
61
|
+
the_account = if account.present?
|
|
62
|
+
if account.is_a?(String)
|
|
63
|
+
account + " "
|
|
64
|
+
else
|
|
65
|
+
account.account+" "
|
|
66
|
+
end
|
|
67
|
+
else
|
|
68
|
+
""
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
entry = average_cost.to_f / (contract.multiplier.to_i.zero? ? 1 : contract.multiplier.to_i)
|
|
72
|
+
|
|
73
|
+
[ the_account,
|
|
74
|
+
contract.to_human[1..-2],
|
|
75
|
+
outprice[position.to_i],
|
|
76
|
+
outprice[entry.to_f.round(3)],
|
|
77
|
+
outprice[market_price.to_f.round(3)],
|
|
78
|
+
outprice[market_value.to_f.round(2)],
|
|
79
|
+
unrealized_pnl.to_i.zero? ? "": outprice[unrealized_pnl],
|
|
80
|
+
realized_pnl.to_i.zero? ? "" : outprice[realized_pnl]
|
|
81
|
+
]
|
|
82
|
+
end
|
|
83
|
+
|
|
50
84
|
|
|
51
|
-
# def to_invest
|
|
52
|
-
# a=attributes
|
|
53
|
-
# a.delete "created_at"
|
|
54
|
-
# a.delete "updated_at"
|
|
55
|
-
# a.delete "id"
|
|
56
|
-
# a.delete "account_id"
|
|
57
|
-
# a.delete "currency_id"
|
|
58
|
-
# a[:currency] = currency.symbol.presence || currency.name.presence || nil unless currency.nil?
|
|
59
|
-
# a #return_value
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# end
|
|
63
85
|
end # class
|
|
64
86
|
end # module
|
data/lib/models/ib/spread.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# require 'ib/verify'
|
|
2
1
|
module IB
|
|
3
2
|
if defined?(Spread)
|
|
4
3
|
puts "Bag already a #{defined?(Spread)}"
|
|
@@ -21,7 +20,7 @@ Adds (or substracts) relative (back) measures to the front month, just passes ab
|
|
|
21
20
|
front: 20180908 back: 1w (-1w) --> 20180918 (20180902)
|
|
22
21
|
=end
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
def self.transform_distance front, back
|
|
25
24
|
# Check Format of back: 201809 --> > 200.000
|
|
26
25
|
# 20180989 ---> 20.000.000
|
|
27
26
|
start_date = front.to_i < 20000000 ? Date.strptime(front.to_s,"%Y%m") : Date.strptime(front.to_s,"%Y%m%d")
|
|
@@ -80,17 +79,14 @@ Adds (or substracts) relative (back) measures to the front month, just passes ab
|
|
|
80
79
|
# Default: action: :buy, weight: 1
|
|
81
80
|
|
|
82
81
|
def add_leg contract, **leg_params
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
error "need a IB::Contract as first argument" unless contract.is_a? IB::Contract
|
|
83
|
+
self.legs << contract
|
|
84
|
+
error "cannot add leg if no con_id is provided" if contract.con_id.blank?
|
|
85
85
|
# weigth = 1 --> sets Combo.side to buy and overwrites the action statement
|
|
86
86
|
# leg_params[:weight] = 1 unless leg_params.key?(:weight) || leg_params.key?(:ratio)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
self.combo_legs << the_leg
|
|
91
|
-
self.description = "#{description.nil? ? "": description} added #{nc.to_human}" rescue "Spread: #{nc.to_human}"
|
|
92
|
-
self.legs << nc
|
|
93
|
-
end
|
|
87
|
+
self.combo_legs << ComboLeg.new( contract.attributes.slice( :con_id, :exchange ).merge( leg_params ))
|
|
88
|
+
self.description = "#{description.nil? ? "": description} added #{contract.to_human}" rescue "Spread: #{contract.to_human}"
|
|
89
|
+
|
|
94
90
|
self # return value to enable chaining
|
|
95
91
|
|
|
96
92
|
|
|
@@ -98,13 +94,20 @@ Adds (or substracts) relative (back) measures to the front month, just passes ab
|
|
|
98
94
|
|
|
99
95
|
# removes the contract from the spread definition
|
|
100
96
|
#
|
|
101
|
-
def remove_leg
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
97
|
+
def remove_leg contract_or_position = nil
|
|
98
|
+
contract = if contract_or_position.is_a? (IB::Contract)
|
|
99
|
+
contract_or_position
|
|
100
|
+
elsif contract_or_position.is_a? Numeric
|
|
101
|
+
legs.at contract_or_position
|
|
102
|
+
else
|
|
103
|
+
error "Specify a contract to be removed or the position in the legs-array as parameter to remove a leg"
|
|
104
|
+
end
|
|
105
|
+
the_con_id = contract.verify.first &.con_id
|
|
106
|
+
error "Invalid Contract specified" unless the_con_id.is_a? Numeric
|
|
107
|
+
legs.delete_if { |x| x.con_id == the_con_id }
|
|
108
|
+
combo_legs.delete_if { |x| x.con_id == the_con_id }
|
|
109
|
+
self.description = description + " removed #{contract.to_human}"
|
|
110
|
+
self # make method chainable
|
|
108
111
|
end
|
|
109
112
|
|
|
110
113
|
# essentail
|
|
@@ -146,6 +149,18 @@ Adds (or substracts) relative (back) measures to the front month, just passes ab
|
|
|
146
149
|
# end
|
|
147
150
|
|
|
148
151
|
|
|
152
|
+
def as_table
|
|
153
|
+
t= Terminal::Table.new title: description[1..-2] ,
|
|
154
|
+
headings: table_header,
|
|
155
|
+
|
|
156
|
+
style: { border: :unicode }
|
|
157
|
+
|
|
158
|
+
t.add_row table_row
|
|
159
|
+
legs.each{ |y| t.add_row y.table_row }
|
|
160
|
+
t.render
|
|
161
|
+
|
|
162
|
+
end
|
|
163
|
+
|
|
149
164
|
def self.build_from_json container
|
|
150
165
|
read_leg = ->(a) do
|
|
151
166
|
IB::ComboLeg.new :con_id => a.read_int,
|
data/lib/requires.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ib-api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 972.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hartmut Bischoff
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-01-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -80,6 +80,34 @@ dependencies:
|
|
|
80
80
|
- - ">="
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: ox
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: terminal-table
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :runtime
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
83
111
|
description: Ruby Implementation of the Interactive Brokers TWS API
|
|
84
112
|
email:
|
|
85
113
|
- topofocus@gmail.com
|
|
@@ -110,7 +138,6 @@ files:
|
|
|
110
138
|
- lib/ib/connection.rb
|
|
111
139
|
- lib/ib/constants.rb
|
|
112
140
|
- lib/ib/errors.rb
|
|
113
|
-
- lib/ib/logger.rb
|
|
114
141
|
- lib/ib/messages.rb
|
|
115
142
|
- lib/ib/messages/abstract_message.rb
|
|
116
143
|
- lib/ib/messages/incoming.rb
|
|
@@ -185,7 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
185
212
|
- !ruby/object:Gem::Version
|
|
186
213
|
version: '0'
|
|
187
214
|
requirements: []
|
|
188
|
-
rubygems_version: 3.
|
|
215
|
+
rubygems_version: 3.3.7
|
|
189
216
|
signing_key:
|
|
190
217
|
specification_version: 4
|
|
191
218
|
summary: Ruby Implementation of the Interactive Brokers TWS API
|
data/lib/ib/logger.rb
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
require "logger"
|
|
2
|
-
module LogDev
|
|
3
|
-
# define default_logger
|
|
4
|
-
def default_logger
|
|
5
|
-
@default_logger ||= Logger.new(STDOUT).tap do |l|
|
|
6
|
-
l.formatter = proc do |severity, datetime, progname, msg|
|
|
7
|
-
# "#{datetime.strftime("%d.%m.(%X)")}#{"%5s" % severity}->#{progname}##{msg}\n"
|
|
8
|
-
## the default logger displays the message only
|
|
9
|
-
msg.to_s + "\n"
|
|
10
|
-
end
|
|
11
|
-
l.level = Logger::INFO
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def default_logger= logger
|
|
17
|
-
@default_logger = logger
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# Add universally accessible log method/accessor into Object
|
|
21
|
-
def logger *args
|
|
22
|
-
default_logger.tap do |logger|
|
|
23
|
-
logger.fatal *args unless args.empty?
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end # module
|