ig_markets 0.4 → 0.5

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -4
  3. data/README.md +70 -17
  4. data/lib/ig_markets.rb +27 -15
  5. data/lib/ig_markets/{account_activity.rb → activity.rb} +1 -1
  6. data/lib/ig_markets/cli/{account_command.rb → commands/account_command.rb} +5 -4
  7. data/lib/ig_markets/cli/commands/activities_command.rb +37 -0
  8. data/lib/ig_markets/cli/commands/confirmation_command.rb +14 -0
  9. data/lib/ig_markets/cli/{orders_command.rb → commands/orders_command.rb} +16 -21
  10. data/lib/ig_markets/cli/{positions_command.rb → commands/positions_command.rb} +31 -19
  11. data/lib/ig_markets/cli/commands/search_command.rb +18 -0
  12. data/lib/ig_markets/cli/commands/sentiment_command.rb +19 -0
  13. data/lib/ig_markets/cli/{sprints_command.rb → commands/sprints_command.rb} +10 -8
  14. data/lib/ig_markets/cli/commands/transactions_command.rb +61 -0
  15. data/lib/ig_markets/cli/{watchlists_command.rb → commands/watchlists_command.rb} +16 -12
  16. data/lib/ig_markets/cli/main.rb +65 -15
  17. data/lib/ig_markets/cli/tables/accounts_table.rb +30 -0
  18. data/lib/ig_markets/cli/tables/activities_table.rb +29 -0
  19. data/lib/ig_markets/cli/tables/client_sentiments_table.rb +31 -0
  20. data/lib/ig_markets/cli/tables/market_overviews_table.rb +47 -0
  21. data/lib/ig_markets/cli/tables/positions_table.rb +83 -0
  22. data/lib/ig_markets/cli/tables/sprint_market_positions_table.rb +55 -0
  23. data/lib/ig_markets/cli/tables/table.rb +103 -0
  24. data/lib/ig_markets/cli/tables/transactions_table.rb +41 -0
  25. data/lib/ig_markets/cli/tables/working_orders_table.rb +27 -0
  26. data/lib/ig_markets/dealing_platform/account_methods.rb +8 -8
  27. data/lib/ig_markets/dealing_platform/client_sentiment_methods.rb +4 -0
  28. data/lib/ig_markets/dealing_platform/market_methods.rb +1 -1
  29. data/lib/ig_markets/format.rb +20 -7
  30. data/lib/ig_markets/market_overview.rb +1 -1
  31. data/lib/ig_markets/model.rb +10 -1
  32. data/lib/ig_markets/model/typecasters.rb +1 -1
  33. data/lib/ig_markets/position.rb +12 -11
  34. data/lib/ig_markets/request_printer.rb +56 -0
  35. data/lib/ig_markets/response_parser.rb +11 -0
  36. data/lib/ig_markets/session.rb +7 -8
  37. data/lib/ig_markets/{account_transaction.rb → transaction.rb} +4 -17
  38. data/lib/ig_markets/version.rb +1 -1
  39. metadata +54 -16
  40. data/lib/ig_markets/cli/activities_command.rb +0 -31
  41. data/lib/ig_markets/cli/confirmation_command.rb +0 -16
  42. data/lib/ig_markets/cli/output.rb +0 -115
  43. data/lib/ig_markets/cli/search_command.rb +0 -16
  44. data/lib/ig_markets/cli/sentiment_command.rb +0 -24
  45. data/lib/ig_markets/cli/transactions_command.rb +0 -57
@@ -5,7 +5,7 @@ module IGMarkets
5
5
  attribute :delay_time, Float
6
6
  attribute :epic, String, regex: Regex::EPIC
7
7
  attribute :exchange_id
8
- attribute :expiry
8
+ attribute :expiry, String, nil_if: '-'
9
9
  attribute :high, Float
10
10
  attribute :instrument_name
11
11
  attribute :instrument_type, Symbol, allowed_values: Instrument.allowed_values(:type)
@@ -24,6 +24,15 @@ module IGMarkets
24
24
  end
25
25
  end
26
26
 
27
+ # Copy initializer that duplicates the {#attributes} hash in full.
28
+ #
29
+ # @param [Model] other The model to copy.
30
+ def initialize_copy(other)
31
+ super
32
+
33
+ @attributes = other.attributes.dup
34
+ end
35
+
27
36
  # Compares this model to another, the attributes and class must match for them to be considered equal.
28
37
  #
29
38
  # @param [#class, #attributes] other The other model to compare to.
@@ -49,7 +58,7 @@ module IGMarkets
49
58
  # Returns the #inspect string for the given value.
