From 0f4aabe04d4a957aee78a6b2d10a380d7249fffe Mon Sep 17 00:00:00 2001 From: Hartmut Bischoff Date: Fri, 16 Oct 2020 09:45:33 +0000 Subject: [PATCH 1/3] removed calls to order.place and order.modigy from Connection-class added integration specs --- lib/ib/connection.rb | 18 +- spec/integration/account_info_spec.rb | 37 ++++ spec/integration/fundamental_data_spec.rb | 202 ++++++++++++++++++++++ spec/integration/placement_spec.rb | 171 ++++++++++++++++++ spec/order_helper.rb | 21 ++- 5 files changed, 436 insertions(+), 13 deletions(-) create mode 100644 spec/integration/account_info_spec.rb create mode 100644 spec/integration/fundamental_data_spec.rb create mode 100644 spec/integration/placement_spec.rb diff --git a/lib/ib/connection.rb b/lib/ib/connection.rb index ff7ab8d..5b22428 100644 --- a/lib/ib/connection.rb +++ b/lib/ib/connection.rb @@ -339,12 +339,26 @@ def send_message what, *args # Place Order (convenience wrapper for send_message :PlaceOrder). # Assigns client_id and order_id fields to placed order. Returns assigned order_id. def place_order order, contract - order.place contract, self + # order.place contract, self ## old + error "Unable to place order, next_local_id not known" unless next_local_id + error "local_id present. Order is already placed. Do might use modify insteed" unless order.local_id.nil? + order.client_id = client_id + order.local_id = next_local_id + self.next_local_id += 1 + order.placed_at = Time.now + modify_order order, contract end # Modify Order (convenience wrapper for send_message :PlaceOrder). Returns order_id. def modify_order order, contract - order.modify contract, self + # order.modify contract, self ## old + error "Unable to modify order; local_id not specified" if order.local_id.nil? + order.modified_at = Time.now + send_message :PlaceOrder, + :order => order, + :contract => contract, + :local_id => order.local_id + order.local_id # return value end # Cancel Orders by their local ids (convenience wrapper for send_message :CancelOrder). diff --git a/spec/integration/account_info_spec.rb b/spec/integration/account_info_spec.rb new file mode 100644 index 0000000..212db84 --- /dev/null +++ b/spec/integration/account_info_spec.rb @@ -0,0 +1,37 @@ +require 'integration_helper' + +describe "Request Account Data", :connected => true, :integration => true do + + before(:all){ establish_connection } + + after(:all) { close_connection } + + context "with subscribe option set" do + before(:all) do + ib = IB::Connection.current + ib.send_message :RequestAccountData, subscribe: true , account_code: ACCOUNT + ib.wait_for :AccountDownloadEnd, 5 # sec + end + after(:all) do + IB::Connection.current.send_message :RequestAccountData, subscribe: false , account_code: ACCOUNT + clean_connection + end + + it_behaves_like 'Valid account data request' + end + + context "without subscribe option" do + before(:all) do + ib = IB::Connection.current + ib.send_message :RequestAccountData, account_code: ACCOUNT + ib.wait_for :AccountDownloadEnd, 5 # sec + end + + after(:all) do + IB::Connection.current.send_message :RequestAccountData, subscribe: false , account_code: ACCOUNT + clean_connection + end + + it_behaves_like 'Valid account data request' + end +end # Request Account Data diff --git a/spec/integration/fundamental_data_spec.rb b/spec/integration/fundamental_data_spec.rb new file mode 100644 index 0000000..33c67b3 --- /dev/null +++ b/spec/integration/fundamental_data_spec.rb @@ -0,0 +1,202 @@ +require 'integration_helper' + +describe 'Request Fundamental Data', + :connected => true, :integration => true, :reuters => true do + + before(:all) do + establish_connection + @ib = IB::Connection.current + + @contract = IB::Contract.new :symbol => 'IBM', + :exchange => 'NYSE', + :currency => 'USD', + :sec_type => 'STK' + + @ib.send_message :RequestFundamentalData, + :id => 456, + :contract => @contract, + :report_type => 'snapshot' # 'estimates', 'finstat' + + @ib.wait_for :FundamentalData, 10 # sec + end + + after(:all) do + close_connection + end + + subject { @ib.received[:FundamentalData].first } + + it { expect( @ib.received[:FundamentalData]).to have_at_least(1).data_message } + + it { is_expected.to be_an IB::Messages::Incoming::FundamentalData } + its(:request_id) { is_expected.to eq 456 } + its(:xml) { is_expected.to be_a Hash } + + it 'responds with XML with relevant data' do + require 'ox' + data_xml = subject.xml[:ReportSnapshot] + name = data_xml[:CoIDs][:CoID].at(1) + expect(name).to match 'International Business Machines' + end +end + +##pp data_xml +# +#{:ReportSnapshot=> +# {:CoIDs=> +# {:CoID=> +# ["4741N", +# "International Business Machines Corp.", +# "130871985", +# "0000051143"]}, +# :Issues=> +# {:Issue=> +# [{:IssueID=> +# ["Ordinary Shares", "IBM", "IBM", "IBM.N", "261483", "1090370"], +# :Exchange=>"New York Stock Exchange", +# :MostRecentSplit=>"2.0"}, +# {:IssueID=> +# ["Preference Shares Series A", +# "IBMPP", +# "IBMPP.PK^C06", +# "1883112", +# "25545447"], +# :Exchange=>"Over The Counter"}]}, +# :CoGeneralInfo=> +# {:CoStatus=>"Active", +# :CoType=>"Equity Issue", +# :LastModified=>"2020-09-15", +# :LatestAvailableAnnual=>"2019-12-31", +# :LatestAvailableInterim=>"2020-06-30", +# :Employees=>"352600", +# :SharesOut=>"890578748.0", +# :CommonShareholders=>"380707", +# :ReportingCurrency=>"U.S. Dollars", +# :MostRecentExchange=>"1.0"}, +# :TextInfo=> +# {:Text=> +# ["International Business Machines Corporation (IBM) is a technology company. The Company operates through five segments: Cognitive Solutions, Global Business Services (GBS), Technology Services & Cloud Platforms, Systems and Global Financing. The Cognitive Solutions segment delivers a spectrum of capabilities, from descriptive, predictive and prescriptive analytics to cognitive systems. Cognitive Solutions includes Watson, a cognitive computing platform that has the ability to interact in natural language, process big data, and learn from interactions with people and computers. The GBS segment provides clients with consulting, application management services and global process services. The Technology Services & Cloud Platforms segment provides information technology infrastructure services. The Systems segment provides clients with infrastructure technologies. The Global Financing segment includes client financing, commercial financing, and remanufacturing and remarketing.", +# "BRIEF: For the six months ended 30 June 2020, International Business Machines Corp. revenues decreased 4% to $35.69B. Net income applicable to common stockholders excluding extraordinary items decreased 37% to $2.69B. Revenues reflect Global Technology Services segment decrease of 7% to $12.78B, Other segment decrease of 87% to $112M, Americas segment decrease of 4% to $16.62B, Europe/Middle East/Africa segment decrease of 6% to $11.21B."]}, +# :contactInfo=> +# {:streetAddress=>["1 New Orchard Rd", nil, nil], +# :city=>"ARMONK", +# :"state-region"=>"NY", +# :postalCode=>"10504-1722", +# :country=>"United States", +# :contactName=>nil, +# :contactTitle=>nil, +# :phone=> +# {:phone=> +# {:countryPhoneCode=>"1", +# :"city-areacode"=>"914", +# :number=>"4991900"}}}, +# :webLinks=>{:webSite=>"https://www.ibm.com/", :eMail=>nil}, +# :peerInfo=> +# {:IndustryInfo=> +# {:Industry=> +# ["IT Services & Consulting - NEC", +# "Computer Systems Design Services", +# "Software Publishers", +# "Custom Computer Programming Services", +# "Electronic Computer Manufacturing", +# "Semiconductor and Related Device Manufacturing", +# "Other Computer Peripheral Equipment Manufacturing", +# "Sales Financing", +# "Office Machinery and Equipment Rental and Leasing", +# "Data Processing Services", +# "Computer Integrated System Design", +# "Prepackaged Software", +# "Computer Programming Services", +# "Electronic Computers", +# "Semiconductors/related Devices", +# "Computer Periph'L Equipment, Nec", +# "Misc Business Credit Institutions", +# "Equipment Rental & Leasing, Nec", +# "Data Processing And Preparation"]}, +# :Indexconstituet=>["S&P 500", "Dow Industry"]}, +# :officers=> +# {:officer=> +# [{:firstName=>"Virginia", +# :mI=>"M.", +# :lastName=>"Rometty", +# :age=>"62 ", +# :title=>"Executive Chairman of the Board"}, +# {:firstName=>"James", +# :mI=>"M.", +# :lastName=>"Whitehurst", +# :age=>"52 ", +# :title=>"President"}, +# {:firstName=>"Arvind", +# :mI=>nil, +# :lastName=>"Krishna", +# :age=>"57 ", +# :title=>"Chief Executive Officer, Director"}, +# {:firstName=>"James", +# :mI=>"J.", +# :lastName=>"Kavanaugh", +# :age=>"53 ", +# :title=> +# "Senior Vice President, Chief Financial Officer, Finance and operation"}, +# {:firstName=>"Diane", +# :mI=>"J.", +# :lastName=>"Gherson", +# :age=>"62 ", +# :title=>"Chief Human Resource Officer, Senior Vice President"}, +# {:firstName=>"John", +# :mI=>"E.", +# :lastName=>"Kelly", +# :age=>"66 ", +# :title=>"Executive Vice President"}, +# {:firstName=>"Michelle", +# :mI=>"H.", +# :lastName=>"Browdy", +# :age=>"55 ", +# :title=> +# "Senior Vice President - Legal and Regulatory Affairs and General Counsel"}, +# {:firstName=>"Kenneth", +# :mI=>"M.", +# :lastName=>"Keverian", +# :age=>"63 ", +# :title=>"Senior Vice President - Corporate Strategy"}, +# {:firstName=>"Robert", +# :mI=>"F.", +# :lastName=>"Del Bene", +# :age=>"60 ", +# :title=>"Vice President, Controller"}]}, +# :Ratios=> +# {:Group=> +# [{:Ratio=> +# ["125.94000", +# "158.75000", +# "90.56000", +# "2020-10-14T00:00:00", +# "6.32897", +# "162793.50000"]}, +# {:Ratio=>["112159.50000", "75499.00000", "17859.00000", "8020.00000"]}, +# {:Ratio=> +# ["8.97154", +# "84.43922", +# "23.07601", +# "15.83689", +# "16.38814", +# "6.49000"]}, +# {:Ratio=> +# ["47.75957", +# "41.99393", +# "1.48558", +# "14.03772", +# "5.45762", +# "352600"]}]}, +# :ForecastData=> +# {:Ratio=> +# [{:Value=>"2.6111"}, +# {:Value=>"135.31250"}, +# {:Value=>"2.5700"}, +# {:Value=>"11.37865"}, +# {:Value=>"74022.22230"}, +# {:Value=>"17542.17620"}, +# {:Value=>"11.06810"}, +# {:Value=>"2.58030"}, +# {:Value=>"9856.58830"}, +# {:Value=>"6.49000"}]}}} +# diff --git a/spec/integration/placement_spec.rb b/spec/integration/placement_spec.rb new file mode 100644 index 0000000..24264fa --- /dev/null +++ b/spec/integration/placement_spec.rb @@ -0,0 +1,171 @@ +require 'order_helper' + +describe 'Order placement' do # :connected => true, :integration => true do + let(:contract_type) { :stock } + + before(:all) { establish_connection } + + after(:all) do + remove_open_orders + close_connection + end + + context 'Placing wrong order', :slow => true do + + before(:all) do + ib = IB::Connection.current + ib.wait_for :NextValidId + @initial_local_id = ib.next_local_id + ib.clear_received # just in case ... + place_the_order do | price | + IB::Order.new order_type: 'LMT', total_quantity: 100, action: :buy, + account: ACCOUNT, + :limit_price => price*2.001 # non-acceptable price + end + end + + context IB::Connection do + + subject { IB::Connection.current } + + it 'does not place new Order' do + expect( subject.received[:OpenOrder] ).to be_empty + expect( subject.received[:OrderStatus] ).to be_empty + end + + it 'still changes client`s next_local_id' do + expect( subject.current.next_local_id ).to eq @initial_local_id +1 + end + + it_has_message "Alert message" , /The price does not conform to the minimum price variation for this contract/ + + + end # context IB::Connection + + end # Placing wrong order + + context 'What-if order' , focus: true do + before(:all) do + ib = IB::Connection.current + ib.clear_received + @initial_local_id = ib.next_local_id + @local_id = place_the_order do | market_price | + IB::Order.new order_type: 'LMT', total_quantity: 100, action: :buy, + :limit_price => market_price - 1, # Set acceptable price + :what_if => true, # Hypothetical + account: ACCOUNT + end + end + + it 'changes client`s next_local_id' do + expect( IB::Connection.current.next_local_id ).to eq @initial_local_id +1 + end + + it { expect( IB::Connection.current.received[:OpenOrder]).to have_at_least(1).open_order_message } + it { expect( IB::Connection.current.received[:OrderStatus]).to have_exactly(0).status_messages } + context IB::Order do + subject { IB::Connection.current.received[:OpenOrder].last.order } + it_behaves_like 'Placed Order' + it_behaves_like 'Presubmitted what-if Order' + end + + context "finalize" do + it 'is not actually being placed though' do + ib = IB::Connection.current + ib.clear_received + ib.send_message :RequestOpenOrders + ib.wait_for :OpenOrderEnd + expect( ib.received[:OpenOrder] ).to have_exactly(0).order_message + end + end + end + + context 'Off-market limit' do + before(:all) do + ib = IB::Connection.current + @initial_local_id = ib.next_local_id + place_the_order do | market_price | + IB::Order.new order_type: 'LMT', total_quantity: 100, action: :buy, + limit_price: market_price-1, # Acceptable price + account: ACCOUNT + end + end + + context IB::Order do + subject { IB::Connection.current.received[:OpenOrder].last.order } + it_behaves_like 'Placed Order' + end + + context "Cancelling wrong order" do + before(:all) do + ib = IB::Connection.current + ib.clear_received + @initial_local_id = ib.next_local_id + ib.cancel_order rand(99999999) + + ib.wait_for :Alert + end + + it { puts IB::Connection.current.received[:Alert].to_human } + it { expect( IB::Connection.current.received[:Alert]).to have_at_least(1).alert_message } + + it 'does not increase client`s next_local_id further' do + expect( IB::Connection.current.next_local_id ).to eq @initial_local_id + end + + it 'does not receive Order messages' do + ib = IB::Connection.current + puts ib.received[:OrderStatus].to_human + expect( ib.received?(:OrderStatus)).to be_falsy + expect( ib.received?(:OpenOrder)).to be_falsy + end + it_has_message "Alert message" , /OrderId \d* that needs to be cancelled is not found/ + + end + end # Off-market limit + + context 'order with conditions' do + before(:all) do + ib = IB::Connection.current + + @initial_local_id = ib.next_local_id + @local_id = place_the_order do | market_price | + + condition1 = IB::MarginCondition.new percent: 45, operator: '<=' + condition2 = IB::PriceCondition.fabricate IB::Symbols::Futures.es, "<=", 2600 + + IB::Limit.order action: :buy, size: 100, + conditions: [condition1, condition2], + conditions_cancel_order: true , + :limit_price => market_price - 1, # Set acceptable price + account: ACCOUNT + end + end + + it 'changes client`s next_local_id' do + expect( IB::Connection.current.next_local_id ).to eq @initial_local_id +1 + end + + it { expect( IB::Connection.current.received[:OpenOrder]).to have_at_least(1).open_order_message } +# it "display order_stauts" do +# puts IB::Connection.current.received[:OrderStatus].inspect +# end + it { expect( IB::Connection.current.received[:OrderStatus]).to have_exactly(1).status_messages } + context IB::Order do + subject { IB::Connection.current.received[:OpenOrder].last.order } + it_behaves_like 'Placed Order' + + it "contains proper conditions" do + expect( subject.conditions ).to be_an Array + expect( subject.conditions ).to have(2).conditions + expect( subject.conditions.first ).to be_an IB::MarginCondition + expect( subject.conditions.last ).to be_an IB::PriceCondition + expect( subject.conditions_cancel_order ).to be_truthy + end + end + + end + + + context '' +end # Orders diff --git a/spec/order_helper.rb b/spec/order_helper.rb index 2e4d044..c488346 100644 --- a/spec/order_helper.rb +++ b/spec/order_helper.rb @@ -9,7 +9,7 @@ if the order-object provides a local_id, the order is modified. =end -def place_the_order( contract: IB::Symbols::Stocks.wfc ) +def place_the_order( contract: SAMPLE ) ib = IB::Connection.current raise 'Unable to place order, no connection' unless ib && ib.connected? order = yield( get_contract_price( contract: contract) ) @@ -23,7 +23,7 @@ def place_the_order( contract: IB::Symbols::Stocks.wfc ) the_order_id # return value end -def get_contract_price contract: IB::Symbols::Stocks.wfc +def get_contract_price contract: SAMPLE ib = IB::Connection.current ib.send_message :RequestMarketDataType, :market_data_type => :delayed the_id = ib.send_message :RequestMarketData, contract: contract @@ -101,10 +101,10 @@ def remove_open_orders expect( IB::VALUES[:tif].values ).to include subject.tif end its( :clearing_intent ){is_expected.to eq :ib } - it "mysterious trailing stop price is absent", :pending => true do - pending "seems to be irrelevant, but needs clarification" - expect( subject.trail_stop_price ).to be_nil.or be_zero - end +# it "mysterious trailing stop price is absent", :pending => true do +# pending "seems to be irrelevant, but needs clarification" +# expect( subject.trail_stop_price ).to be_nil.or be_zero +# end # end end @@ -113,17 +113,16 @@ def remove_open_orders if used_contract.is_a? IB::Bag ## Combos dont have fixed commissions its( :commission ){ is_expected.to be_nil.or be_zero } else - its( :commission ){ is_expected.to be_a( BigDecimal ).and be > 0 } + its( :commission ){ is_expected.to be_nil.or be_zero } + its( :min_commission ){ is_expected.to be_a( BigDecimal ).and be > 0 } + its( :max_commission ){ is_expected.to be_a( BigDecimal ).and be > 0 } + its( :commission_currency ){ is_expected.to be_a( String )} end its( :what_if ){ is_expected.to be_truthy } its( :equity_with_loan ){ is_expected.to be_a( BigDecimal ).and be > 0 } its( :init_margin ){ is_expected.to be_a( BigDecimal ).and be > 0 } its( :maint_margin ){ is_expected.to be_a( BigDecimal ).and be > 0 } - it "mysterious trailing stop price is absent", pending: true do - pending "seems to be irrelevant, but needs clarification" - expect( subject.trail_stop_price ).to be_nil.or be_zero - end end From 65ca96f425ceefda80638866230ca353d5841f74 Mon Sep 17 00:00:00 2001 From: Hartmut Bischoff Date: Sun, 18 Oct 2020 18:58:55 +0000 Subject: [PATCH 2/3] Cleaning up --- lib/ib/model.rb | 16 ---- lib/models/ib/butterfly.rb | 93 --------------------- lib/models/ib/calendar.rb | 107 ------------------------ lib/models/ib/spread.rb | 150 ---------------------------------- lib/models/ib/stock_spread.rb | 45 ---------- lib/models/ib/straddle.rb | 78 ------------------ lib/models/ib/strangle.rb | 49 ----------- lib/order-prototypes.rb | 43 ---------- lib/spread-prototypes.rb | 60 -------------- 9 files changed, 641 deletions(-) delete mode 100644 lib/models/ib/butterfly.rb delete mode 100644 lib/models/ib/calendar.rb delete mode 100644 lib/models/ib/spread.rb delete mode 100644 lib/models/ib/stock_spread.rb delete mode 100644 lib/models/ib/straddle.rb delete mode 100644 lib/models/ib/strangle.rb delete mode 100644 lib/order-prototypes.rb delete mode 100644 lib/spread-prototypes.rb diff --git a/lib/ib/model.rb b/lib/ib/model.rb index 5d2fc62..7c522e4 100644 --- a/lib/ib/model.rb +++ b/lib/ib/model.rb @@ -1,20 +1,4 @@ -## Connection to Orientdb is established if the oriendb-client is -# present upon require if ib-ruby -# If ActiveOrient is not connected (ActiveOrient::Init.connect has not been called) # lightweigth tables are used require 'ib/base_properties' -#require 'active-orient' -#if ActiveOrient::Model.orientdb.nil? require 'ib/base' IB::Model = IB::Base -#else -# require 'ib/orientdb' -# IB::Model = V #ActiveOrient::Base -# IB::DB.connect -# puts " IB-Ruby is run in OrientDB-Mode" -#end -#module IB - # IB Models can be either lightweight (tableless) or database-backed. - # require 'ib/db' - to make all IB models database-backed -# Model = IB.db_backed? ? ActiveRecord::Base : IB::Base -#end diff --git a/lib/models/ib/butterfly.rb b/lib/models/ib/butterfly.rb deleted file mode 100644 index dfdcfad..0000000 --- a/lib/models/ib/butterfly.rb +++ /dev/null @@ -1,93 +0,0 @@ -require_relative 'contract' -module IB - class Butterfly < Spread - -=begin -Macro-Class to simplify the definition of Butterflies - -Initialize with - - butterfly = IB::Butterfly.new IB::Option.new( symbol: :estx50, strike: 3000, expiry:'201901'), front: 2850, back: 3150 - -or - - straddle = IB::Butterfly.new underlying: Symbols::Index.stoxx, - strike: 2000, - expiry: '201901', front: 2850, back: 3150 - - where :strike defines the center of the Spread. - -=end - - - - def initialize master=nil, - underlying: nil, - strike: 0, - expiry: IB::Symbols::Futures.next_expiry, - right: :put, - front: 0, back: 0, - **args # trading-class, multiplier and others - - master_option, msg = if master.present? - if master.is_a?(IB::Option) - [ master.essential, nil ] - else - [ nil, "First Argument is no IB::Option" ] - end - elsif underlying.present? - if underlying.is_a?(IB::Contract) - master = IB::Option.new underlying.attributes - .slice( :currency, :symbol, :exchange, :strike, :right, :expiry ) - .merge(args) - master.sec_type = 'FOP' if underlying.is_a?(IB::Future) - master.strike, master.expiry, master.right = strike , expiry, right unless underlying.is_a? IB::Option - [master, master.strike.zero? ? "strike has to be specified" : nil] - else - [nil, "Underlying has to be an IB::Contract"] - end - else - [ nil, "Required parameters: Master-Option or Underlying, strike, expiry, front, back" ] - end - - error msg, :args, nil if msg.present? - master_option.trading_class = args[:trading_class] if args[:trading_class].present? - l=[] ; master_option.verify{|x| x.contract_detail= nil; l << x } - if l.empty? - error "Invalid Parameters. No Contract found #{master.to_human}" - elsif l.size > 1 - Connection.logger.error "ambigous contract-specification: #{l.map(&:to_human).join(';')}" - available_trading_classes = l.map( &:trading_class ).uniq - if available_trading_classes.size >1 - error "Refine Specification with trading_class: #{available_trading_classes.join('; ')} " - else - error "Respecify expiry, verification reveals #{l.size} contracts (only 1 is allowed)" - end - end - - master_option.exchange ||= l.first.exchange - master_option.currency ||= l.first.currency - - l = [] ; c_l = [] - strikes = [front, master.strike, back] - strikes.zip([1, -2, 1]).each do |strike, weight| - master_option.strike = strike - master_option.verify do |c| - l << c.essential - c_l << ComboLeg.new( con_id: c.con_id, weight: weight, exchange: c.exchange ) - end - end - - super exchange: master_option.exchange, - symbol: master_option.symbol.to_s , - currency: master_option.currency, - legs: l, - combo_legs: c_l - end - - def to_human - x= [ combo_legs.map(&:weight) , legs.map( &:strike )].transpose - "" - end - end -end diff --git a/lib/models/ib/calendar.rb b/lib/models/ib/calendar.rb deleted file mode 100644 index 0761110..0000000 --- a/lib/models/ib/calendar.rb +++ /dev/null @@ -1,107 +0,0 @@ -require_relative 'contract' - - -module IB - class Calendar < Spread - -=begin -Macro-Class to simplify the definition of Calendar-Spreads - -Initialize with - - calendar = IB::Calendar.new underlying: Symbols::Index.stoxx, - strike: 3000, right: :put - front: 201901, back: 291903 - or - - master = IB::Option.new symbol: :Estx50, right: :put, multiplier: 10, exchange: 'DTB', currency: 'EUR' - strike: 3000, expiry: 201812 - calendar = IB::Calendar.new master, back: 201903 - - - In »master-mode« futures may be underlyings, too, ie - - calendar = IB::Calendar.new IB::Symbols::Futures.zn, back: '3m' - - calendar.to_human - - "" - - or - calendar = IB::Calendar.new IB::Symbols::Futures.zn, front: 201903, back: '3m' -=end - def initialize master=nil, # provides strike, front-month, right, trading-class - underlying: nil, - strike: 0, - right: :put, - front: nil, # has to be specified as "YYYMM(DD)" String or Numeric - back: , # has to be specified either as "YYYYMM(DD)" String or Numeric - # or relative "1m" "3m" "2w" "-1w" - **args # trading_class and others - - - the_master, msg = if master.present? - if master.is_a?(IB::Option) - front = master.expiry unless master.expiry.nil? - [ master.essential, nil ] - elsif master.is_a? IB::Future - front ||= master.expiry - [ master.essential, nil ] - else - [ nil, "First Argument is no IB::Option" ] - end - elsif underlying.present? - if underlying.is_a?(IB::Contract) - master = IB::Option.new underlying.attributes.slice( :currency, :symbol, :exchange ).merge(args) - master.sec_type = 'FOP' if underlying.is_a?(IB::Future) - master.strike, master.right, master.expiry = strike, right, front - [master, strike.zero? ? "strike has to be specified" : nil] - else - [nil, "Underlying has to be an IB::Contract"] - end - else - [ nil, "Required parameters: Master-Option or Underlying, strike" ] - end - - error msg, :args, nil if msg.present? - the_master.trading_class = args[:trading_class] if args[:trading_class].present? - the_master.expiry = front if front.present? - the_master.expiry = IB::Symbols::Futures.next_expiry if master_option.expiry.blank? - - #if the_master.is_a?(IB::Option) && ( master_option.expiry.nil? || master_option.expiry == '') - l=[] ; the_master.verify{|x| x.contract_detail = nil; l << x } - if l.empty? - error "Invalid Parameters. No Contract found #{the_master.to_human}" - elsif l.size > 2 - available_trading_classes = l.map( &:trading_class ).uniq - Connection.logger.error "ambigous contract-specification: #{l.map(&:to_human).join(';')}" - if available_trading_classes.size >1 - error "Refine Specification with trading_class: #{available_trading_classes.join('; ')} (details in log)" - else - error "Respecify expiry, verification reveals #{l.size} contracts (only 2 are allowed) #{the_master.to_human}" - end - end - - the_master.expiry = transform_distance(front, back) - the_master.verify{|x| x.contract_detail = nil; l << x } - error "Two legs are required, \n Verifiying the master-option exposed #{l.size} legs" unless l.size ==2 - - the_master.exchange ||= l.first.exchange - the_master.currency ||= l.first.currency - - # default is to sell the front month - c_l = l.map.with_index{ |l,i| ComboLeg.new con_id: l.con_id, action: i.zero? ? :sell : :buy, exchange: l.exchange, ratio: 1 } - - super exchange: the_master.exchange, - symbol: the_master.symbol.to_s, - currency: the_master.currency, - legs: l, - combo_legs: c_l - end - def to_human - x= [ combo_legs.map(&:weight) , legs.map( &:last_trading_day )].transpose - "" - end - - end # class -end # module diff --git a/lib/models/ib/spread.rb b/lib/models/ib/spread.rb deleted file mode 100644 index 02c693b..0000000 --- a/lib/models/ib/spread.rb +++ /dev/null @@ -1,150 +0,0 @@ -module IB - class Spread < Bag - has_many :legs - - using IBSupport - -=begin -Parameters: front: YYYMM(DD) - back: {n}w, {n}d or YYYYMM(DD) - -Adds (or substracts) relative (back) measures to the front month, just passes absolute YYYYMM(DD) value - - front: 201809 back: 2m (-1m) --> 201811 (201808) - front: 20180908 back: 1w (-1w) --> 20180918 (20180902) -=end - - def transform_distance front, back - # Check Format of back: 201809 --> > 200.000 - # 20180989 ---> 20.000.000 - start_date = front.to_i < 20000000 ? Date.strptime(front.to_s,"%Y%m") : Date.strptime(front.to_s,"%Y%m%d") - nb = if back.to_i > 200000 - back.to_i - elsif back[-1] == "w" && front.to_i > 20000000 - start_date + (back.to_i * 7) - elsif back[-1] == "m" && front.to_i > 200000 - start_date >> back.to_i - else - error "Wrong date #{back} required format YYYMM, YYYYMMDD ord {n}w or {n}m" - end - if nb.is_a?(Date) - if back[-1]=='w' - nb.strftime("%Y%m%d") - else - nb.strftime("%Y%m") - end - else - nb - end - end # def - - def to_human - self.description - end - - def calculate_spread_value( array_of_portfolio_values ) - array_of_portfolio_values.map{|x| x.send yield }.sum if block_given? - end - - def fake_portfolio_position( array_of_portfolio_values ) - calculate_spread_value= ->( a_o_p_v, attribute ) do - a_o_p_v.map{|x| x.send attribute }.sum - end - ar=array_of_portfolio_values - IB::PortfolioValue.new contract: self, - average_cost: calculate_spread_value[ar, :average_cost], - market_price: calculate_spread_value[ar, :market_price], - market_value: calculate_spread_value[ar, :market_value], - unrealized_pnl: calculate_spread_value[ar, :unrealized_pnl], - realized_pnl: calculate_spread_value[ar, :realized_pnl], - position: 0 - - end - - - - def serialize_rabbit - { "Spread" => serialize( :option, :trading_class ), - 'legs' => legs.map{ |y| y.serialize :option, :trading_class }, 'combo_legs' => combo_legs.map(&:serialize), - 'misc' => [description] - } - end - - # adds a leg to any spread - # - # Parameter: - # contract: Will be verified. Contract.essential is added to legs-array - # action: :buy or :sell - # weight: - # ratio: - # - # Default: action: :buy, weight: 1 - - def add_leg contract, **leg_params - evaluated_contracts = [] - nr = contract.verify do |c| - self.combo_legs << ComboLeg.new( c.attributes - .slice( :con_id, :exchange ) - .merge( action: :buy ) - .merge( leg_params ) - ) - self.description = description + " added #{c.to_human}" rescue "Spread: #{c.to_human}" - self.legs << c.essential - evaluated_contracts << c.essential - end - error "ambiguous contract-specification\n #{evaluated_contracts.map{|c| [c.to_human, c.trading_class].join(" / ")}.join("\n ")}" if nr > 1 - self # return value to enable chaining - - - end - - # removes the contract from the spread definition - # - def remove_leg contract - contract.verify do |c| - legs.delete_if { |x| x.con_id == c.con_id } - combo_legs.delete_if { |x| x.con_id == c.con_id } - self.description = description + " removed #{c.to_human}" - end - self - end - - - def essential - legs.each{ |x| x.contract_detail = nil } - self - end - def multiplier - (legs.map(&:multiplier).sum/legs.size).to_i - end - - # provide a negative con_id - def con_id - -legs.map(&:con_id).sum - end - -# optional: specify default order prarmeters for all spreads -# def order_requirements -# super.merge symbol: symbol -# end - - - def self.build_from_json container - read_leg = ->(a) do - IB::ComboLeg.new :con_id => a.read_int, - :ratio => a.read_int, - :action => a.read_string, - :exchange => a.read_string - - end - object= self.new container['Spread'].read_contract - object.legs = container['legs'].map{|x| IB::Contract.build x.read_contract} - object.combo_legs = container['combo_legs'].map{ |x| read_leg[ x ] } - object.description = container['misc'].read_string - object - - end - end - - -end diff --git a/lib/models/ib/stock_spread.rb b/lib/models/ib/stock_spread.rb deleted file mode 100644 index 34a535b..0000000 --- a/lib/models/ib/stock_spread.rb +++ /dev/null @@ -1,45 +0,0 @@ -require_relative 'contract' -module IB - class StockSpread < Spread - -=begin -Macro-Class to simplify the definition of Stock-Spreads, ie. buy some equity and sell another at the same time - -Initialize with - - spread= IB::StockSpread.new IB::Stock.new( symbol: 'T' ), IB::Stock.new( symbol: 'GE' ), ratio:[ 1,-2 ] - -or - -=end - - - - def initialize *underlying, ratio: [1,-1] - - are_stocks = ->{ underlying.all?{|y| y.is_a? IB::Stock} } - error "only spreads with two underyings of type »IB::Stock« are supported" unless underlying.size==2 && are_stocks[] - verified_legs = underlying.map{|c| c.verify!.essential} # ensure, that con_id is present - - c_l = verified_legs.zip(ratio).map do |l,r| - action = r >0 ? :buy : :sell - ComboLeg.new con_id: l.con_id, action: action, exchange: l.exchange, ratio: r.abs - end - super exchange: verified_legs.first.exchange, - currency: verified_legs.first.currency, -# symbol: verified_legs.map( &:symbol ).zip(ratio).sort{|x,y| x.last <=> y.last}.map{|c,_| c }.join(","), # alphabetical order - symbol: verified_legs.map( &:symbol ).sort.join(","), # alphabetical order - legs: verified_legs, - combo_legs: c_l - end - - def to_human - info= legs.map( &:symbol ).zip(combo_legs.map( &:weight )) - "" - end - # always route a order as NonGuaranteed - def order_requirements - super.merge combo_params: [ ['NonGuaranteed', true] ] - end - end -end diff --git a/lib/models/ib/straddle.rb b/lib/models/ib/straddle.rb deleted file mode 100644 index a01d4a6..0000000 --- a/lib/models/ib/straddle.rb +++ /dev/null @@ -1,78 +0,0 @@ -require_relative 'contract' -module IB - class Straddle < Spread - -=begin -Macro-Class to simplify the definition of Straddles - -Initialize with - - straddle= IB::Straddle.new IB::Option.new( symbol: :estx50, strike: 3000, expiry:'201901') - -or - - straddle = IB::Straddle.new underlying: Symbols::Index.stoxx, - strike: 2000, - expiry: '201901' - - possible underlyings are IB::Stock, IB::Future and IB::Index -=end - - - - def initialize master=nil, - underlying: nil, - strike: 0, - expiry: IB::Symbols::Futures.next_expiry, - **args # trading-class and others - - master_option, msg = if master.present? - if master.is_a?(IB::Option) - [ master.essential, nil ] - else - [ nil, "First Argument is no IB::Option" ] - end - elsif underlying.present? - if underlying.is_a?(IB::Contract) - master = IB::Option.new underlying.attributes.slice( :currency, :symbol, :exchange ).merge(args) - master.sec_type = 'FOP' if underlying.is_a?(IB::Future) - master.strike, master.expiry = strike , expiry - [master, strike.zero? ? "strike has to be specified" : nil] - else - [nil, "Underlying has to be an IB::Contract"] - end - else - [ nil, "Required parameters: Master-Option or Underlying, strike, expiry" ] - end - - error msg, :args, nil if msg.present? - master_option.trading_class = args[:trading_class] if args[:trading_class].present? - master_option.right = nil; master_option.con_id = 0; - l=[] ; master_option.verify{|x| x.contract_detail= nil; l << x } - if l.size < 2 - error "Invalid Parameters. Two legs are required, \n Verifiying the master-option exposed #{l.size} legs", :args, nil - elsif l.size > 2 - Connection.logger.error "ambigous contract-specification: #{l.map(&:to_human).join(';')}" - available_trading_classes = l.map( &:trading_class ).uniq - if available_trading_classes.size >1 - error "Refine Specification with trading_class: #{available_trading_classes.join('; ')} " - else - error "Respecify expiry, verification reveals #{l.size} contracts (only 2 are allowed)" - end - end - - master_option.exchange ||= l.first.exchange - master_option.currency ||= l.first.currency - - c_l = l.map{ |l| ComboLeg.new con_id: l.con_id, action: :buy, exchange: l.exchange, ratio: 1 } - super exchange: master_option.exchange, - symbol: master_option.symbol.to_s , - currency: master_option.currency, - legs: l, - combo_legs: c_l - end - def to_human - "" - end - end -end diff --git a/lib/models/ib/strangle.rb b/lib/models/ib/strangle.rb deleted file mode 100644 index fdc1161..0000000 --- a/lib/models/ib/strangle.rb +++ /dev/null @@ -1,49 +0,0 @@ -require_relative 'contract' - -module IB - class Strangle < Spread - -=begin -Macro-Class to simplify the definition of Strangles - -Initialize with - - strangle = IB::Strangle.new underlying: Symbols::Index.stoxx, - put: 3000, call: 3200 - expiry: '201901' - -=end - - - - def initialize underlying: , - p: , c: , - expiry: IB::Symbols::Futures.next_expiry, - **args # trading-class and others - - error "Underlying has to be an IB::Contract" unless underlying.is_a? IB::Contract - master_option = IB::Option.new underlying.attributes.slice( :currency, :symbol, :exchange ).merge(args) - master_option.expiry = expiry - master_option.sec_type = 'FOP' if underlying.is_a?(IB::Future) - - leg_option = ->(strike, kind) do - l=[]; master_option.strike = strike; master_option.verify{|c| c.contract_detail = nil; l <" - end - end -end diff --git a/lib/order-prototypes.rb b/lib/order-prototypes.rb deleted file mode 100644 index 85b9f03..0000000 --- a/lib/order-prototypes.rb +++ /dev/null @@ -1,43 +0,0 @@ -#The Module OrderPrototypes provides a wrapper to define even complex ordertypes. -# -#The Order is build by -# -# IB::.order -# -#A description is available through -# -# puts IB::.summary -# -#Nessesary and optional arguments are printed by -# -# puts IB::.parameters -# -#Orders can be setup interactively -# -# > d = Discretionary.order -# Traceback (most recent call last): (..) -# IB::ArgumentError (IB::Discretionary.order -> A necessary field is missing: -# action: --> {"B"=>:buy, "S"=>:sell, "T"=>:short, "X"=>:short_exempt}) -# > d = Discretionary.order action: :buy -# IB::ArgumentError (IB::Discretionary.order -> A necessary field is missing: -# total_quantity: --> also aliased as :size) -# > d = Discretionary.order action: :buy, size: 100 -# Traceback (most recent call last): -# IB::ArgumentError (IB::Discretionary.order -> A necessary field is missing: limit_price: --> decimal) -# -# -# -#Prototypes are defined as module. They extend OrderPrototype and establish singleton methods, which -#can adress and extend similar methods from OrderPrototype. -# -# - -require 'ib/order_prototypes/abstract' -require 'ib/order_prototypes/forex' -require 'ib/order_prototypes/market' -require 'ib/order_prototypes/limit' -require 'ib/order_prototypes/stop' -require 'ib/order_prototypes/volatility' -require 'ib/order_prototypes/premarket' -require 'ib/order_prototypes/pegged' -require 'ib/order_prototypes/combo' diff --git a/lib/spread-prototypes.rb b/lib/spread-prototypes.rb deleted file mode 100644 index 0a19c6f..0000000 --- a/lib/spread-prototypes.rb +++ /dev/null @@ -1,60 +0,0 @@ - -# These modules are used to facilitate referencing of most common Spreads - -# Spreads are created in two ways: -# -# (1) IB::Spread::{prototype}.build from: {underlying}, -# trading_class: (optional) -# {other specific attributes} -# -# (2) IB::Spread::{prototype}.fabcricate master: [one leg}, -# {other specific attributes} -# -# They return a freshly instantiated Spread-Object -# -module IB - module SpreadPrototype - - - def build from: , **fields - - end - - - def initialize_spread ref_contract = nil, **attributes - attributes = ref_contract.attributes.merge attributes if ref_contract.is_a?(IB::Contract) - the_spread = nil - IB::Contract.new(attributes).verify do| c| - the_spread= IB::Spread.new c.attributes.slice( :exchange, :symbol, :currency ) - end - error "Initializing of Spread failed – Underling is no Contract" if the_spread.nil? - yield the_spread if block_given? # yield outside mutex controlled verify-environment - the_spread # return_value - end - - def requirements - {} - end - - def defaults - {} - end - - def optional - { } - end - - def parameters - the_output = ->(var){ var.empty? ? "none" : var.map{|x| x.join(" --> ") }.join("\n\t: ")} - - "Required : " + the_output[requirements] + "\n --------------- \n" + - "Optional : " + the_output[optional] + "\n --------------- \n" - - end - end - -end -require 'ib/spread_prototypes/straddle' -require 'ib/spread_prototypes/strangle' -require 'ib/spread_prototypes/vertical' -require 'ib/spread_prototypes/calendar' From d80dd1714c6b63d211fc73c87e47885c4ad15823 Mon Sep 17 00:00:00 2001 From: Hartmut Bischoff Date: Mon, 19 Oct 2020 08:29:16 +0000 Subject: [PATCH 3/3] Introducing IB::Contract.merge --- lib/models/ib/contract.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/models/ib/contract.rb b/lib/models/ib/contract.rb index 997c8ec..deee5b6 100644 --- a/lib/models/ib/contract.rb +++ b/lib/models/ib/contract.rb @@ -215,6 +215,11 @@ def essential end + # creates a new Contract substituting attributes by the provied key-value pairs + def merge **new_attributes + self.class.new attributes.merge new_attributes + end + # Contract comparison def == other # :nodoc: