ib-extensions 1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|