50
59
  def inspect_value(value)
51
60
  if value.is_a? Time
52
- value.utc.strftime '%F %T %z'
61
+ value.utc.strftime '%F %T %Z'
53
62
  elsif value.is_a? Date
54
63
  value.strftime '%F'
55
64
  else
@@ -35,7 +35,7 @@ module IGMarkets
35
35
  end
36
36
 
37
37
  def typecaster_float(value, _options)
38
- return nil if value.nil?
38
+ return nil if value.nil? || value == ''
39
39
 
40
40
  Float(value)
41
41
  end
@@ -23,6 +23,13 @@ module IGMarkets
23
23
  !trailing_step.nil? && !trailing_stop_distance.nil?
24
24
  end
25
25
 
26
+ # Returns the level at which this position would close at given the current market price as stored in {#market}.
27
+ #
28
+ # @return [Float]
29
+ def close_level
30
+ { buy: market.bid, sell: market.offer }.fetch direction
31
+ end
32
+
26
33
  # Returns the favorable difference in the price between {#level} and the current market price as stored in
27
34
  # {#market}. If {#direction} is `:buy` and the market has since risen then this method will return a positive value,
28
35
  # but if {#direction} is `:sell` and the market has since risen then this method will return a negative value.
@@ -30,9 +37,9 @@ module IGMarkets
30
37
  # @return [Float]
31
38
  def price_delta
32
39
  if direction == :buy
33
- market.bid - level
40
+ close_level - level
34
41
  elsif direction == :sell
35
- level - market.offer
42
+ level - close_level
36
43
  end
37
44
  end
38
45
 
@@ -42,19 +49,13 @@ module IGMarkets
42
49
  end
43
50
 
44
51
  # Returns this position's current profit or loss, denominated in its {#currency}, and based on the current market
45
- # state as stored in {#market}.
52
+ # state as stored in {#market}. For binary instruments this returns the payout amount.
46
53
  #
47
54
  # @return [Float]
48
55
  def profit_loss
49
- price_delta * size * market.lot_size * market.scaling_factor
50
- end
56
+ return size / level if market.instrument_type == :binary
51
57
 
52
- # Returns this position's {#size} as a string prefixed with a `+` if {#direction} is `:buy`, or a `-` if
53
- # {#direction} is `:sell`.
54
- #
55
- # @return [String]
56
- def formatted_size
57
- "#{{ buy: '+', sell: '-' }.fetch(direction)}#{format '%g', size}"
58
+ price_delta * size * contract_size
58
59
  end
59
60
 
60
61
  # Closes this position. If called with no options then this position will be fully closed at current market prices,
@@ -0,0 +1,56 @@
1
+ module IGMarkets
2
+ # This class contains methods for printing a REST request and its JSON response for inspection and debugging.
3
+ # Request printing is enabled by setting {enabled} to `true`.
4
+ class RequestPrinter
5
+ class << self
6
+ # @return [Boolean] Whether the request printer is enabled.
7
+ attr_accessor :enabled
8
+
9
+ # Prints out an options hash that is ready to be passed to `RestClient::Request.execute`.
10
+ #
11
+ # @param [Hash] options The options hash.
12
+ #
13
+ # @return [void]
14
+ def print_options(options)
15
+ return unless enabled
16
+
17
+ puts "#{options[:method].to_s.upcase} #{options[:url]}"
18
+
19
+ puts ' Headers:'
20
+ options[:headers].each do |name, value|
21
+ print_request_header name, value
22
+ end
23
+
24
+ print_request_body options[:payload]
25
+ end
26
+
27
+ # Formats and prints a JSON response body.
28
+ #
29
+ # @param [String] body The response body.
30
+ #
31
+ # @return [void]
32
+ def print_response_body(body)
33
+ return unless enabled
34
+
35
+ print ' Response: '
36
+
37
+ puts JSON.pretty_generate(JSON.parse(body)).gsub "\n", "\n "
38
+ rescue JSON::ParserError
39
+ puts body
40
+ end
41
+
42
+ private
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
+ def print_request_body(body)
49
+ return unless body
50
+
51
+ print ' Body: '
52
+ puts JSON.pretty_generate(JSON.parse(body)).gsub("\n", "\n ")
53
+ end
54
+ end
55
+ end
56
+ end
@@ -3,6 +3,17 @@ module IGMarkets
3
3
  module ResponseParser
4
4
  module_function
5
5
 
