ib-api 972.0 → 972.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.lock +11 -12
- data/README.md +48 -3
- data/VERSION +1 -1
- data/changelog.md +11 -0
- data/lib/ib/base_properties.rb +3 -4
- data/lib/ib/messages/incoming.rb +1 -1
- data/lib/ib/messages/incoming/contract_data.rb +0 -2
- data/lib/ib/messages/incoming/ticks.rb +21 -60
- data/lib/ib/messages/outgoing.rb +4 -2
- data/lib/ib/messages/outgoing/request_marketdata.rb +10 -7
- data/lib/ib/models.rb +1 -1
- data/lib/models/ib/account.rb +0 -13
- data/lib/models/ib/bag.rb +1 -1
- data/lib/models/ib/contract.rb +26 -8
- data/lib/models/ib/contract_detail.rb +13 -7
- data/lib/models/ib/index.rb +1 -1
- data/lib/models/ib/option.rb +6 -2
- data/lib/models/ib/option_detail.rb +19 -3
- data/lib/models/ib/spread.rb +159 -0
- data/lib/models/ib/stock.rb +9 -3
- data/lib/models/ib/underlying.rb +4 -1
- data/lib/requires.rb +10 -3
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d9119cbc06f3b5e5e32b73fbe0d706b06d94c55ff2ce2ca4357e4f349f49f43f
|
4
|
+
data.tar.gz: b977e09dee0df176e673679839b738425871dbf25ca8cad4e8f497323647ecc6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '02538d6ab965ee0024cef798aebbcdff42859025a848108c5ca7af1e305cae019b1db3f9489300497fde388f5b0e659064d305ec316695d4154d20abc05277f1'
|
7
|
+
data.tar.gz: 3245c0fda19a98f6ab5b29827b246b91d31c91a5daac10bcd8bbe114d699edc117922bae11503b3219f7bd18d8b654b6be6e19365c346bad3f4201829ce6f97b
|
data/Gemfile.lock
CHANGED
@@ -7,21 +7,21 @@ GIT
|
|
7
7
|
PATH
|
8
8
|
remote: .
|
9
9
|
specs:
|
10
|
-
ib-api (972.
|
10
|
+
ib-api (972.1)
|
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.0
|
18
|
-
activesupport (= 6.0
|
19
|
-
activesupport (6.0
|
17
|
+
activemodel (6.1.0)
|
18
|
+
activesupport (= 6.1.0)
|
19
|
+
activesupport (6.1.0)
|
20
20
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
21
|
-
i18n (>=
|
22
|
-
minitest (
|
23
|
-
tzinfo (~>
|
24
|
-
zeitwerk (~> 2.
|
21
|
+
i18n (>= 1.6, < 2)
|
22
|
+
minitest (>= 5.1)
|
23
|
+
tzinfo (~> 2.0)
|
24
|
+
zeitwerk (~> 2.3)
|
25
25
|
coderay (1.1.3)
|
26
26
|
concurrent-ruby (1.1.7)
|
27
27
|
diff-lcs (1.4.4)
|
@@ -80,11 +80,10 @@ GEM
|
|
80
80
|
rspec-support (3.9.3)
|
81
81
|
shellany (0.0.1)
|
82
82
|
thor (1.0.1)
|
83
|
-
|
84
|
-
|
85
|
-
thread_safe (~> 0.1)
|
83
|
+
tzinfo (2.0.4)
|
84
|
+
concurrent-ruby (~> 1.0)
|
86
85
|
value_semantics (3.6.0)
|
87
|
-
zeitwerk (2.4.
|
86
|
+
zeitwerk (2.4.2)
|
88
87
|
|
89
88
|
PLATFORMS
|
90
89
|
ruby
|
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
|
+
__Documentation: [https://ib-ruby.github.io/ib-doc/](https://ib-ruby.github.io/ib-doc/)__ (_work in progress_)
|
6
7
|
|
7
8
|
----
|
8
9
|
`ib-ruby` offers a modular access to the TWS-API-Interface of Interactive Brokers.
|
@@ -11,12 +12,18 @@ Reimplementation of the basic functions of ib-ruby
|
|
11
12
|
|
12
13
|
----
|
13
14
|
|
14
|
-
|
15
|
+
Install in the usual way
|
15
16
|
|
17
|
+
```
|
18
|
+
$ gem install ib-api
|
19
|
+
```
|
20
|
+
|
21
|
+
In its plain vanilla usage, it just exchanges messages with the TWS. Any response is stored in the `recieved-Array`.
|
16
22
|
|
17
23
|
Even then, it needs just a few lines of code to place an order
|
18
24
|
|
19
25
|
```ruby
|
26
|
+
require 'ib-api'
|
20
27
|
# connect with default parameters
|
21
28
|
ib = IB::Connection.new
|
22
29
|
|
@@ -38,10 +45,48 @@ puts ib.recieved[:OrderStatus].to_human
|
|
38
45
|
|
39
46
|
# => ["<OrderState: Submitted #17/1528367295 from 2000 filled 0.0/100.0 at 0.0/0.0 why_held >"]
|
40
47
|
|
48
|
+
```
|
49
|
+
|
50
|
+
##### User-specific Actions
|
51
|
+
Besides storing any TWS-response in an array, callbacks are implemented.
|
52
|
+
|
53
|
+
The user subscribes to a certain response and defines the actions in a typically ruby manner. These actions
|
54
|
+
can be defined globaly
|
55
|
+
```ruby
|
56
|
+
ib = IB::Connection.new do |tws|
|
57
|
+
# Subscribe to TWS alerts/errors and order-related messages
|
58
|
+
tws.subscribe(:Alert, :OpenOrder, :OrderStatus, :OpenOrderEnd) { |msg| puts msg.to_human }
|
59
|
+
end
|
60
|
+
|
61
|
+
```
|
62
|
+
|
63
|
+
or occationally
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
# first define actions
|
67
|
+
a = ib.subscribe(:Alert, :ContractData ) do |msg|
|
68
|
+
case msg
|
69
|
+
when Messages::Incoming::Alert
|
70
|
+
if msg.code == 200 # No security found
|
71
|
+
# do someting
|
72
|
+
end
|
73
|
+
when Messages::Incoming::ContractData # security returned
|
74
|
+
# do something
|
75
|
+
|
76
|
+
end # case
|
77
|
+
end
|
78
|
+
# perform request
|
79
|
+
ib.send_message :RequestContractData, :contract => #{some contract}
|
80
|
+
|
81
|
+
# wait until the :ContractDataEnd message returned
|
82
|
+
ib.wait_for :ContractDataEnd
|
83
|
+
|
84
|
+
ib.unsubscribe a # release subscriptions
|
85
|
+
|
41
86
|
```
|
42
87
|
## Minimal TWS-Version
|
43
88
|
|
44
|
-
`ib-api` is tested via the _stable
|
89
|
+
`ib-api` is tested via the _stable IB-Gateway_ (Version 9.72) and should work with any current tws-installation.
|
45
90
|
|
46
91
|
## Tests
|
47
92
|
|
@@ -58,7 +103,7 @@ You have to edit `spec/spec.yml` and replace the `:account`-Setting with your ow
|
|
58
103
|
|
59
104
|
## Contributing
|
60
105
|
|
61
|
-
Bug reports and pull requests are welcome
|
106
|
+
Bug reports and pull requests are welcome. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
62
107
|
|
63
108
|
## Code of Conduct
|
64
109
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
972.
|
1
|
+
972.1
|
data/changelog.md
CHANGED
@@ -5,3 +5,14 @@ Changelog
|
|
5
5
|
|=++++ |=++++++++++++ |
|
6
6
|
| 30.8.2020 | migrating lib-files from ib-ruby-project |
|
7
7
|
|
8
|
+
| 28.11.2020| separating lib/model and lib/models to enable extension with
|
9
|
+
ActiveRecord/Rails and OrientDB/ActiveOrient. |
|
10
|
+
| | Introducing a Database-Switch in /lib/requires to omit
|
11
|
+
loading of model- and messages files. This has to be done
|
12
|
+
manually after assigning the database-model framework. |
|
13
|
+
|
14
|
+
| 1.12.2020 | moving model/ib/spread.rb from `ib-extensions` to `ib-api`.|
|
15
|
+
| 1.12.2020 | creating a dummy Contract#verify to guaranty safe operation of spreads |
|
16
|
+
|
17
|
+
| | Preparation of a Gem-Release |
|
18
|
+
|
data/lib/ib/base_properties.rb
CHANGED
@@ -19,11 +19,10 @@ module IB
|
|
19
19
|
|
20
20
|
# Comparison support
|
21
21
|
def content_attributes
|
22
|
-
#HashWithIndifferentAccess[attributes.reject do |(attr, _)|
|
23
22
|
#NoMethodError if a Hash is assigned to an attribute
|
24
23
|
Hash[attributes.reject do |(attr, _)|
|
25
24
|
attr.to_s =~ /(_count)\z/ ||
|
26
|
-
[:created_at, :
|
25
|
+
[:created_at, :type, # :updated_at,
|
27
26
|
:id, :order_id, :contract_id].include?(attr.to_sym)
|
28
27
|
end]
|
29
28
|
end
|
@@ -151,9 +150,9 @@ Remove all Time-Stamps from the list of Attributes
|
|
151
150
|
end
|
152
151
|
|
153
152
|
# Timestamps in lightweight models
|
154
|
-
|
153
|
+
# unless defined?(ActiveRecord::Base) && ancestors.include?(ActiveRecord::Base)
|
155
154
|
prop :created_at #, :updated_at
|
156
|
-
|
155
|
+
# end
|
157
156
|
|
158
157
|
end # included
|
159
158
|
end # module BaseProperties
|
data/lib/ib/messages/incoming.rb
CHANGED
@@ -139,7 +139,7 @@ module IB
|
|
139
139
|
TickRequestParameters = def_message [81, 0], [ :ticker_id, :int ],
|
140
140
|
[ :min_tick, :decimal],
|
141
141
|
[ :exchange, :string ],
|
142
|
-
[ :
|
142
|
+
[ :snapshot_permissions, :int ]
|
143
143
|
# class TickRequestParameters
|
144
144
|
# def load
|
145
145
|
# simple_load
|
@@ -13,7 +13,7 @@ module IB
|
|
13
13
|
"<#{self.message_type} #{type}:" +
|
14
14
|
@data.map do |key, value|
|
15
15
|
" #{key} #{value}" unless [:version, :ticker_id, :tick_type].include?(key)
|
16
|
-
end.compact.join(',') + " >"
|
16
|
+
end.compact.join('",') + " >"
|
17
17
|
end
|
18
18
|
|
19
19
|
def the_data
|
@@ -129,71 +129,32 @@ module IB
|
|
129
129
|
[:theta, :decimal_limit_2], # -2 -"-
|
130
130
|
[:under_price, :decimal_limit_1]) do
|
131
131
|
|
132
|
-
"<TickOption #{type}
|
133
|
-
"option @ #{option_price}, IV #{implied_volatility
|
134
|
-
|
132
|
+
"<TickOption #{type} " +
|
133
|
+
"option @ #{"%8.3f" % (option_price || -1)}, IV: #{"%4.3f" % (implied_volatility || -1)}, " +
|
134
|
+
"delta: #{"%5.3f" % (delta || -1)}, " +
|
135
|
+
"gamma: #{"%6.4f" % (gamma || -1)}, vega: #{ "%6.5f" % (vega || -1)}, " +
|
136
|
+
"theta: #{"%7.6f" % (theta || -1)}, pv_dividend: #{"%5.3f" % (pv_dividend || -1)}, " +
|
137
|
+
"underlying @ #{"% 8.3f" % (under_price || -1)} >"
|
135
138
|
end
|
136
139
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
reqId = decode(int, fields)
|
142
|
-
tickType = decode(int, fields)
|
143
|
-
time = decode(int, fields)
|
144
|
-
|
145
|
-
if tickType == 0:
|
146
|
-
# None
|
147
|
-
pass
|
148
|
-
elif tickType == 1 or tickType == 2:
|
149
|
-
# Last or AllLast
|
150
|
-
price = decode(float, fields)
|
151
|
-
size = decode(int, fields)
|
152
|
-
mask = decode(int, fields)
|
153
|
-
class TickAttribLast(Object):
|
154
|
-
def __init__(self):
|
155
|
-
self.pastLimit = False
|
156
|
-
self.unreported = False
|
157
|
-
|
158
|
-
def __str__(self):
|
159
|
-
return "PastLimit: %d, Unreported: %d" % (self.pastLimit, self.unreported)
|
160
|
-
|
161
|
-
tickAttribLast = TickAttribLast()
|
162
|
-
tickAttribLast.pastLimit = mask & 1 != 0
|
163
|
-
tickAttribLast.unreported = mask & 2 != 0
|
164
|
-
exchange = decode(str, fields)
|
165
|
-
specialConditions = decode(str, fields)
|
140
|
+
class TickOption
|
141
|
+
def greeks
|
142
|
+
{ delta: delta, gamma: gamma, vega: vega, theta: theta }
|
143
|
+
end
|
166
144
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
mask = decode(int, fields)
|
176
|
-
class TickAttribBidAsk(Object):
|
177
|
-
def __init__(self):
|
178
|
-
self.bidPastLow = False
|
179
|
-
self.askPastHigh = False
|
145
|
+
def iv
|
146
|
+
implied_volatility
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
def greeks?
|
151
|
+
greeks.values.any? &:present?
|
152
|
+
end
|
180
153
|
|
181
|
-
|
182
|
-
return "BidPastLow: %d, AskPastHigh: %d" % (self.bidPastLow, self.askPastHigh)
|
154
|
+
end
|
183
155
|
|
156
|
+
TickSnapshotEnd = def_message 57, [:ticker_id, :int]
|
184
157
|
|
185
|
-
tickAttribBidAsk = TickAttribBidAsk()
|
186
|
-
tickAttribBidAsk.bidPastLow = mask & 1 != 0
|
187
|
-
tickAttribBidAsk.askPastHigh = mask & 2 != 0
|
188
|
-
|
189
|
-
self.wrapper.tickByTickBidAsk(reqId, time, bidPrice, askPrice, bidSize,
|
190
|
-
askSize, tickAttribBidAsk)
|
191
|
-
elif tickType == 4:
|
192
|
-
# MidPoint
|
193
|
-
midPoint = decode(float, fields)
|
194
|
-
|
195
|
-
self.wrapper.tickByTickMidPoint(reqId, time, midPoint)
|
196
|
-
=end
|
197
158
|
TickByTick = def_message [99, 0], [:ticker_id, :int ],
|
198
159
|
[ :tick_type, :int],
|
199
160
|
[ :time, :int_date ]
|
data/lib/ib/messages/outgoing.rb
CHANGED
@@ -249,10 +249,12 @@ module IB
|
|
249
249
|
CancelHistogramData =
|
250
250
|
def_message [89,0 ] # , :(request_)id required
|
251
251
|
|
252
|
+
## Attention: If not reasonable data are used, simply nothing is returned.
|
253
|
+
## There is no error message either.
|
252
254
|
RequestCalculateImpliedVolatility = CalculateImpliedVolatility =
|
253
255
|
RequestImpliedVolatility =
|
254
256
|
def_message([ 54,3 ],:request_id, # autogenerated
|
255
|
-
[:contract, :serialize_short
|
257
|
+
[:contract, :serialize_short],
|
256
258
|
:option_price,
|
257
259
|
:under_price,
|
258
260
|
[:implied_volatility_options_count, 0],
|
@@ -262,7 +264,7 @@ module IB
|
|
262
264
|
# :volatility => double, :under_price => double }
|
263
265
|
RequestCalculateOptionPrice = CalculateOptionPrice = RequestOptionPrice =
|
264
266
|
def_message([ 55, 3], :request_id, #autogenerated if not specified
|
265
|
-
[:contract, :serialize_short
|
267
|
+
[:contract, :serialize_short],
|
266
268
|
:volatility,
|
267
269
|
:under_price,
|
268
270
|
[:implied_volatility_options_count, 0],
|
@@ -4,7 +4,8 @@ module IB
|
|
4
4
|
module Outgoing
|
5
5
|
extend Messages # def_message macros
|
6
6
|
|
7
|
-
|
7
|
+
# ==> details: https://interactivebrokers.github.io/tws-api/tick_types.html
|
8
|
+
#
|
8
9
|
# @data={:id => int: ticker_id - Must be a unique value. When the market data
|
9
10
|
# returns, it will be identified by this tag,
|
10
11
|
# if omitted, id-autogeneration process is performed
|
@@ -32,7 +33,7 @@ module IB
|
|
32
33
|
# 292 - (Wide News)
|
33
34
|
# 293 - (TradeCount)
|
34
35
|
# 295 - (VolumeRate)
|
35
|
-
# 318 - (
|
36
|
+
# 318 - (LastRTHT-Trade)
|
36
37
|
# 370 - (Participation Monitor)
|
37
38
|
# 375 - RTTrdVolumne
|
38
39
|
# 377 - CttTickTag
|
@@ -46,7 +47,11 @@ module IB
|
|
46
47
|
# 411 - Realtime Historical Volatility - 58
|
47
48
|
# 428 - Monetary Close
|
48
49
|
# 439 - MonitorTicTag
|
49
|
-
# 456/59 - IB Dividends
|
50
|
+
# 456/59 - IB Dividends, 4 comma separated values: 12 Month dividend,
|
51
|
+
# projected 12 Month dividend,
|
52
|
+
# next dividend date,
|
53
|
+
# next dividend value
|
54
|
+
# (use primary exchange instead of smart)
|
50
55
|
# 459 - RTCLOSE
|
51
56
|
# 460 - Bond Factor Multiplier
|
52
57
|
# 499 - Fee and Rebate Ratge
|
@@ -79,7 +84,7 @@ module IB
|
|
79
84
|
# :tick_list values if you use snapshot.
|
80
85
|
#
|
81
86
|
# :regulatory_snapshot => bool - With the US Value Snapshot Bundle for stocks,
|
82
|
-
# regulatory snapshots are available for 0.01 USD each.
|
87
|
+
# regulatory snapshots are available for 0.01 USD each. (applies on demo accounts as well)
|
83
88
|
# :mktDataOptions => (TagValueList) For internal use only.
|
84
89
|
# Use default value XYZ.
|
85
90
|
#
|
@@ -88,9 +93,7 @@ module IB
|
|
88
93
|
[:contract, :serialize_short, :primary_exchange], # include primary exchange in request
|
89
94
|
[:contract, :serialize_legs, []],
|
90
95
|
[:contract, :serialize_under_comp, []],
|
91
|
-
[:tick_list,
|
92
|
-
tick_list.is_a?(Array) ? tick_list.join(',') : (tick_list || '')
|
93
|
-
end, []],
|
96
|
+
[:tick_list, ->(tick_list){ tick_list.is_a?(Array) ? tick_list.join(',') : (tick_list || '')}, []],
|
94
97
|
[:snapshot, false],
|
95
98
|
[:regulatory_snapshot, false],
|
96
99
|
[:mkt_data_options, "XYZ"]
|
data/lib/ib/models.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'ib/model'
|
2
1
|
|
3
2
|
require 'models/ib/account'
|
4
3
|
require 'models/ib/account_value'
|
@@ -11,4 +10,5 @@
|
|
11
10
|
require 'models/ib/combo_leg'
|
12
11
|
require 'models/ib/execution'
|
13
12
|
require 'models/ib/bar'
|
13
|
+
require 'models/ib/spread'
|
14
14
|
require 'models/ib/condition'
|
data/lib/models/ib/account.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
module IB
|
2
2
|
class Account < IB::Model
|
3
3
|
include BaseProperties
|
4
|
-
# include Redis::Objects
|
5
4
|
# attr_accessible :alias, :account, :connected
|
6
5
|
|
7
6
|
prop :account, # String
|
@@ -10,10 +9,6 @@ module IB
|
|
10
9
|
:last_updated,
|
11
10
|
:connected => :bool
|
12
11
|
|
13
|
-
# redis_id_field :account
|
14
|
-
# value :my_alias
|
15
|
-
# value :the_account
|
16
|
-
# value :active
|
17
12
|
|
18
13
|
|
19
14
|
validates_format_of :account, :with => /\A[D]?[UF]{1}\d{5,8}\z/ , :message => 'should be (X)X00000'
|
@@ -37,14 +32,6 @@ module IB
|
|
37
32
|
Connection.logger
|
38
33
|
end
|
39
34
|
|
40
|
-
# Setze Account connect/disconnect und undate!
|
41
|
-
def connect!
|
42
|
-
update_attribute :connected , true
|
43
|
-
end
|
44
|
-
def disconnect!
|
45
|
-
update_attribute :connected , false
|
46
|
-
end
|
47
|
-
|
48
35
|
def print_type #nodoc#
|
49
36
|
(test_environment? ? "demo_" : "") + ( user? ? "user" : "advisor" )
|
50
37
|
end
|
data/lib/models/ib/bag.rb
CHANGED
data/lib/models/ib/contract.rb
CHANGED
@@ -4,7 +4,13 @@ require 'models/ib/underlying'
|
|
4
4
|
|
5
5
|
|
6
6
|
module IB
|
7
|
-
|
7
|
+
|
8
|
+
if defined?(Contract)
|
9
|
+
puts "Contract already a #{defined?(Contract)}"
|
10
|
+
# puts Contract.ancestors
|
11
|
+
# IB.send(:remove_const, 'Contract')
|
12
|
+
end
|
13
|
+
class Contract < IB::Model
|
8
14
|
include BaseProperties
|
9
15
|
|
10
16
|
# Fields are Strings unless noted otherwise
|
@@ -211,17 +217,24 @@ module IB
|
|
211
217
|
# the link to contract-details is __not__ maintained.
|
212
218
|
def essential
|
213
219
|
|
214
|
-
self_attributes = [ :
|
215
|
-
the_attributes = [ :symbol , :con_id, :exchange,
|
220
|
+
self_attributes = [ :sec_type]
|
221
|
+
the_attributes = [ :symbol , :con_id, :exchange, :right,
|
216
222
|
:currency, :expiry, :strike, :local_symbol, :last_trading_day,
|
217
223
|
:multiplier, :primary_exchange, :trading_class ]
|
218
|
-
the_hash= the_attributes.map{|x| y=
|
219
|
-
the_hash[:description] =
|
220
|
-
|
224
|
+
the_hash= the_attributes.map{|x| y= self.send(x); [x,y] if y.present? }.compact.to_h
|
225
|
+
the_hash[:description] =
|
226
|
+
if @description.present?
|
227
|
+
@description
|
228
|
+
elsif contract_detail.present?
|
229
|
+
contract_detail.long_name
|
230
|
+
else
|
231
|
+
""
|
232
|
+
end
|
233
|
+
self.class.new the_hash.merge( self_attributes.map{|x| y = self.send(x); [x,y] unless y == :none }.compact.to_h )
|
221
234
|
end
|
222
235
|
|
223
236
|
|
224
|
-
# creates a new Contract substituting attributes by the
|
237
|
+
# creates a new Contract substituting attributes by the provided key-value pairs.
|
225
238
|
#
|
226
239
|
# con_id is resetted
|
227
240
|
def merge **new_attributes
|
@@ -327,6 +340,10 @@ module IB
|
|
327
340
|
self[:sec_type] == 'IND'
|
328
341
|
end
|
329
342
|
|
343
|
+
|
344
|
+
def verify # :nodoc:
|
345
|
+
error "verify must be overloaded. Please require at least `ib/verify` from the `ib-extenstions` gem "
|
346
|
+
end
|
330
347
|
=begin
|
331
348
|
From the release notes of TWS 9.50
|
332
349
|
|
@@ -365,7 +382,7 @@ In places where these terms are used to indicate a concept, we have left them as
|
|
365
382
|
|
366
383
|
### Now let's deal with Contract subclasses
|
367
384
|
|
368
|
-
|
385
|
+
require_relative 'option'
|
369
386
|
require 'models/ib/bag'
|
370
387
|
require 'models/ib/forex'
|
371
388
|
require 'models/ib/future'
|
@@ -378,6 +395,7 @@ In places where these terms are used to indicate a concept, we have left them as
|
|
378
395
|
Subclasses = Hash.new(Contract)
|
379
396
|
Subclasses[:bag] = IB::Bag
|
380
397
|
Subclasses[:option] = IB::Option
|
398
|
+
Subclasses[:future_option] = IB::FutureOption
|
381
399
|
Subclasses[:future] = IB::Future
|
382
400
|
Subclasses[:stock] = IB::Stock
|
383
401
|
Subclasses[:forex] = IB::Forex
|
@@ -95,13 +95,19 @@ module IB
|
|
95
95
|
end
|
96
96
|
|
97
97
|
def to_human
|
98
|
-
ret = "<ContractDetails #{long_name},
|
99
|
-
ret << "
|
100
|
-
ret << "
|
101
|
-
|
102
|
-
|
103
|
-
ret <<"
|
104
|
-
ret <<"
|
98
|
+
ret = "<ContractDetails #{long_name}, "
|
99
|
+
ret << "--> #{market_name}, " if market_name.present?
|
100
|
+
ret << "/C/ #{category}, /I/ #{industry} /SC/ #{subcategory}, " if category.present?
|
101
|
+
ret << "Underlying:#{under_symbol}[#{under_sec_type}](#{under_con_id}), " unless under_con_id.zero?
|
102
|
+
ret << "ev_multiplier:#{ev_multiplier}, " if ev_multiplier.present?
|
103
|
+
ret << "convertible:#{convertible}, " if convertible
|
104
|
+
ret << "coupon:#{coupon}, " if coupon.present? && coupon > 0
|
105
|
+
ret << "md_size_multiplier:#{md_size_multiplier}, min_tick:#{min_tick}, "
|
106
|
+
ret << "next_option_partial:#{next_option_partial}, " if next_option_partial.present?
|
107
|
+
ret << "price_magnifier:#{price_magnifier}, "
|
108
|
+
ret << "puttable:#{puttable}, " if puttable.present?
|
109
|
+
ret << "sec_id-list:#{sec_id_list}, " unless sec_id_list.empty?
|
110
|
+
ret <<"valid exchanges: #{ valid_exchanges}; order types: #{order_types} >"
|
105
111
|
end
|
106
112
|
|
107
113
|
end # class ContractDetail
|
data/lib/models/ib/index.rb
CHANGED
data/lib/models/ib/option.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
#require_relative 'contract'
|
2
|
+
require_relative 'option_detail'
|
3
3
|
|
4
4
|
module IB
|
5
5
|
class Option < Contract
|
@@ -75,4 +75,8 @@ module IB
|
|
75
75
|
end
|
76
76
|
|
77
77
|
end # class Option
|
78
|
+
|
79
|
+
class FutureOption < Option
|
80
|
+
|
81
|
+
end
|
78
82
|
end # module IB
|
@@ -17,7 +17,8 @@ module IB
|
|
17
17
|
:next_strike,
|
18
18
|
:prev_expiry,
|
19
19
|
:next_expiry,
|
20
|
-
:option_price
|
20
|
+
:option_price,
|
21
|
+
:updated_at
|
21
22
|
belongs_to :option
|
22
23
|
|
23
24
|
# returns true if all datafields are filled with reasonal data
|
@@ -32,19 +33,34 @@ module IB
|
|
32
33
|
|
33
34
|
def greeks?
|
34
35
|
fields= [ :delta, :gamma, :vega, :theta,
|
35
|
-
:implied_volatility
|
36
|
+
:implied_volatility]
|
36
37
|
|
37
38
|
!fields.detect{|y| self.send(y).nil?}
|
38
39
|
|
39
40
|
end
|
40
41
|
|
42
|
+
def prices?
|
43
|
+
fields = [:implied_volatility, :under_price, :option_price]
|
44
|
+
!fields.detect{|y| self.send(y).nil?}
|
45
|
+
end
|
46
|
+
|
47
|
+
def iv
|
48
|
+
implied_volatility
|
49
|
+
end
|
50
|
+
|
51
|
+
def spread
|
52
|
+
bid_price - ask_price
|
53
|
+
end
|
54
|
+
|
41
55
|
def to_human
|
42
56
|
outstr= ->( item ) { if item.nil? then "--" else sprintf("%g" , item) end }
|
43
57
|
att = " optionPrice: #{ outstr[ option_price ]}, UnderlyingPrice: #{ outstr[ under_price] } impl.Vola: #{ outstr[ implied_volatility ]} ; dividend: #{ outstr[ pv_dividend ]}; "
|
44
58
|
greeks = "Greeks:: delta: #{ outstr[ delta ] }; gamma: #{ outstr[ gamma ]}, vega: #{ outstr[ vega ] }; theta: #{ outstr[ theta ]}"
|
45
59
|
prices= " close: #{ outstr[ close_price ]}; bid: #{ outstr[ bid_price ]}; ask: #{ outstr[ ask_price ]} "
|
46
60
|
if complete?
|
47
|
-
|
61
|
+
"< "+ prices + "\n" + att + "\n" + greeks + " >"
|
62
|
+
elsif prices?
|
63
|
+
"< " + att + greeks + " >"
|
48
64
|
else
|
49
65
|
"< " + greeks + " >"
|
50
66
|
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
#require 'ib/verify'
|
2
|
+
module IB
|
3
|
+
class Spread < Bag
|
4
|
+
has_many :legs
|
5
|
+
|
6
|
+
using IBSupport
|
7
|
+
|
8
|
+
=begin
|
9
|
+
Parameters: front: YYYMM(DD)
|
10
|
+
back: {n}w, {n}d or YYYYMM(DD)
|
11
|
+
|
12
|
+
Adds (or substracts) relative (back) measures to the front month, just passes absolute YYYYMM(DD) value
|
13
|
+
|
14
|
+
front: 201809 back: 2m (-1m) --> 201811 (201808)
|
15
|
+
front: 20180908 back: 1w (-1w) --> 20180918 (20180902)
|
16
|
+
=end
|
17
|
+
|
18
|
+
def transform_distance front, back
|
19
|
+
# Check Format of back: 201809 --> > 200.000
|
20
|
+
# 20180989 ---> 20.000.000
|
21
|
+
start_date = front.to_i < 20000000 ? Date.strptime(front.to_s,"%Y%m") : Date.strptime(front.to_s,"%Y%m%d")
|
22
|
+
nb = if back.to_i > 200000
|
23
|
+
back.to_i
|
24
|
+
elsif back[-1] == "w" && front.to_i > 20000000
|
25
|
+
start_date + (back.to_i * 7)
|
26
|
+
elsif back[-1] == "m" && front.to_i > 200000
|
27
|
+
start_date >> back.to_i
|
28
|
+
else
|
29
|
+
error "Wrong date #{back} required format YYYMM, YYYYMMDD ord {n}w or {n}m"
|
30
|
+
end
|
31
|
+
if nb.is_a?(Date)
|
32
|
+
if back[-1]=='w'
|
33
|
+
nb.strftime("%Y%m%d")
|
34
|
+
else
|
35
|
+
nb.strftime("%Y%m")
|
36
|
+
end
|
37
|
+
else
|
38
|
+
nb
|
39
|
+
end
|
40
|
+
end # def
|
41
|
+
|
42
|
+
def to_human
|
43
|
+
self.description
|
44
|
+
end
|
45
|
+
|
46
|
+
def calculate_spread_value( array_of_portfolio_values )
|
47
|
+
array_of_portfolio_values.map{|x| x.send yield }.sum if block_given?
|
48
|
+
end
|
49
|
+
|
50
|
+
def fake_portfolio_position( array_of_portfolio_values )
|
51
|
+
calculate_spread_value= ->( a_o_p_v, attribute ) do
|
52
|
+
a_o_p_v.map{|x| x.send attribute }.sum
|
53
|
+
end
|
54
|
+
ar=array_of_portfolio_values
|
55
|
+
IB::PortfolioValue.new contract: self,
|
56
|
+
average_cost: calculate_spread_value[ar, :average_cost],
|
57
|
+
market_price: calculate_spread_value[ar, :market_price],
|
58
|
+
market_value: calculate_spread_value[ar, :market_value],
|
59
|
+
unrealized_pnl: calculate_spread_value[ar, :unrealized_pnl],
|
60
|
+
realized_pnl: calculate_spread_value[ar, :realized_pnl],
|
61
|
+
position: 0
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
def serialize_rabbit
|
68
|
+
{ "Spread" => serialize( :option, :trading_class ),
|
69
|
+
'legs' => legs.map{ |y| y.serialize :option, :trading_class }, 'combo_legs' => combo_legs.map(&:serialize),
|
70
|
+
'misc' => [description]
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
# adds a leg to any spread
|
75
|
+
#
|
76
|
+
# Parameter:
|
77
|
+
# contract: Will be verified. Contract.essential is added to legs-array
|
78
|
+
# action: :buy or :sell
|
79
|
+
# weight:
|
80
|
+
# ratio:
|
81
|
+
#
|
82
|
+
# Default: action: :buy, weight: 1
|
83
|
+
|
84
|
+
def add_leg contract, **leg_params
|
85
|
+
evaluated_contracts = []
|
86
|
+
nc = contract.verify.first.essential
|
87
|
+
# weigth = 1 --> sets Combo.side to buy and overwrites the action statement
|
88
|
+
# leg_params[:weight] = 1 unless leg_params.key?(:weight) || leg_params.key?(:ratio)
|
89
|
+
if nc.is_a?( IB::Contract) && nc.con_id.present?
|
90
|
+
the_leg= ComboLeg.new( nc.attributes.slice( :con_id, :exchange )
|
91
|
+
.merge( leg_params ))
|
92
|
+
self.combo_legs << the_leg
|
93
|
+
self.description = description + " added #{nc.to_human}" rescue "Spread: #{nc.to_human}"
|
94
|
+
self.legs << nc
|
95
|
+
end
|
96
|
+
self # return value to enable chaining
|
97
|
+
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
# removes the contract from the spread definition
|
102
|
+
#
|
103
|
+
def remove_leg contract
|
104
|
+
contract.verify do |c|
|
105
|
+
legs.delete_if { |x| x.con_id == c.con_id }
|
106
|
+
combo_legs.delete_if { |x| x.con_id == c.con_id }
|
107
|
+
self.description = description + " removed #{c.to_human}"
|
108
|
+
end
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
def essential
|
114
|
+
legs.each{ |x| x.essential }
|
115
|
+
self
|
116
|
+
end
|
117
|
+
def multiplier
|
118
|
+
(legs.map(&:multiplier).sum/legs.size).to_i
|
119
|
+
end
|
120
|
+
|
121
|
+
# provide a negative con_id
|
122
|
+
def con_id
|
123
|
+
-legs.map(&:con_id).sum
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
def non_guaranteed= x
|
128
|
+
super.merge combo_params: [ ['NonGuaranteed', x] ]
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
def non_guaranteed
|
133
|
+
combo_params['NonGuaranteed']
|
134
|
+
end
|
135
|
+
# optional: specify default order prarmeters for all spreads
|
136
|
+
# def order_requirements
|
137
|
+
# super.merge symbol: symbol
|
138
|
+
# end
|
139
|
+
|
140
|
+
|
141
|
+
def self.build_from_json container
|
142
|
+
read_leg = ->(a) do
|
143
|
+
IB::ComboLeg.new :con_id => a.read_int,
|
144
|
+
:ratio => a.read_int,
|
145
|
+
:action => a.read_string,
|
146
|
+
:exchange => a.read_string
|
147
|
+
|
148
|
+
end
|
149
|
+
object= self.new container['Spread'].read_contract
|
150
|
+
object.legs = container['legs'].map{|x| IB::Contract.build x.read_contract}
|
151
|
+
object.combo_legs = container['combo_legs'].map{ |x| read_leg[ x ] }
|
152
|
+
object.description = container['misc'].read_string
|
153
|
+
object
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
end
|
data/lib/models/ib/stock.rb
CHANGED
@@ -1,14 +1,20 @@
|
|
1
|
-
require_relative 'contract'
|
1
|
+
#require_relative 'contract'
|
2
2
|
module IB
|
3
3
|
class Stock < IB::Contract
|
4
4
|
validates_format_of :sec_type, :with => /\Astock\z/,
|
5
5
|
:message => "should be a Stock"
|
6
|
+
validates_format_of :symbol, with: /\A.*\z/,
|
7
|
+
message: 'should not be blank'
|
6
8
|
def default_attributes
|
7
9
|
super.merge :sec_type => :stock, currency:'USD', exchange:'SMART'
|
8
10
|
end
|
9
11
|
|
10
|
-
def to_human
|
11
|
-
att = [ symbol,
|
12
|
+
def to_human
|
13
|
+
att = [ symbol,
|
14
|
+
currency, ( exchange == 'SMART' ? nil: exchange ),
|
15
|
+
(primary_exchange.present? && !primary_exchange.empty? ? primary_exchange : nil),
|
16
|
+
@description.present? ? " (#{@description}) " : nil,
|
17
|
+
].compact
|
12
18
|
"<Stock: " + att.join(" ") + ">"
|
13
19
|
end
|
14
20
|
|
data/lib/models/ib/underlying.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
module IB
|
2
|
+
if defined?(Underlying)
|
3
|
+
puts "Underlying already a #{defined?(Underlying)}"
|
4
|
+
else
|
2
5
|
|
3
6
|
# Calculated characteristics of underlying Contract (volatile)
|
4
7
|
class Underlying < IB::Model
|
@@ -30,5 +33,5 @@ module IB
|
|
30
33
|
|
31
34
|
end # class Underlying
|
32
35
|
UnderComp = Underlying
|
33
|
-
|
36
|
+
end
|
34
37
|
end # module IB
|
data/lib/requires.rb
CHANGED
@@ -7,6 +7,13 @@ require 'ib/errors'
|
|
7
7
|
require 'ib/constants'
|
8
8
|
require 'ib/connection'
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
# An external model- or database-driver provides the base class for models
|
11
|
+
# if the constant DB is defined
|
12
|
+
#
|
13
|
+
# basically IB::Model has to be assigned to the substitute base class
|
14
|
+
# the database-driver requires models and messages at the appropoate time
|
15
|
+
unless defined?(DB)
|
16
|
+
require 'ib/model'
|
17
|
+
require 'ib/models'
|
18
|
+
require 'ib/messages'
|
19
|
+
end
|
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: '972.
|
4
|
+
version: '972.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: 2020-
|
11
|
+
date: 2020-12-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -172,6 +172,7 @@ files:
|
|
172
172
|
- lib/models/ib/order.rb
|
173
173
|
- lib/models/ib/order_state.rb
|
174
174
|
- lib/models/ib/portfolio_value.rb
|
175
|
+
- lib/models/ib/spread.rb
|
175
176
|
- lib/models/ib/stock.rb
|
176
177
|
- lib/models/ib/underlying.rb
|
177
178
|
- lib/models/ib/vertical.rb
|