ig_markets 0.17 → 0.18

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4c696f25e6b960d65896474cf519c7492f8ad195
4
- data.tar.gz: 54dcc2e3caa92373344c3faf7c3a1ea8f916dcf2
3
+ metadata.gz: 2b3fab7b62a9c929a8262f9ef104c200278e03b2
4
+ data.tar.gz: 1fe0c83b896f00e493a2e7a4257fb2f43a80188f
5
5
  SHA512:
6
- metadata.gz: 3b0052a80212495c796d6ab5c75188690f259d640fa96982b7c0cb7f7b636014bc43cea9e5102a4bb17e8c07cb0f671d634db469165ab83b002d66d7bdde53b6
7
- data.tar.gz: 588dd121edd94b04546afa353802f39ccdde3c972684f17d297d6d2d649baa2cce25968dfc82a6876e06ce7e0e86093236ba4a13720d9db98531a437c057cb17
6
+ metadata.gz: e3325e6ea4704dc3bc75f333d82718115a82febc8aa4f814bd10f3495bdf27a3fc43bafa59b39fe0a7c01a454c8417478ef4aa9588551cfbe5875ab4927e2ae6
7
+ data.tar.gz: e4578ffa1bb86eab2cfb2b32a0223e9792db69ce50f433e6da2936415fac0cc248d51a605b37a1a777434da01697e6e5def084e5911faad5c1ad565b9fade536
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # IG Markets Changelog
2
2
 
3
+ ### 0.18 — August 2, 2016
4
+
5
+ - Added support for the recently added `#date` attribute on `IGMarkets::DealConfirmation`
6
+ - Fixed the second call to `IGMarkets::Session#sign_in` looping indefinitely if the previously logged in session was not
7
+ explicitly signed out
8
+ - Fixed automatic re-sign-in when the client security token is invalid not working correctly
9
+ - Fixed exception when executing a `DELETE` request and verbose output is enabled
10
+
3
11
  ### 0.17 — July 27, 2016
4
12
 
5
13
  - Switched to the `excon` HTTP library
@@ -0,0 +1,54 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Implements the `ig_markets stream` command.
4
+ class Main < Thor
5
+ desc 'stream', 'Displays a live stream of prices for a set of markets'
6
+
7
+ option :epics, type: :array, desc: 'The EPICs of the markets to stream live prices for'
8
+
9
+ def stream
10
+ self.class.begin_session(options) do |dealing_platform|
11
+ @data_queue = Queue.new
12
+
13
+ lightstreamer = create_lightstreamer_session dealing_platform
14
+ create_lightstreamer_subscription lightstreamer
15
+
16
+ loop do
17
+ puts @data_queue.pop
18
+
19
+ raise lightstreamer.error if lightstreamer.error
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def create_lightstreamer_session(dealing_platform)
27
+ session = dealing_platform.session
28
+
29
+ server_url = dealing_platform.client_account_summary.lightstreamer_endpoint
30
+ username = dealing_platform.client_account_summary.client_id
31
+ password = "CST-#{session.client_security_token}|XST-#{session.x_security_token}"
32
+
33
+ lightstreamer = Lightstreamer::Session.new server_url: server_url, username: username, password: password
34
+
35
+ lightstreamer.connect
36
+ puts "Session ID: #{lightstreamer.session_id}"
37
+
38
+ lightstreamer
39
+ end
40
+
41
+ def create_lightstreamer_subscription(lightstreamer)
42
+ items = Array(options[:epics]).map { |market| "MARKET:#{market}" }
43
+
44
+ subscription = lightstreamer.build_subscription items: items, fields: [:bid, :offer], mode: :merge
45
+
46
+ subscription.on_data do |_subscription, item_name, item_data, _new_values|
47
+ @data_queue.push "#{item_name[7..-1]} - #{item_data.map { |key, value| "#{key}: #{value}" }.join ', '}"
48
+ end
49
+
50
+ subscription.start snapshot: true
51
+ end
52
+ end
53
+ end
54
+ end
@@ -135,11 +135,12 @@ module IGMarkets
135
135
  # Prints the passed error to `stderr` and then exits the application.