6
+ # Parses the passed JSON string and then passes it on to {parse}. If `json` is not valid JSON then `{}` is returned.
7
+ #
8
+ # @param [String] json The JSON string.
9
+ #
10
+ # @return [Hash]
11
+ def parse_json(json)
12
+ parse JSON.parse(json)
13
+ rescue JSON::ParserError
14
+ {}
15
+ end
16
+
6
17
  # Parses the specified value that was returned from a call to the IG Markets API.
7
18
  #
8
19
  # @param [Hash, Array, Object] response The response or part of a reponse that should be parsed. If this is of type
@@ -125,7 +125,12 @@ module IGMarkets
125
125
  options[:headers] = request_headers(options)
126
126
  options[:payload] = options[:payload] && options[:payload].to_json
127
127
 
128
+ RequestPrinter.print_options options
129
+
128
130
  response = execute_request options
131
+
132
+ RequestPrinter.print_response_body response.body
133
+
129
134
  result = process_response response
130
135
 
131
136
  { response: response, result: result }
@@ -167,17 +172,11 @@ module IGMarkets
167
172
  end
168
173
 
169
174
  def client_token_invalid?(response)
170
- ResponseParser.parse(JSON.parse(response.body))[:error_code] == 'error.security.client-token-invalid'
171
- rescue JSON::ParserError
172
- false
175
+ ResponseParser.parse_json(response.body)[:error_code] == 'error.security.client-token-invalid'
173
176
  end
174
177
 
175
178
  def process_response(response)
176
- result = begin
177
- ResponseParser.parse JSON.parse(response.body)
178
- rescue JSON::ParserError
179
- {}
180
- end
179
+ result = ResponseParser.parse_json response.body
181
180
 
182
181
  unless response.code >= 200 && response.code < 300
183
182
  raise RequestFailedError.new(result[:error_code], response.code)
@@ -2,14 +2,14 @@ module IGMarkets
2
2
  # Contains details on a single transaction that occurred on an IG Markets account. Returned by
3
3
  # {DealingPlatform::AccountMethods#transactions_in_date_range} and
4
4
  # {DealingPlatform::AccountMethods#recent_transactions}.
5
- class AccountTransaction < Model
5
+ class Transaction < Model
6
6
  attribute :cash_transaction, Boolean
7
- attribute :close_level
7
+ attribute :close_level, String, nil_if: %w(- 0)
8
8
  attribute :currency
9
9
  attribute :date, Date, format: '%d/%m/%y'
10
10
  attribute :instrument_name
11
- attribute :open_level, String, nil_if: '-'
12
- attribute :period, String, nil_if: '-'
11
+ attribute :open_level, String, nil_if: %w(- 0)
12
+ attribute :period, Time, nil_if: '-', format: '%d/%m/%y %T', time_zone: '+10:00'
13
13
  attribute :profit_and_loss
14
14
  attribute :reference
15
15
  attribute :size, String, nil_if: '-'
@@ -32,18 +32,5 @@ module IGMarkets
32
32
 
33
33
  profit_and_loss[currency.length..-1].delete(',').to_f
34
34
  end
35
-
36
- # Returns this transaction's {#transaction_type} as a human-readable string.
37
- #
38
- # @return [String]
39
- def formatted_transaction_type
40
- {
41
- deal: 'Deal',
42
- depo: 'Deposit',
43
- dividend: 'Dividend',
44
- exchange: 'Exchange',
45
- with: 'Withdrawal'
46
- }.fetch transaction_type
47
- end
48
35
  end
49
36
  end
@@ -1,4 +1,4 @@
1
1
  module IGMarkets
2
2
  # The version of this gem.
3
- VERSION = '0.4'.freeze
3
+ VERSION = '0.5'.freeze
4
4
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ig_markets
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.4'
4
+ version: '0.5'
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-04-17 00:00:00.000000000 Z
11
+ date: 2016-04-23 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.7'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rest-client
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -24,6 +38,20 @@ dependencies:
24
38
  - - "~>"
25
39
  - !ruby/object:Gem::Version
26
40
  version: '1.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: terminal-table
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.5'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.5'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: thor
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -163,24 +191,31 @@ files:
163
191
  - bin/ig_markets
164
192
  - lib/ig_markets.rb
165
193
  - lib/ig_markets/account.rb
166
- - lib/ig_markets/account_activity.rb
167
- - lib/ig_markets/account_transaction.rb
194
+ - lib/ig_markets/activity.rb
168
195
  - lib/ig_markets/api_versions.rb
169
196
  - lib/ig_markets/application.rb
170
197
  - lib/ig_markets/boolean.rb
