ib-extensions 1.0
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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +112 -0
- data/Guardfile +24 -0
- data/README.md +99 -0
- data/Rakefile +6 -0
- data/bin/console +96 -0
- data/bin/console.yml +3 -0
- data/bin/gateway.rb +97 -0
- data/bin/setup +8 -0
- data/changelog.md +31 -0
- data/examples/cancel_orders +74 -0
- data/examples/eod +35 -0
- data/examples/input.rb +475 -0
- data/examples/market_price +57 -0
- data/examples/option_chain +67 -0
- data/examples/place_and_modify_order +162 -0
- data/examples/place_bracket_order +62 -0
- data/examples/place_butterfly_order +104 -0
- data/examples/place_combo_order +70 -0
- data/examples/place_limit_order +82 -0
- data/examples/place_the_limit_order +145 -0
- data/examples/volatility_research +139 -0
- data/examples/what_if_order +90 -0
- data/ib-extensions.gemspec +37 -0
- data/lib/ib-gateway.rb +5 -0
- data/lib/ib/alerts/base-alert.rb +128 -0
- data/lib/ib/alerts/gateway-alerts.rb +15 -0
- data/lib/ib/alerts/order-alerts.rb +68 -0
- data/lib/ib/eod.rb +152 -0
- data/lib/ib/extensions.rb +9 -0
- data/lib/ib/extensions/contract.rb +37 -0
- data/lib/ib/extensions/version.rb +5 -0
- data/lib/ib/flex.rb +150 -0
- data/lib/ib/gateway.rb +425 -0
- data/lib/ib/gateway/account-infos.rb +115 -0
- data/lib/ib/gateway/order-handling.rb +150 -0
- data/lib/ib/market-price.rb +134 -0
- data/lib/ib/models/account.rb +329 -0
- data/lib/ib/models/spread.rb +159 -0
- data/lib/ib/option-chain.rb +198 -0
- data/lib/ib/option-greeks.rb +88 -0
- data/lib/ib/order-prototypes.rb +110 -0
- data/lib/ib/order_prototypes/abstract.rb +67 -0
- data/lib/ib/order_prototypes/combo.rb +46 -0
- data/lib/ib/order_prototypes/forex.rb +40 -0
- data/lib/ib/order_prototypes/limit.rb +177 -0
- data/lib/ib/order_prototypes/market.rb +116 -0
- data/lib/ib/order_prototypes/pegged.rb +173 -0
- data/lib/ib/order_prototypes/premarket.rb +31 -0
- data/lib/ib/order_prototypes/stop.rb +202 -0
- data/lib/ib/order_prototypes/volatility.rb +39 -0
- data/lib/ib/spread-prototypes.rb +62 -0
- data/lib/ib/spread_prototypes/butterfly.rb +79 -0
- data/lib/ib/spread_prototypes/calendar.rb +85 -0
- data/lib/ib/spread_prototypes/stock-spread.rb +48 -0
- data/lib/ib/spread_prototypes/straddle.rb +75 -0
- data/lib/ib/spread_prototypes/strangle.rb +96 -0
- data/lib/ib/spread_prototypes/vertical.rb +84 -0
- data/lib/ib/verify.rb +226 -0
- metadata +206 -0
@@ -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
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'ib/verify'
|
2
|
+
require 'ib/market-price'
|
3
|
+
module IB
|
4
|
+
# define a custom ErrorClass which can be fired if a verification fails
|
5
|
+
class VerifyError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class Contract
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
# returns the Option Chain of the contract (if available)
|
13
|
+
#
|
14
|
+
## parameters
|
15
|
+
### right:: :call, :put, :straddle
|
16
|
+
### ref_price:: :request or a numeric value
|
17
|
+
### sort:: :strike, :expiry
|
18
|
+
### exchange:: List of Exchanges to be queried (Blank for all available Exchanges)
|
19
|
+
def option_chain ref_price: :request, right: :put, sort: :strike, exchange: ''
|
20
|
+
|
21
|
+
ib = Connection.current
|
22
|
+
|
23
|
+
## Enable Cashing of Definition-Matrix
|
24
|
+
@option_chain_definition ||= []
|
25
|
+
|
26
|
+
my_req = nil; finalize= false
|
27
|
+
|
28
|
+
# -----------------------------------------------------------------------------------------------------
|
29
|
+
# get OptionChainDefinition from IB ( instantiate cashed Hash )
|
30
|
+
if @option_chain_definition.blank?
|
31
|
+
sub_sdop = ib.subscribe( :SecurityDefinitionOptionParameterEnd ) { |msg| finalize = true if msg.request_id == my_req }
|
32
|
+
sub_ocd = ib.subscribe( :OptionChainDefinition ) do | msg |
|
33
|
+
if msg.request_id == my_req
|
34
|
+
message = msg.data
|
35
|
+
# transfer the first record to @option_chain_definition
|
36
|
+
if @option_chain_definition.blank?
|
37
|
+
@option_chain_definition = msg.data
|
38
|
+
|
39
|
+
end
|
40
|
+
# override @option_chain_definition if a decent combination of attributes is met
|
41
|
+
# us- options: use the smart dataset
|
42
|
+
# other options: prefer options of the default trading class
|
43
|
+
if message[:exchange] == 'SMART'
|
44
|
+
@option_chain_definition = msg.data
|
45
|
+
finalize = true
|
46
|
+
end
|
47
|
+
if @option_chain_definition.blank? && message[:trading_class] == symbol
|
48
|
+
@option_chain_definition = msg.data
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
c = verify.first # ensure a complete set of attributes
|
54
|
+
my_req = ib.send_message :RequestOptionChainDefinition, con_id: c.con_id,
|
55
|
+
symbol: c.symbol,
|
56
|
+
exchange: c.sec_type == :future ? c.exchange : "", # BOX,CBOE',
|
57
|
+
sec_type: c[:sec_type]
|
58
|
+
|
59
|
+
Thread.new do
|
60
|
+
|
61
|
+
Timeout::timeout(1, IB::TransmissionError,"OptionChainDefinition not received" ) do
|
62
|
+
loop{ sleep 0.1; break if finalize }
|
63
|
+
end
|
64
|
+
ib.unsubscribe sub_sdop , sub_ocd
|
65
|
+
end.join
|
66
|
+
else
|
67
|
+
Connection.logger.error { "#{to_human} : using cached data" }
|
68
|
+
end
|
69
|
+
|
70
|
+
# -----------------------------------------------------------------------------------------------------
|
71
|
+
# select values and assign to options
|
72
|
+
#
|
73
|
+
unless @option_chain_definition.blank?
|
74
|
+
requested_strikes = if block_given?
|
75
|
+
ref_price = market_price if ref_price == :request
|
76
|
+
if ref_price.nil?
|
77
|
+
ref_price = @option_chain_definition[:strikes].min +
|
78
|
+
( @option_chain_definition[:strikes].max -
|
79
|
+
@option_chain_definition[:strikes].min ) / 2
|
80
|
+
Connection.logger.error{ "#{to_human} :: market price not set – using midpoint of available strikes instead: #{ref_price.to_f}" }
|
81
|
+
end
|
82
|
+
atm_strike = @option_chain_definition[:strikes].min_by { |x| (x - ref_price).abs }
|
83
|
+
the_grouped_strikes = @option_chain_definition[:strikes].group_by{|e| e <=> atm_strike}
|
84
|
+
begin
|
85
|
+
the_strikes = yield the_grouped_strikes
|
86
|
+
the_strikes.unshift atm_strike unless the_strikes.first == atm_strike # the first item is the atm-strike
|
87
|
+
the_strikes
|
88
|
+
rescue
|
89
|
+
Connection.logger.error "#{to_human} :: not enough strikes :#{@option_chain_definition[:strikes].map(&:to_f).join(',')} "
|
90
|
+
[]
|
91
|
+
end
|
92
|
+
else
|
93
|
+
@option_chain_definition[:strikes]
|
94
|
+
end
|
95
|
+
|
96
|
+
# third Friday of a month
|
97
|
+
monthly_expirations = @option_chain_definition[:expirations].find_all{|y| (15..21).include? y.day }
|
98
|
+
# puts @option_chain_definition.inspect
|
99
|
+
option_prototype = -> ( ltd, strike ) do
|
100
|
+
IB::Option.new( symbol: symbol,
|
101
|
+
exchange: @option_chain_definition[:exchange],
|
102
|
+
trading_class: @option_chain_definition[:trading_class],
|
103
|
+
multiplier: @option_chain_definition[:multiplier],
|
104
|
+
currency: currency,
|
105
|
+
last_trading_day: ltd,
|
106
|
+
strike: strike,
|
107
|
+
right: right )
|
108
|
+
end
|
109
|
+
options_by_expiry = -> ( schema ) do
|
110
|
+
# Array: [ yymm -> Options] prepares for the correct conversion to a Hash
|
111
|
+
Hash[ monthly_expirations.map do | l_t_d |
|
112
|
+
[ l_t_d.strftime('%y%m').to_i , schema.map{ | strike | option_prototype[ l_t_d, strike ]}.compact ]
|
113
|
+
end ] # by Hash[ ]
|
114
|
+
end
|
115
|
+
options_by_strike = -> ( schema ) do
|
116
|
+
Hash[ schema.map do | strike |
|
117
|
+
[ strike , monthly_expirations.map{ | l_t_d | option_prototype[ l_t_d, strike ]}.compact ]
|
118
|
+
end ] # by Hash[ ]
|
119
|
+
end
|
120
|
+
|
121
|
+
if sort == :strike
|
122
|
+
options_by_strike[ requested_strikes ]
|
123
|
+
else
|
124
|
+
options_by_expiry[ requested_strikes ]
|
125
|
+
end
|
126
|
+
else
|
127
|
+
Connection.logger.error "#{to_human} ::No Options available"
|
128
|
+
nil # return_value
|
129
|
+
end
|
130
|
+
end # def
|
131
|
+
|
132
|
+
# return a set of AtTheMoneyOptions
|
133
|
+
def atm_options ref_price: :request, right: :put
|
134
|
+
option_chain( right: right, ref_price: ref_price, sort: :expiry) do | chain |
|
135
|
+
chain[0]
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
# return InTheMoneyOptions
|
142
|
+
def itm_options count: 5, right: :put, ref_price: :request, sort: :strike
|
143
|
+
option_chain( right: right, ref_price: ref_price, sort: sort ) do | chain |
|
144
|
+
if right == :put
|
145
|
+
above_market_price_strikes = chain[1][0..count-1]
|
146
|
+
else
|
147
|
+
below_market_price_strikes = chain[-1][-count..-1].reverse
|
148
|
+
end # branch
|
149
|
+
end
|
150
|
+
end # def
|
151
|
+
|
152
|
+
# return OutOfTheMoneyOptions
|
153
|
+
def otm_options count: 5, right: :put, ref_price: :request, sort: :strike
|
154
|
+
option_chain( right: right, ref_price: ref_price, sort: sort ) do | chain |
|
155
|
+
if right == :put
|
156
|
+
# puts "Chain: #{chain}"
|
157
|
+
below_market_price_strikes = chain[-1][-count..-1].reverse
|
158
|
+
else
|
159
|
+
above_market_price_strikes = chain[1][0..count-1]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
def associate_ticdata
|
166
|
+
|
167
|
+
tws= IB::Connection.current # get the initialized ib-ruby instance
|
168
|
+
the_id = nil
|
169
|
+
finalize= false
|
170
|
+
# switch to delayed data
|
171
|
+
tws.send_message :RequestMarketDataType, :market_data_type => :delayed
|
172
|
+
|
173
|
+
s_id = tws.subscribe(:TickSnapshotEnd) { |msg| finalize = true if msg.ticker_id == the_id }
|
174
|
+
|
175
|
+
sub_id = tws.subscribe(:TickPrice, :TickSize, :TickGeneric, :TickOption) do |msg|
|
176
|
+
self.bars << msg.the_data if msg.ticker_id == the_id
|
177
|
+
end
|
178
|
+
|
179
|
+
# initialize »the_id« that is used to identify the received tick messages
|
180
|
+
# by firing the market data request
|
181
|
+
the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
|
182
|
+
|
183
|
+
#keep the method-call running until the request finished
|
184
|
+
#and cancel subscriptions to the message handler.
|
185
|
+
Thread.new do
|
186
|
+
i=0; loop{ i+=1; sleep 0.1; break if finalize || i > 1000 }
|
187
|
+
tws.unsubscribe sub_id
|
188
|
+
tws.unsubscribe s_id
|
189
|
+
#puts "#{symbol} data gathered"
|
190
|
+
end # method returns the (running) thread
|
191
|
+
|
192
|
+
end # def
|
193
|
+
end # class
|
194
|
+
|
195
|
+
|
196
|
+
|
197
|
+
|
198
|
+
end # module
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module IB
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
class Option
|
6
|
+
# Ask for the Greeks and implied Vola
|
7
|
+
#
|
8
|
+
# The result can be customized by a provided block.
|
9
|
+
#
|
10
|
+
# IB::Symbols::Options.aapl.greeks{ |x| x }
|
11
|
+
# -> {"bid"=>0.10142e3, "ask"=>0.10144e3, "last"=>0.10142e3, "close"=>0.10172e3}
|
12
|
+
#
|
13
|
+
# Possible values for Parameter :what --> :all :model, :bid, :ask, :bidask, :last
|
14
|
+
#
|
15
|
+
def request_greeks delayed: true, what: :model, thread: false
|
16
|
+
|
17
|
+
tws= Connection.current # get the initialized ib-ruby instance
|
18
|
+
# define requested tick-attributes
|
19
|
+
request_data_type = IB::MARKET_DATA_TYPES.rassoc( delayed ? :frozen_delayed : :frozen ).first
|
20
|
+
# possible types = [ [ :delayed_model_option , :model_option ] , [:delayed_last_option , :last_option ],
|
21
|
+
# [ :delayed_bid_option , :bid_option ], [ :delayed_ask_option , :ask_option ] ]
|
22
|
+
tws.send_message :RequestMarketDataType, :market_data_type => request_data_type
|
23
|
+
tickdata = []
|
24
|
+
|
25
|
+
self.greek = OptionDetail.new if greek.nil?
|
26
|
+
greek.updated_at = Time.now
|
27
|
+
|
28
|
+
#keep the method-call running until the request finished
|
29
|
+
#and cancel subscriptions to the message handler
|
30
|
+
# method returns the (running) thread
|
31
|
+
th = Thread.new do
|
32
|
+
the_id = nil
|
33
|
+
finalize= false
|
34
|
+
# subscribe to TickPrices
|
35
|
+
s_id = tws.subscribe(:TickSnapshotEnd) { |msg| finalize = true if msg.ticker_id == the_id }
|
36
|
+
e_id = tws.subscribe(:Alert){|x| finalize = true if [200,353].include?( x.code) && x.error_id == the_id }
|
37
|
+
# TWS Error 200: No security definition has been found for the request
|
38
|
+
# TWS Error 354: Requested market data is not subscribed.
|
39
|
+
|
40
|
+
sub_id = tws.subscribe(:TickOption ) do |msg| #, :TickSize, :TickGeneric do |msg|
|
41
|
+
if msg.ticker_id == the_id && tickdata.is_a?(Array) # do nothing if tickdata have already gathered
|
42
|
+
case msg.type
|
43
|
+
when /ask/
|
44
|
+
greek.ask_price = msg.option_price unless msg.option_price.nil?
|
45
|
+
tickdata << msg if [ :all, :ask, :bidask ].include?( what )
|
46
|
+
|
47
|
+
when /bid/
|
48
|
+
greek.bid_price = msg.option_price unless msg.option_price.nil?
|
49
|
+
tickdata << msg if [ :all, :bid, :bidask ].include?( what )
|
50
|
+
when /last/
|
51
|
+
tickdata << msg if msg.type =~ /last/
|
52
|
+
when /model/
|
53
|
+
# transfer attributs from TickOption to OptionDetail
|
54
|
+
bf =[ :option_price, :implied_volatility, :under_price, :pv_dividend ]
|
55
|
+
(bf + msg.greeks.keys).each{ |a| greek.send( a.to_s+"=", msg.send( a)) }
|
56
|
+
tickdata << msg if [ :all, :model ].include?( what )
|
57
|
+
end
|
58
|
+
tickdata = tickdata &.first unless [:bidask, :all].include? what
|
59
|
+
finalize = true if tickdata.is_a?(IB::Messages::Incoming::TickOption) || (tickdata.size == 2 && what== :bidask) || (tickdata.size == 4 && what == :all)
|
60
|
+
end
|
61
|
+
end # if sub_id
|
62
|
+
|
63
|
+
# initialize »the_id« that is used to identify the received tick messages
|
64
|
+
# by firing the market data request
|
65
|
+
the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
|
66
|
+
|
67
|
+
begin
|
68
|
+
# todo implement config-feature to set timeout in configuration (DRY-Feature)
|
69
|
+
Timeout::timeout(5) do # max 5 sec.
|
70
|
+
loop{ break if finalize ; sleep 0.05 }
|
71
|
+
# reduce :close_price delayed_close to close a.s.o
|
72
|
+
self.misc = tickdata if thread # store internally if in thread modus
|
73
|
+
end
|
74
|
+
rescue Timeout::Error
|
75
|
+
Connection.logger.info{ "#{to_human} --> No Marketdata received " }
|
76
|
+
end
|
77
|
+
tws.unsubscribe sub_id, s_id, e_id
|
78
|
+
end # thread
|
79
|
+
if thread
|
80
|
+
th # return thread
|
81
|
+
else
|
82
|
+
th.join
|
83
|
+
tickdata # return
|
84
|
+
end
|
85
|
+
end #
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# These modules are used to facilitate referencing of most common Ordertypes
|
2
|
+
|
3
|
+
module IB
|
4
|
+
module OrderPrototype
|
5
|
+
|
6
|
+
|
7
|
+
#The Module OrderPrototypes provides a wrapper to define even complex ordertypes.
|
8
|
+
#
|
9
|
+
#The Order is build by
|
10
|
+
#
|
11
|
+
# IB::<OrderPrototye>.order
|
12
|
+
#
|
13
|
+
#A description is available through
|
14
|
+
#
|
15
|
+
# puts IB::<OrderPrototype>.summary
|
16
|
+
#
|
17
|
+
#Nessesary and optional arguments are printed by
|
18
|
+
#
|
19
|
+
# puts IB::<OrderPrototype>.parameters
|
20
|
+
#
|
21
|
+
#Orders can be setup interactively
|
22
|
+
#
|
23
|
+
# > d = Discretionary.order
|
24
|
+
# Traceback (most recent call last): (..)
|
25
|
+
# IB::ArgumentError (IB::Discretionary.order -> A necessary field is missing:
|
26
|
+
# action: --> {"B"=>:buy, "S"=>:sell, "T"=>:short, "X"=>:short_exempt})
|
27
|
+
# > d = Discretionary.order action: :buy
|
28
|
+
# IB::ArgumentError (IB::Discretionary.order -> A necessary field is missing:
|
29
|
+
# total_quantity: --> also aliased as :size)
|
30
|
+
# > d = Discretionary.order action: :buy, size: 100
|
31
|
+
# Traceback (most recent call last):
|
32
|
+
# IB::ArgumentError (IB::Discretionary.order -> A necessary field is missing: limit_price: --> decimal)
|
33
|
+
#
|
34
|
+
#
|
35
|
+
#
|
36
|
+
#Prototypes are defined as module. They extend OrderPrototype and establish singleton methods, which
|
37
|
+
#can adress and extend similar methods from OrderPrototype.
|
38
|
+
#
|
39
|
+
#
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
def order **fields
|
44
|
+
|
45
|
+
# special treatment of size: positive numbers --> buy order, negative: sell
|
46
|
+
if fields[:size].present? && fields[:action].blank?
|
47
|
+
error "Size = 0 is not possible" if fields[:size].zero?
|
48
|
+
fields[:action] = fields[:size] >0 ? :buy : :sell
|
49
|
+
fields[:size] = fields[:size].abs
|
50
|
+
end
|
51
|
+
# change aliases to the original. We are modifying the fields-hash.
|
52
|
+
fields.keys.each{|x| fields[aliases.key(x)] = fields.delete(x) if aliases.has_value?(x)}
|
53
|
+
# inlcude defaults (arguments override defaults)
|
54
|
+
the_arguments = defaults.merge fields
|
55
|
+
# check if requirements are fullfilled
|
56
|
+
necessary = requirements.keys.detect{|y| the_arguments[y].nil?}
|
57
|
+
if necessary.present?
|
58
|
+
msg =self.name + ".order -> A necessary field is missing: #{necessary}: --> #{requirements[necessary]}"
|
59
|
+
error msg, :args, nil
|
60
|
+
end
|
61
|
+
if alternative_parameters.present?
|
62
|
+
unless ( alternative_parameters.keys & the_arguments.keys ).size == 1
|
63
|
+
msg =self.name + ".order -> One of the alternative fields needs to be specified: \n\t:" +
|
64
|
+
"#{alternative_parameters.map{|x| x.join ' => '}.join(" or \n\t:")}"
|
65
|
+
error msg, :args, nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# initialise order with given attributes
|
70
|
+
IB::Order.new the_arguments
|
71
|
+
end
|
72
|
+
|
73
|
+
def alternative_parameters
|
74
|
+
{}
|
75
|
+
end
|
76
|
+
def requirements
|
77
|
+
{ action: IB::VALUES[:side], total_quantity: 'also aliased as :size' }
|
78
|
+
end
|
79
|
+
|
80
|
+
def defaults
|
81
|
+
{ tif: :good_till_cancelled }
|
82
|
+
end
|
83
|
+
|
84
|
+
def optional
|
85
|
+
{ account: 'Account(number) to trade on' }
|
86
|
+
end
|
87
|
+
|
88
|
+
def aliases
|
89
|
+
{ total_quantity: :size }
|
90
|
+
end
|
91
|
+
|
92
|
+
def parameters
|
93
|
+
the_output = ->(var){ var.map{|x| x.join(" --> ") }.join("\n\t: ")}
|
94
|
+
|
95
|
+
"Required : " + the_output[requirements] + "\n --------------- \n" +
|
96
|
+
"Optional : " + the_output[optional] + "\n --------------- \n"
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
require 'ib/order_prototypes/forex'
|
104
|
+
require 'ib/order_prototypes/market'
|
105
|
+
require 'ib/order_prototypes/limit'
|
106
|
+
require 'ib/order_prototypes/stop'
|
107
|
+
require 'ib/order_prototypes/volatility'
|
108
|
+
require 'ib/order_prototypes/premarket'
|
109
|
+
require 'ib/order_prototypes/pegged'
|
110
|
+
require 'ib/order_prototypes/combo'
|