136
136
  def warn_and_exit(error)
137
137
  class_name = error.class.name.split('::').last
138
+ class_name = nil if class_name == 'IGMarketsError'
138
139
 
139
140
  message = error.message.to_s
140
- message = nil if message.empty? || message == error.class.to_s
141
+ message = nil if ['', error.class.to_s].include? message
141
142
 
142
- warn ["IG Markets: #{class_name}", message].compact.join(', ')
143
+ warn "ig_markets: #{[class_name, message].compact.join ', '}"
143
144
 
144
145
  exit 1
145
146
  end
@@ -8,6 +8,7 @@ module IGMarkets
8
8
  end
9
9
 
10
10
  attribute :affected_deals, AffectedDeal
11
+ attribute :date, Time, format: '%FT%T.%L'
11
12
  attribute :deal_id
12
13
  attribute :deal_reference
13
14
  attribute :deal_status, Symbol, allowed_values: [:accepted, :fund_account, :rejected]
@@ -23,14 +24,14 @@ module IGMarkets
23
24
  attribute :reason, Symbol, allowed_values: [:account_not_enabled_to_trading, :attached_order_level_error,
24
25
  :attached_order_trailing_stop_error, :cannot_change_stop_type,
25
26
  :cannot_remove_stop, :closing_only_trades_accepted_on_this_market,
26
- :conflicting_order, :cr_spacing, :duplicate_order_error,
27
- :exchange_manual_override, :finance_repeat_dealing,
28
- :force_open_on_same_market_different_currency, :general_error,
29
- :good_till_date_in_the_past, :instrument_not_found, :insufficient_funds,
30
- :level_tolerance_error, :manual_order_timeout, :market_closed,
31
- :market_closed_with_edits, :market_closing, :market_not_borrowable,
32
- :market_offline, :market_phone_only, :market_rolled,
33
- :market_unavailable_to_client, :max_auto_size_exceeded,
27
+ :conflicting_order, :contact_support_instrument_error, :cr_spacing,
28
+ :duplicate_order_error, :exchange_manual_override,
29
+ :finance_repeat_dealing, :force_open_on_same_market_different_currency,
30
+ :general_error, :good_till_date_in_the_past, :instrument_not_found,
31
+ :insufficient_funds, :level_tolerance_error, :manual_order_timeout,
32
+ :market_closed, :market_closed_with_edits, :market_closing,
33
+ :market_not_borrowable, :market_offline, :market_phone_only,
34
+ :market_rolled, :market_unavailable_to_client, :max_auto_size_exceeded,
34
35
  :minimum_order_size_error, :move_away_only_limit, :move_away_only_stop,
35
36
  :move_away_only_trigger_level, :opposing_direction_orders_not_allowed,
36
37
  :opposing_positions_not_allowed, :order_locked, :order_not_found,
@@ -70,9 +70,9 @@ module IGMarkets
70
70
  def create(attributes)
71
71
  model = PositionCreateAttributes.new attributes
72
72
 
73
- payload = PayloadFormatter.format model, expiry: '-'
73
+ body = RequestBodyFormatter.format model, expiry: '-'
74
74
 
75
- @dealing_platform.session.post('positions/otc', payload, API_V2).fetch(:deal_reference)
75
+ @dealing_platform.session.post('positions/otc', body, API_V2).fetch(:deal_reference)
76
76
  end
77
77
 
78
78
  private
@@ -42,9 +42,9 @@ module IGMarkets
42
42
  # @return [String] The resulting deal reference, use {DealingPlatform#deal_confirmation} to check the result of
43
43
  # the sprint market position creation.
44
44
  def create(attributes)
45
- payload = PayloadFormatter.format SprintMarketPositionCreateAttributes.new attributes
45
+ body = RequestBodyFormatter.format SprintMarketPositionCreateAttributes.new(attributes)
46
46
 
47
- @dealing_platform.session.post('positions/sprintmarkets', payload).fetch :deal_reference
47
+ @dealing_platform.session.post('positions/sprintmarkets', body).fetch :deal_reference
48
48
  end
49
49
 
50
50
  # Internal model used by {#create}
@@ -64,9 +64,9 @@ module IGMarkets
64
64
  def create(attributes)
65
65
  model = WorkingOrderCreateAttributes.new attributes
66
66
 
67
- payload = PayloadFormatter.format model, expiry: '-'
67
+ body = RequestBodyFormatter.format model, expiry: '-'
68
68
 
69
- @dealing_platform.session.post('workingorders/otc', payload, API_V2).fetch :deal_reference
69
+ @dealing_platform.session.post('workingorders/otc', body, API_V2).fetch :deal_reference
70
70
  end
71
71
 
72
72
  # Internal model used by {#create}.
@@ -93,9 +93,9 @@ module IGMarkets
93
93
  model = PositionCloseAttributes.build options
94
94
  model.validate
95
95
 
96
- payload = PayloadFormatter.format model
96
+ body = RequestBodyFormatter.format model
97
97
 
98
- @dealing_platform.session.delete('positions/otc', payload).fetch :deal_reference
98
+ @dealing_platform.session.delete('positions/otc', body).fetch :deal_reference
99
99
  end
100
100
 
101
101
  # Updates this position. No attributes are mandatory, and any attributes not specified will be kept at their
@@ -119,9 +119,9 @@ module IGMarkets
119
119
  new_attributes[:trailing_stop_distance] = new_attributes[:trailing_stop_increment] = nil
120
120
  end
121
121
 
122
- payload = PayloadFormatter.format PositionUpdateAttributes.new new_attributes
122
+ body = RequestBodyFormatter.format PositionUpdateAttributes.new(new_attributes)
123
123
 
124
- @dealing_platform.session.put("positions/otc/#{deal_id}", payload, API_V2).fetch(:deal_reference)
124
+ @dealing_platform.session.put("positions/otc/#{deal_id}", body, API_V2).fetch(:deal_reference)
125
125
  end
126
126
 
127
127
  # Validates the internal consistency of the `:order_type`, `:quote_id` and `:level` attributes.
@@ -1,11 +1,11 @@
1
1
  module IGMarkets
2
- # Contains methods for formatting payloads that can be passed to the IG Markets API.
2
+ # Contains methods for formatting request bodies that can be passed to the IG Markets API.
3
3
  #
4
4
  # @private
5
- module PayloadFormatter
5
+ module RequestBodyFormatter
6
6
  module_function
7
7
 
8
- # Takes a {Model} and returns its attributes in a hash ready to be passed as a payload to the IG Markets API.
8
+ # Takes a {Model} and returns its attributes in a hash ready to be passed as a request body to the IG Markets API.
9
9
  # Attribute names will be converted from snake case to camel case, `Symbol` attributes will be converted to strings
10
10
  # and will be uppercased, and both `Date` and `Time` attributes will be converted to strings using their first
11
11
  # available `:format` option.
@@ -20,10 +20,10 @@ module IGMarkets
20
20
 
21
21
  puts ' Headers:'
22
22
  options[:headers].each do |name, value|
23
- print_request_header name, value
23
+ puts " #{name}: #{value}"
24
24
  end
25
25
 
26
- print_request_body options[:payload]
26
+ print_request_body options[:body]
27
27
  end
28
28
 
29
29
  # Formats and prints a JSON response body.
@@ -41,10 +41,6 @@ module IGMarkets
41
41
 
42
42
  private
43
43
 
44
- def print_request_header(name, value)
45
- puts " #{name.to_s.split('_').map { |h| h[0].upcase + h[1..-1] }.join('-')}: #{value}"
46
- end
47
-
48
44
  def print_request_body(body)
49
45
  return unless body
50
46
 
@@ -40,6 +40,8 @@ module IGMarkets
40
40
  #
41
41
  # @return [Hash] The data returned in the body of the sign in request.
42
42
  def sign_in
43
+ @client_security_token = @x_security_token = nil
44
+
43
45
  body = { identifier: username, password: password_encryptor.encrypt(password), encryptedPassword: true }
44
46
 
45
47
  sign_in_result = request method: :post, url: 'session', body: body, api_version: API_V2
@@ -139,7 +141,7 @@ module IGMarkets
139
141
 
140
142
  headers['Content-Type'] = headers['Accept'] = 'application/json; charset=UTF-8'
141
143
  headers['X-IG-API-KEY'] = api_key
142
- headers['Version'] = options.delete :api_version
144
+ headers['Version'] = options.fetch :api_version
143
145
 
144
146
  headers['CST'] = client_security_token if client_security_token
145
147
  headers['X-SECURITY-TOKEN'] = x_security_token if x_security_token
@@ -162,12 +164,12 @@ module IGMarkets
162
164
  def process_response(response, options)
163
165
  body = parse_body response
164
166
 
165
- if body.key? :error_code
167
+ if body.is_a?(Hash) && body.key?(:error_code)
166
168
  error = IGMarketsError.build body[:error_code]
167
169
 
168
170
  raise error unless should_retry_request? error, options
169
171
 
170
- execute_request options.merge(retry: true)
172
+ execute_request options.merge(retry: true, headers: request_headers(options))
171
173
  else
172
174
  { response: response, body: body }
173
175
  end
@@ -1,4 +1,4 @@
1
1
  module IGMarkets
2
2
  # The version of this gem.
3
- VERSION = '0.17'.freeze
3
+ VERSION = '0.18'.freeze
4
4
  end
@@ -56,9 +56,9 @@ module IGMarkets
56
56
  model = WorkingOrderUpdateAttributes.new existing_attributes, new_attributes
57
57
  model.validate
58
58
 
59
- payload = PayloadFormatter.format model
59
+ body = RequestBodyFormatter.format model
60
60
 
61
- @dealing_platform.session.put("workingorders/otc/#{deal_id}", payload, API_V2).fetch(:deal_reference)
61
+ @dealing_platform.session.put("workingorders/otc/#{deal_id}", body, API_V2).fetch(:deal_reference)
62
62
  end
63
63
 
64
64
  # Internal model used by {#update}.
data/lib/ig_markets.rb CHANGED
@@ -39,8 +39,8 @@ require 'ig_markets/market'
39
39
  require 'ig_markets/market_overview'
40
40
  require 'ig_markets/market_hierarchy_result'
41
41
  require 'ig_markets/password_encryptor'
42
- require 'ig_markets/payload_formatter'
43
42
  require 'ig_markets/position'
43
+ require 'ig_markets/request_body_formatter'
44
44
  require 'ig_markets/request_printer'
45
45
  require 'ig_markets/response_parser'
46
46
  require 'ig_markets/session'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ig_markets
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.17'
4
+ version: '0.18'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Viney
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-27 00:00:00.000000000 Z
11
+ date: 2016-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -236,6 +236,7 @@ files:
236
236
  - lib/ig_markets/cli/commands/self_test_command.rb
237
237
  - lib/ig_markets/cli/commands/sentiment_command.rb
238
238
  - lib/ig_markets/cli/commands/sprints_command.rb
239
+ - lib/ig_markets/cli/commands/stream_command.rb
239
240
  - lib/ig_markets/cli/commands/transactions_command.rb
240
241
  - lib/ig_markets/cli/commands/watchlists_command.rb
241
242
  - lib/ig_markets/cli/config_file.rb
@@ -273,9 +274,9 @@ files:
273
274
  - lib/ig_markets/model.rb
274
275
  - lib/ig_markets/model/typecasters.rb
275
276
  - lib/ig_markets/password_encryptor.rb
276
- - lib/ig_markets/payload_formatter.rb
277
277
  - lib/ig_markets/position.rb
278
278
  - lib/ig_markets/regex.rb
279
+ - lib/ig_markets/request_body_formatter.rb
279
280
  - lib/ig_markets/request_printer.rb
280
281
  - lib/ig_markets/response_parser.rb
281
282
  - lib/ig_markets/session.rb