171
- - lib/ig_markets/cli/account_command.rb
172
- - lib/ig_markets/cli/activities_command.rb
198
+ - lib/ig_markets/cli/commands/account_command.rb
199
+ - lib/ig_markets/cli/commands/activities_command.rb
200
+ - lib/ig_markets/cli/commands/confirmation_command.rb
201
+ - lib/ig_markets/cli/commands/orders_command.rb
202
+ - lib/ig_markets/cli/commands/positions_command.rb
203
+ - lib/ig_markets/cli/commands/search_command.rb
204
+ - lib/ig_markets/cli/commands/sentiment_command.rb
205
+ - lib/ig_markets/cli/commands/sprints_command.rb
206
+ - lib/ig_markets/cli/commands/transactions_command.rb
207
+ - lib/ig_markets/cli/commands/watchlists_command.rb
173
208
  - lib/ig_markets/cli/config_file.rb
174
- - lib/ig_markets/cli/confirmation_command.rb
175
209
  - lib/ig_markets/cli/main.rb
176
- - lib/ig_markets/cli/orders_command.rb
177
- - lib/ig_markets/cli/output.rb
178
- - lib/ig_markets/cli/positions_command.rb
179
- - lib/ig_markets/cli/search_command.rb
180
- - lib/ig_markets/cli/sentiment_command.rb
181
- - lib/ig_markets/cli/sprints_command.rb
182
- - lib/ig_markets/cli/transactions_command.rb
183
- - lib/ig_markets/cli/watchlists_command.rb
210
+ - lib/ig_markets/cli/tables/accounts_table.rb
211
+ - lib/ig_markets/cli/tables/activities_table.rb
212
+ - lib/ig_markets/cli/tables/client_sentiments_table.rb
213
+ - lib/ig_markets/cli/tables/market_overviews_table.rb
214
+ - lib/ig_markets/cli/tables/positions_table.rb
215
+ - lib/ig_markets/cli/tables/sprint_market_positions_table.rb
216
+ - lib/ig_markets/cli/tables/table.rb
217
+ - lib/ig_markets/cli/tables/transactions_table.rb
218
+ - lib/ig_markets/cli/tables/working_orders_table.rb
184
219
  - lib/ig_markets/client_sentiment.rb
185
220
  - lib/ig_markets/deal_confirmation.rb
186
221
  - lib/ig_markets/dealing_platform.rb
@@ -204,9 +239,11 @@ files:
204
239
  - lib/ig_markets/position.rb
205
240
  - lib/ig_markets/regex.rb
206
241
  - lib/ig_markets/request_failed_error.rb
242
+ - lib/ig_markets/request_printer.rb
207
243
  - lib/ig_markets/response_parser.rb
208
244
  - lib/ig_markets/session.rb
209
245
  - lib/ig_markets/sprint_market_position.rb
246
+ - lib/ig_markets/transaction.rb
210
247
  - lib/ig_markets/version.rb
211
248
  - lib/ig_markets/watchlist.rb
212
249
  - lib/ig_markets/working_order.rb
@@ -233,6 +270,7 @@ rubyforge_project:
233
270
  rubygems_version: 2.5.1
234
271
  signing_key:
235
272
  specification_version: 4
236
- summary: Ruby client for the IG Markets dealing platform.
273
+ summary: Ruby library and command-line client for accessing the IG Markets dealing
274
+ platform.
237
275
  test_files: []
238
276
  has_rdoc:
@@ -1,31 +0,0 @@
1
- module IGMarkets
2
- module CLI
3
- # Implements the `ig_markets activities` command.
4
- class Main
5
- desc 'activities', 'Prints account activities'
6
-
7
- option :days, type: :numeric, required: true, desc: 'The number of days to print account activities for'
8
- option :start_date, desc: 'The start date to print account activities from, format: yyyy-mm-dd'
9
-
10
- def activities
11
- self.class.begin_session(options) do |dealing_platform|
12
- gather_activities(dealing_platform, options[:days], options[:start_date]).sort_by(&:date).each do |activity|
13
- Output.print_activity activity
14
- end
15
- end
16
- end
17
-
18
- private
19
-
20
- def gather_activities(dealing_platform, days, start_date = nil)
21
- if start_date
22
- start_date = Date.strptime options[:start_date], '%F'
23
-
24
- dealing_platform.account.activities_in_date_range start_date, start_date + days.to_i
25
- else
26
- dealing_platform.account.recent_activities days
27
- end
28
- end
29
- end
30
- end
31
- end
@@ -1,16 +0,0 @@
1
- module IGMarkets
2
- module CLI
3
- # Implements the `ig_markets confirmation` command.
4
- class Main < Thor
5
- desc 'confirmation <DEAL-REFERENCE>', 'Prints the deal confirmation for the specified deal reference'
6
-
7
- def confirmation(deal_reference)
8
- self.class.begin_session(options) do |dealing_platform|
9
- deal_confirmation = dealing_platform.deal_confirmation deal_reference
10
-
11
- Output.print_deal_confirmation deal_confirmation
12
- end
13
- end
14
- end
15
- end
16
- end
@@ -1,115 +0,0 @@
1
- module IGMarkets
2
- module CLI
3
- # Contains methods used by the CLI to print models to stdout.
4
- module Output
5
- module_function
6
-
7
- def print_account(account)
8
- puts <<-END
9
- Account '#{account.account_name}':
10
- ID: #{account.account_id}
11
- Type: #{account.account_type.to_s.upcase}
12
- Currency: #{account.currency}
13
- Status: #{account.status.to_s.upcase}
14
- END
15
- end
16
-
17
- def print_account_balance(account)
18
- {
19
- available: 'Available: ',
20
- balance: 'Balance: ',
21
- deposit: 'Margin: ',
22
- profit_loss: 'Profit/loss:'
23
- }.each do |attribute, title|
24
- puts " #{title} #{Format.currency account.balance.send(attribute), account.currency}"
25
- end
26
- end
27
-
28
- def print_activity(activity)
29
- puts <<-END
30
- #{activity.date.strftime '%F'} #{activity.deal_id}: \
31
- #{activity.size} of #{activity.epic}, \
32
- level: #{activity.level}, \
33
- result: #{activity.result}
34
- END
35
- end
36
-
37
- def print_client_sentiment(client_sentiment)
38
- puts <<-END
39
- #{client_sentiment.market_id}: \
40
- longs: #{client_sentiment.long_position_percentage}%, \
41
- shorts: #{client_sentiment.short_position_percentage}%
42
- END
43
- end
44
-
45
- def print_deal_confirmation(deal_confirmation)
46
- print "Deal confirmation: #{deal_confirmation.deal_id}, #{deal_confirmation.deal_status}, "
47
-
48
- if deal_confirmation.deal_status == :accepted
49
- print "affected deals: #{deal_confirmation.affected_deals.map(&:deal_id).join(',')}, "
50
- else
51
- print "reason: #{deal_confirmation.reason}, "
52
- end
53
-
54
- puts "epic: #{deal_confirmation.epic}"
55
- end
56
-
57
- def print_market_overview(market)
58
- puts <<-END
59
- #{market.epic}: \
60
- #{market.instrument_name}, \
61
- type: #{market.instrument_type}, \
62
- bid: #{market.bid} \
63
- offer: #{market.offer}
64
- END
65
- end
66
-
67
- def print_position(position)
68
- puts <<-END
69
- #{position.deal_id}: \
70
- #{position.formatted_size} of #{position.market.epic} at #{position.level}, \
71
- profit/loss: #{Format.currency position.profit_loss, position.currency}
72
- END
73
- end
74
-
75
- def print_sprint_market_position(sprint)
76
- puts <<-END
77
- #{sprint.deal_id}: \
78
- #{Format.currency sprint.size, sprint.currency} on #{sprint.epic} \
79
- to be #{{ buy: 'above', sell: 'below' }.fetch(sprint.direction)} #{sprint.strike_level} \
80
- in #{Format.seconds sprint.seconds_till_expiry}, \
81
- payout: #{Format.currency sprint.payout_amount, sprint.currency}
82
- END
83
- end
84
-
85
- def print_transaction(transaction)
86
- puts <<-END
87
- #{transaction.date.strftime '%F'} #{transaction.reference}: \
88
- #{transaction.formatted_transaction_type}, \
89
- #{"#{transaction.size} of " if transaction.size}\
90
- #{transaction.instrument_name}, \
91
- profit/loss: #{Format.currency transaction.profit_and_loss_amount, transaction.currency}
92
- END
93
- end
94
-
95
- def print_watchlist(watchlist)
96
- puts <<-END
97
- #{watchlist.id}: #{watchlist.name}, \
98
- editable: #{watchlist.editable}, \
99
- deleteable: #{watchlist.deleteable}, \
100
- default: #{watchlist.default_system_watchlist}
101
- END
102
- end
103
-
104
- def print_working_order(order)
105
- puts <<-END
106
- #{order.deal_id}: \
107
- #{order.direction} #{format '%g', order.order_size} of #{order.epic} at #{order.order_level}\
108
- , limit distance: #{order.limit_distance || '-'}\
109
- , stop distance: #{order.stop_distance || '-'}\
110
- #{", good till #{order.good_till_date.utc.strftime '%F %R %z'}" if order.time_in_force == :good_till_date}
111
- END
112
- end
113
- end
114
- end
115
- end