ig_markets 0.6 → 0.7

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: 589e81bb5e11e735c8dacf82cac67d784d5d705f
4
- data.tar.gz: 2a84abcae5a0844574fdbc25cbeff2fd831624e3
3
+ metadata.gz: fe408c4f3b1493d7da7574265ceab99d931d8685
4
+ data.tar.gz: b21bbe2f50cb5a5bedbd92b718fea4afc02462b5
5
5
  SHA512:
6
- metadata.gz: f2706333bf110e2b169df6769d602bda5dae0d7e14cb98b92bb73785d81f169b35286855b72367f7182b5d41994514053768d95e1991028ee39c4a7733b3f4fb
7
- data.tar.gz: d7c015c2573702b1ac5c1be4ec83f9c5d7e4212ded0be95d80fbe15f6542d580fed02e7fa1e5cfa5fb921227e4cd6ab74b3aaa47da6a3a093776f1949b8f0d6e
6
+ metadata.gz: 8ff8f42c43576533bd8c44de769f5bb43f725857294c9edce1ab8a45716704c2551eeddb0eb8307345cbfa292a0b1aacb53e45129a3b7ce4c317534ab81dd14c
7
+ data.tar.gz: d7a8011e786e30d2cd5a75de50edf5b129ab73ddc4d037fb6f22e18ec00a783adbd7276035326752f7a42fe2f7dad93a78dd0f77c11dcd5b6f772bd146d6ece2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # IG Markets Changelog
2
2
 
3
+ ### 0.7 — 1 May, 2016
4
+
5
+ - Added `ig_markets console` command which logs in then opens a live Ruby console
6
+ - Merged `IGMarkets::DealingPlatform::AccountMethods#activities_in_date_range` and
7
+ `IGMarkets::DealingPlatform::AccountMethods#recent_activities` into
8
+ `IGMarkets::DealingPlatform::AccountMethods#activities`
9
+ - Merged `IGMarkets::DealingPlatform::AccountMethods#transactions_in_date_range` and
10
+ `IGMarkets::DealingPlatform::AccountMethods#recent_transactions` into
11
+ `IGMarkets::DealingPlatform::AccountMethods#transactions`
12
+ - Merged `IGMarkets::Market#recent_prices` and `IGMarkets::Market#prices_in_date_range` into
13
+ `IGMarkets::Market#historical_prices`
14
+ - Removed the `--account-time-zone` option and `IGMarkets::DealingPlatform#account_time_zone`
15
+ - Improved error reporting on assignment of invalid values to model attributes
16
+ - `IGMarkets::Model#inspect` now reports `Time` attributes in the local time zone
17
+ - Model attributes that are deprecated in the IG Markets API are now no longer included as part of the model classes
18
+
3
19
  ### 0.6 — April 27, 2016
4
20
 
5
21
  - Added `ig_markets prices` command to print historical market prices
data/README.md CHANGED
@@ -69,6 +69,7 @@ commands and their subcommands is:
69
69
  - `ig_markets account`
70
70
  - `ig_markets activities --days N [--start-date YYYY-MM-DD]`
71
71
  - `ig_markets confirmation DEAL-REFERENCE`
72
+ - `ig_markets console`
72
73
  - `ig_markets help [COMMAND]`
73
74
  - `ig_markets orders [list]`
74
75
  - `ig_markets orders create ...`
@@ -125,24 +126,9 @@ ig_markets orders create --direction buy --epic CS.D.EURUSD.CFD.IP --level 1.1 -
125
126
 
126
127
  # Print daily prices for EURUSD from the last two weeks
127
128
  ig_markets prices --epic CS.D.EURUSD.CFD.IP --resolution day --number 14
128
- ```
129
-
130
- #### Account Time Zone
131
-
132
- Some timestamps returned by the IG Markets API are in an unspecified time zone, which can result in some times
133
- displayed by the command-line client being incorrect. By default the unknown time zone is assumed to be UTC, but if this
134
- is incorrect then use the `--account-time-zone` argument to specify the actual time zone these times are in.
135
-
136
- For example, an IG Markets Australia account requires `--account-time-zone +1000`.
137
-
138
- The `--account-time-zone` argument should be put into an `.ig_markets` config file to avoid specifying it on every
139
- invocation.
140
-
141
- To check that the account time zone is correct run the following command and verify that the date and time reported for
142
- the price is the current date/time in your local time zone.
143
129
 
144
- ```
145
- ig_markets prices --epic CS.D.EURUSD.CFD.IP --resolution minute --number 1
130
+ # Log in and then open a live Ruby console which can be used to query the IG API
131
+ ig_markets console
146
132
  ```
147
133
 
148
134
  ## Usage — Library
@@ -162,16 +148,12 @@ ig = IGMarkets::DealingPlatform.new
162
148
  ig.sign_in 'username', 'password', 'api_key', :demo
163
149
  ig.sign_out
164
150
 
165
- # Set the time zone for the account, this is applied to time attributes returned from the IG Markets API that are not
166
- # in a known time zone such as UTC
167
- ig.account_time_zone = '+0000'
168
-
169
151
  # Account
170
152
  ig.account.all
171
- ig.account.recent_activities 365
172
- ig.account.recent_transactions 365
173
- ig.account.activities_in_date_range Date.today - 14, Date.today - 7
174
- ig.account.transactions_in_date_range Date.today - 14, Date.today - 7
153
+ ig.account.activities days: 365
154
+ ig.account.activities from: Date.today - 14, to: Date.today - 7
155
+ ig.account.transactions days: 365
156
+ ig.account.transactions from: Date.today - 14, to: Date.today - 7
175
157
 
176
158
  # Dealing
177
159
  ig.deal_confirmation 'deal_reference'
@@ -195,15 +177,15 @@ ig.working_orders.all
195
177
  ig.working_orders.create currency_code: 'USD', direction: :buy, epic: 'CS.D.EURUSD.CFD.IP', level: 0.99,
196
178
  size: 1, type: :limit
197
179
  ig.working_orders['deal_id']
198
- ig.working_orders['deal_id'].update level: 1.25, limit_distance: 50, stop_distance: 0.02
180
+ ig.working_orders['deal_id'].update level: 1.25, limit_distance: 50, stop_distance: 50
199
181
  ig.working_orders['deal_id'].delete
200
182
 
201
183
  # Markets
202
184
  ig.markets.hierarchy
203
185
  ig.markets.search 'EURUSD'
204
186
  ig.markets['CS.D.EURUSD.CFD.IP']
205
- ig.markets['CS.D.EURUSD.CFD.IP'].recent_prices :day, 10
206
- ig.markets['CS.D.EURUSD.CFD.IP'].prices_in_date_range :day, Date.today - 14, Date.today - 7
187
+ ig.markets['CS.D.EURUSD.CFD.IP'].historical_prices resolution: :hour, number: 48
188
+ ig.markets['CS.D.EURUSD.CFD.IP'].historical_prices resolution: :second, from: Time.now - 120, to: Time.now - 60
207
189
 
208
190
  # Watchlists
209
191
  ig.watchlists.all
data/lib/ig_markets.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'base64'
2
2
  require 'colorize'
3
3
  require 'date'
4
+ require 'pry'
4
5
  require 'rest-client'
5
6
  require 'terminal-table'
6
7
  require 'thor'
@@ -52,6 +53,7 @@ require 'ig_markets/cli/config_file'
52
53
  require 'ig_markets/cli/commands/account_command'
53
54
  require 'ig_markets/cli/commands/activities_command'
54
55
  require 'ig_markets/cli/commands/confirmation_command'
56
+ require 'ig_markets/cli/commands/console_command'
55
57
  require 'ig_markets/cli/commands/prices_command'
56
58
  require 'ig_markets/cli/commands/search_command'
57
59
  require 'ig_markets/cli/commands/sentiment_command'
@@ -1,14 +1,13 @@
1
1
  module IGMarkets
2
2
  # Contains details on a single activity that occurred on an IG Markets account. Returned by
3
- # {DealingPlatform::AccountMethods#activities_in_date_range} and
4
- # {DealingPlatform::AccountMethods#recent_activities}.
3
+ # {DealingPlatform::AccountMethods#activities}.
5
4
  class Activity < Model
6
5
  attribute :action_status, Symbol, allowed_values: [:accept, :reject, :manual, :not_set]
7
6
  attribute :activity
8
7
  attribute :activity_history_id
9
8
  attribute :channel
10
9
  attribute :currency
11
- attribute :date, Date, format: '%d/%m/%y'
10
+ attribute :date, Time, format: '%FT%T'
12
11
  attribute :deal_id
13
12
  attribute :epic, String, regex: Regex::EPIC
14
13
  attribute :level, Float
@@ -19,6 +18,5 @@ module IGMarkets
19
18
  attribute :size
20
19
  attribute :stop, String, nil_if: '-'
21
20
  attribute :stop_type, String, nil_if: '-', allowed_values: %w(G N T(50))
22
- attribute :time
23
21
  end
24
22
  end
@@ -19,20 +19,17 @@ module IGMarkets
19
19
 
20
20
  private
21
21
 
22
- def gather_account_history(type)
23
- days = options[:days]
24
- start_date = options[:start_date]
22
+ def gather_account_history(method_name)
23
+ history_options = if options[:start_date]
24
+ from = Date.strptime options[:start_date], '%F'
25
+ to = from + options[:days].to_i
25
26
 
26
- send_args = if start_date
27
- start_date = Date.strptime start_date, '%F'
28
- end_date = start_date + days.to_i
27
+ { from: from, to: to }
28
+ else
29
+ { days: options[:days] }
30
+ end
29
31
 
30
- ["#{type}_in_date_range", start_date, end_date]
31
- else
32
- ["recent_#{type}", days]
33
- end
34
-
35
- Main.dealing_platform.account.send(*send_args)
32
+ Main.dealing_platform.account.send method_name, history_options
36
33
  end
37
34
  end
38
35
  end
@@ -0,0 +1,16 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Implements the `ig_markets console` command.
4
+ class Main < Thor
5
+ desc 'console', 'Logs in and opens a live Ruby console, the IGMarkets::DealingPlatform instance is named \'ig\''
6
+
7
+ def console
8
+ self.class.begin_session(options) do |dealing_platform|
9
+ ig = dealing_platform
10
+
11
+ pry binding
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -17,14 +17,15 @@ module IGMarkets
17
17
  def prices
18
18
  self.class.begin_session(options) do |dealing_platform|
19
19
  result = historical_price_result dealing_platform
20
+ allowance = result.metadata.allowance
20
21
 
21
22
  table = HistoricalPriceResultSnapshotsTable.new result.prices, title: "Prices for #{options[:epic]}"
22
23
 
23
24
  puts <<-END
24
25
  #{table}
25
26
 
26
- Allowance: #{result.allowance.total_allowance}
27
- Remaining: #{result.allowance.remaining_allowance}
27
+ Allowance: #{allowance.total_allowance}
28
+ Remaining: #{allowance.remaining_allowance}
28
29
  END
29
30
  end
30
31
  end
@@ -50,7 +51,7 @@ END
50
51
  end
51
52
 
52
53
  def historical_price_result_from_number(market)
53
- market.recent_prices resolution, options[:number]
54
+ market.historical_prices resolution: resolution, max: options[:number]
54
55
  end
55
56
 
56
57
  def historical_price_result_from_date_range(market)
@@ -59,7 +60,7 @@ END
59
60
  self.class.parse_date_time filtered, :start_date, Time, '%FT%R%z', 'yyyy-mm-ddThh:mm(+|-)zz:zz'
60
61
  self.class.parse_date_time filtered, :end_date, Time, '%FT%R%z', 'yyyy-mm-ddThh:mm(+|-)zz:zz'
61
62
 
62
- market.prices_in_date_range resolution, filtered[:start_date], filtered[:end_date]
63
+ market.historical_prices resolution: resolution, from: filtered[:start_date], to: filtered[:end_date]
63
64
  end
64
65
  end
65
66
  end
@@ -29,7 +29,7 @@ module IGMarkets
29
29
  def gather_transactions
30
30
  regex = Regexp.new options.fetch('instrument', '')
31
31
 
32
- gather_account_history(:transactions).sort_by(&:date).select do |transaction|
32
+ gather_account_history(:transactions).sort_by(&:date_utc).select do |transaction|
33
33
  regex.match(transaction.instrument_name) && (options[:interest] || !transaction.interest?)
34
34
  end
35
35
  end
@@ -2,12 +2,14 @@ module IGMarkets
2
2
  module CLI
3
3
  # Helper class for working with the config files supported by the command-line client.
4
4
  class ConfigFile
5
- # Initializes this config file with the contents of the specified config file.
6
- def initialize(config_file)
7
- @lines = File.readlines config_file
5
+ # Initializes this config file with the passed lines.
6
+ #
7
+ # @param [Array<String>] lines
8
+ def initialize(lines = [])
9
+ @lines = lines
8
10
  end
9
11
 
10
- # Returns the arguments in this config file as an array.
12
+ # Returns the arguments contained in this config file.
11
13
  #
12
14
  # @return [Array<String>]
13
15
  def arguments
@@ -17,20 +19,28 @@ module IGMarkets
17
19
  .split(' ')
18
20
  end
19
21
 
20
- # Returns the config file to use, or `nil` if there is no config file.
22
+ # Inserts the arguments from this config file into the passed arguments array.
23
+ #
24
+ # @param [Array<String>] argv The array of command-line arguments to alter.
21
25
  #
22
- # @return [CLI::ConfigFile]
23
- def self.find
24
- config_file_locations = [
25
- "#{Dir.pwd}/.ig_markets",
26
- "#{Dir.home}/.ig_markets"
27
- ]
26
+ # @return [void]
27
+ def prepend_arguments_to_argv(argv)
28
+ insert_index = argv.index do |argument|
29
+ argument[0] == '-'
30
+ end || -1
28
31
 
29
- config_file = config_file_locations.detect do |filename|
32
+ argv.insert insert_index, *arguments
33
+ end
34
+
35
+ # Takes a list of potential config files and returns a {ConfigFile} instance for the first one that exists.
36
+ #
37
+ # @return [ConfigFile]
38
+ def self.find(*config_files)
39
+ config_file = config_files.detect do |filename|
30
40
  File.exist? filename
31
41
  end
32
42
 
33
- config_file ? new(config_file) : nil
43
+ new(config_file ? File.readlines(config_file) : [])
34
44
  end
35
45
  end
36
46
  end
@@ -7,7 +7,6 @@ module IGMarkets
7
7
  class_option :password, required: true, desc: 'The password for the session'
8
8
  class_option :api_key, required: true, desc: 'The API key for the session'
9
9
  class_option :demo, type: :boolean, desc: 'Use the demo platform (default is production)'
10
- class_option :account_time_zone, default: '+0000', desc: 'The time zone of the account'
11
10
  class_option :print_requests, type: :boolean, desc: 'Whether to print the raw REST API requests and responses'
12
11
 
13
12
  desc 'orders [SUBCOMAND=list ...]', 'Command for working with orders'
@@ -34,8 +33,6 @@ module IGMarkets
34
33
 
35
34
  RequestPrinter.enabled = true if options[:print_requests]
36
35
 
37
- dealing_platform.account_time_zone = options[:account_time_zone]
38
-
39
36
  dealing_platform.sign_in options[:username], options[:password], options[:api_key], platform
40
37
 
41
38
  yield dealing_platform
@@ -125,7 +122,7 @@ END
125
122
  #
126
123
  # @return [void]
127
124
  def bootstrap(argv)
128
- prepend_config_file_arguments argv
125
+ config_file.prepend_arguments_to_argv argv
129
126
 
130
127
  if argv.index('--version') || argv.index('-v')
131
128
  puts VERSION
@@ -135,21 +132,11 @@ END
135
132
  start argv
136
133
  end
137
134
 
138
- # Searches for a config file and if found inserts its arguments into the passed arguments array.
139
- #
140
- # @param [Array<String>] argv The array of command-line arguments.
135
+ # Returns the config file to use for this invocation.
141
136
  #
142
- # @return [void]
143
- def prepend_config_file_arguments(argv)
144
- config_file = ConfigFile.find
145
-
146
- return unless config_file
147
-
148
- insert_index = argv.index do |argument|
149
- argument[0] == '-'
150
- end || -1
151
-
152
- argv.insert insert_index, *config_file.arguments
137
+ # @return [ConfigFile]
138
+ def config_file
139
+ ConfigFile.find "#{Dir.pwd}/.ig_markets", "#{Dir.home}/.ig_markets"
153
140
  end
154
141
  end
155
142
  end
@@ -9,15 +9,15 @@ module IGMarkets
9
9
  end
10
10
 
11
11
  def headings
12
- %w(Date Time Channel Type Status EPIC Market Size Level Limit Stop Result)
12
+ %w(Date Channel Type Status EPIC Market Size Level Limit Stop Result)
13
13
  end
14
14
 
15
15
  def right_aligned_columns
16
- [7, 8, 9, 10]
16
+ [6, 7, 8, 9]
17
17
  end
18
18
 
19
19
  def row(activity)
20
- [activity.date, activity.time, activity.channel, activity.activity, activity_status(activity), activity.epic,
20
+ [activity.date, activity.channel, activity.activity, activity_status(activity), activity.epic,
21
21
  activity.market_name, activity.size, activity.level, activity.limit, activity.stop, activity.result]
22
22
  end
23
23
 
@@ -13,7 +13,7 @@ module IGMarkets
13
13
  end
14
14
 
15
15
  def row(snapshot)
16
- [snapshot.snapshot_time, format_price(snapshot.open_price), format_price(snapshot.close_price),
16
+ [snapshot.snapshot_time_utc, format_price(snapshot.open_price), format_price(snapshot.close_price),
17
17
  format_price(snapshot.low_price), format_price(snapshot.high_price)]
18
18
  end
19
19
 
@@ -66,7 +66,7 @@ module IGMarkets
66
66
  return format_boolean(value) if value.is_a?(TrueClass) || value.is_a?(FalseClass)
67
67
  return format_float(value) if value.is_a? Float
68
68
  return format_time(value) if value.is_a? Time
69
- return format_date(value) if value.is_a? Date
69
+ return format_symbol(value) if value.is_a? Symbol
70
70
 
71
71
  format_string value
72
72
  end
@@ -83,16 +83,12 @@ module IGMarkets
83
83
  value.localtime.strftime '%F %T %Z'
84
84
  end
85
85
 
86
- def format_date(value)
87
- value.strftime '%F'
86
+ def format_symbol(value)
87
+ Format.symbol value
88
88
  end
89
89
 
90
90
  def format_string(value)
91
- value = if value.is_a? Symbol
92
- Format.symbol value
93
- else
94
- value.to_s
95
- end
91
+ value = value.to_s
96
92
 
97
93
  return '' if value.empty?
98
94
 
@@ -17,7 +17,7 @@ module IGMarkets
17
17
  end
18
18
 
19
19
  def row(transaction)
20
- [transaction.date, transaction.reference, formatted_type(transaction.transaction_type),
20
+ [transaction.date_utc, transaction.reference, formatted_type(transaction.transaction_type),
21
21
  transaction.instrument_name, transaction.size, Format.level(transaction.open_level),
22
22
  Format.level(transaction.close_level),
23
23
  Format.currency(transaction.profit_and_loss_amount, transaction.currency)]
@@ -38,11 +38,6 @@ module IGMarkets
38
38
  # @return [WorkingOrderMethods] Methods for working with working orders.
39
39
  attr_reader :working_orders
40
40
 
41
- # @return [String] The time zone of the account, e.g. `'+1000'` or `'-0800'`. This is required in order for certain
42
- # dates and times reported by this library to be correct, due to the fact that the IG Markets API does not reliably
43
- # report time zone details in all attributes. Defaults to `'+0000'`.
44
- attr_accessor :account_time_zone
45
-
46
41
  def initialize
47
42
  @session = Session.new
48
43
 
@@ -53,8 +48,6 @@ module IGMarkets
53
48
  @sprint_market_positions = SprintMarketPositionMethods.new self
54
49
  @watchlists = WatchlistMethods.new self
55
50
  @working_orders = WorkingOrderMethods.new self
56
-
57
- @account_time_zone = '+0000'
58
51
  end
59
52
 
60
53
  # Signs in to the IG Markets Dealing Platform, either the production platform or the demo platform.
@@ -18,91 +18,67 @@ module IGMarkets
18
18
  @dealing_platform.instantiate_models Account, result
19
19
  end
20
20
 
21
- # Returns all account activities that occurred in the specified date range.
21
+ # Returns activities for this account, either the most recent activities by specifying the `:days` option, or
22
+ # those from a date range by specifying the `:from` and `:to` options.
22
23
  #
23
- # @param [Date] from_date The start date of the desired date range.
24
- # @param [Date] to_date The end date of the desired date range.
24
+ # @param [Hash] options The options hash.
25
+ # @option options [Float] :days The number of recent days to return activities for. If this is specified then the
26
+ # `:from` and `:to` options must not be specified.
27
+ # @option options [Date] :from The start of the period to return activities for.
28
+ # @option options [Date] :to The end of the period to return activities for.
25
29
  #
26
30
  # @return [Array<Activity>]
27
- def activities_in_date_range(from_date, to_date)
28
- from_date = format_date from_date
29
- to_date = format_date to_date
31
+ def activities(options)
32
+ parse_history_options options
30
33
 
31
- result = @dealing_platform.session.get("history/activity/#{from_date}/#{to_date}").fetch :activities
34
+ result = history_request('history/activity', options)
32
35
 
33
- @dealing_platform.instantiate_models Activity, result
36
+ @dealing_platform.instantiate_models Activity, result.fetch(:activities)
34
37
  end
35
38
 
36
- # Returns all account activities that occurred in the most recent specified number of days.
39
+ # Returns transactions for this account, either the most recent transactions by specifying the `:days` option, or
40
+ # those from a date range by specifying the `:from` and `:to` options.
37
41
  #
38
- # @param [Fixnum, Float] days The number of days to return recent activities for.
42
+ # @param [Hash] options The options hash.
43
+ # @option options [:all, :all_deal, :deposit, :withdrawal] :type The type of transactions to return. Defaults to
44
+ # `:all`.
45
+ # @option options [Float] :days The number of recent days to return transactions for. If this is specified then
46
+ # the `:from` and `:to` options must not be specified.
47
+ # @option options [Date] :from The start of the period to return transactions for.
48
+ # @option options [Date] :to The end of the period to return transactions for.
39
49
  #
40
50
  # @return [Array<Activity>]
41
- def recent_activities(days)
42
- result = @dealing_platform.session.get("history/activity/#{milliseconds(days)}").fetch :activities
51
+ def transactions(options)
52
+ options[:type] ||= :all
43
53
 
44
- @dealing_platform.instantiate_models Activity, result
45
- end
46
-
47
- # Returns all transactions that occurred in the specified date range.
48
- #
49
- # @param [Date] from_date The start date of the desired date range.
50
- # @param [Date] to_date The end date of the desired date range.
51
- # @param [:all, :all_deal, :deposit, :withdrawal] transaction_type The type of transactions to return.
52
- #
53
- # @return [Array<Transaction>]
54
- def transactions_in_date_range(from_date, to_date, transaction_type = :all)
55
- validate_transaction_type transaction_type
56
-
57
- from_date = format_date from_date
58
- to_date = format_date to_date
59
-
60
- url = "history/transactions/#{transaction_type.to_s.upcase}/#{from_date}/#{to_date}"
61
- result = @dealing_platform.session.get(url).fetch :transactions
62
-
63
- @dealing_platform.instantiate_models Transaction, result
64
- end
54
+ parse_history_options options
65
55
 
66
- # Returns all transactions that occurred in the last specified number of days.
67
- #
68
- # @param [Fixnum, Float] days The number of days to return recent transactions for.
69
- # @param [:all, :all_deal, :deposit, :withdrawal] transaction_type The type of transactions to return.
70
- #
71
- # @return [Array<Transaction>]
72
- def recent_transactions(days, transaction_type = :all)
73
- validate_transaction_type transaction_type
56
+ result = history_request('history/transactions', options)
74
57
 
75
- url = "history/transactions/#{transaction_type.to_s.upcase}/#{milliseconds(days)}"
76
- result = @dealing_platform.session.get(url).fetch :transactions
77
-
78
- @dealing_platform.instantiate_models Transaction, result
58
+ @dealing_platform.instantiate_models Transaction, result.fetch(:transactions)
79
59
  end
80
60
 
81
61
  private
82
62
 
83
- # Validates whether the passed argument is a valid transaction type.
84
- #
85
- # @param [Symbol] type The candidate transaction type to validate.
86
- def validate_transaction_type(type)
87
- raise ArgumentError, 'transaction type is invalid' unless [:all, :all_deal, :deposit, :withdrawal].include? type
88
- end
89
-
90
- # Formats the passed `Date` as a string in the manner needed for building IG Markets URLs.
63
+ # Sends a GET request to the specified URL with the passed options and returns the response.
91
64
  #
92
- # @param [Date] date The date to format.
65
+ # @param [String] url The base URL.
66
+ # @param [Hash] options The options to put with the URL.
93
67
  #
94
- # @return [String]
95
- def format_date(date)
96
- date.strftime '%d-%m-%Y'
68
+ # @return [Hash]
69
+ def history_request(url, options)
70
+ url = "#{url}?#{options.map { |key, value| "#{key}=#{value.to_s.upcase}" }.join '&'}"
71
+
72
+ @dealing_platform.session.get url, API_V2
97
73
  end
98
74
 
99
- # Converts a number of days into a number of milliseconds.
100
- #
101
- # @param [Fixnum, Float] days The number of days.
75
+ # Parses and formats the history options shared by {#activities} and {#transactions}.
102
76
  #
103
- # @return [Fixnum]
104
- def milliseconds(days)
105
- (days.to_f * 24 * 60 * 60 * 1000).to_i
77
+ # @param [Hash] options
78
+ def parse_history_options(options)
79
+ options[:maxSpanSeconds] = (options.delete(:days).to_f * 24 * 60 * 60).to_i if options.key? :days
80
+ options[:from] = options[:from].strftime('%F') if options.key? :from
81
+ options[:to] = options[:to].strftime('%F') if options.key? :to
106
82
  end
107
83
  end
108
84
  end
@@ -13,7 +13,7 @@ module IGMarkets
13
13
  #
14
14
  # @return [Array<SprintMarketPosition>]
15
15
  def all
16
- result = @dealing_platform.session.get('positions/sprintmarkets').fetch :sprint_market_positions
16
+ result = @dealing_platform.session.get('positions/sprintmarkets', API_V2).fetch :sprint_market_positions
17
17
 
18
18
  @dealing_platform.instantiate_models SprintMarketPosition, result
19
19
  end
@@ -1,33 +1,48 @@
1
1
  module IGMarkets
2
- # Contains details on a historical price query result. Returned by {Market#recent_prices} and
3
- # {Market#prices_in_date_range}.
2
+ # Contains details on a historical price query result. Returned by {Market#historical_prices}.
4
3
  class HistoricalPriceResult < Model
5
- # Contains details on the remaining allowance for looking up historical prices. Used by {#allowance}.
6
- class DataAllowance < Model
7
- attribute :allowance_expiry, Fixnum
8
- attribute :remaining_allowance, Fixnum
9
- attribute :total_allowance, Fixnum
10
- end
4
+ # Contains metadata associated with an historical price lookup. Used by {#metadata}.
5
+ class Metadata < Model
6
+ # Contains details on the remaining allowance for looking up historical prices. Used by {#allowance}.
7
+ class Allowance < Model
8
+ attribute :allowance_expiry, Fixnum
9
+ attribute :remaining_allowance, Fixnum
10
+ attribute :total_allowance, Fixnum
11
+ end
12
+
13
+ # Contains details on paging status for a historical price lookup. Used by {#page_data}.
14
+ class PageData < Model
15
+ attribute :page_number, Fixnum
16
+ attribute :page_size, Fixnum
17
+ attribute :total_pages, Fixnum
18
+ end
11
19
 
12
- # Contains details on a single historical price. Used by {Snapshot}.
13
- class Price < Model
14
- attribute :ask, Float
15
- attribute :bid, Float
16
- attribute :last_traded, Float
20
+ attribute :allowance, Allowance
21
+ attribute :page_data, PageData
22
+ attribute :size, Fixnum
17
23
  end
18
24
 
19
25
  # Contains details on a single historical price snapshot. Used by {#prices}.
20
26
  class Snapshot < Model
27
+ # Contains details on a single historical price.
28
+ class Price < Model
29
+ attribute :ask, Float
30
+ attribute :bid, Float
31
+ attribute :last_traded, Float
32
+ end
33
+
21
34
  attribute :close_price, Price
22
35
  attribute :high_price, Price
23
36
  attribute :last_traded_volume, Float
24
37
  attribute :low_price, Price
25
38
  attribute :open_price, Price
26
- attribute :snapshot_time, Time, format: '%Y/%m/%d %T', time_zone: -> { @dealing_platform.account_time_zone }
39
+ attribute :snapshot_time_utc, Time, format: '%FT%T'
40
+
41
+ deprecated_attribute :snapshot_time
27
42
  end
28
43
 
29
- attribute :allowance, DataAllowance
30
44
  attribute :instrument_type, Symbol, allowed_values: Instrument.allowed_values(:type)
45
+ attribute :metadata, Metadata
31
46
  attribute :prices, Snapshot
32
47
  end
33
48
  end
@@ -12,7 +12,7 @@ module IGMarkets
12
12
 
13
13
  # Contains details on the expiry details of an instrument. Returned by {#expiry_details}.
14
14
  class ExpiryDetails < Model
15
- attribute :last_dealing_date, Time, format: '%FT%R', time_zone: -> { @dealing_platform.account_time_zone }
15
+ attribute :last_dealing_date, Time, format: '%FT%R'
16
16
  attribute :settlement_info
17
17
  end
18
18
 
@@ -43,57 +43,48 @@ module IGMarkets
43
43
  attribute :instrument, Instrument
44
44
  attribute :snapshot, Snapshot
45
45
 
46
- # Returns recent historical prices for this market at a specified resolution.
46
+ # Returns historical prices for this market at a given resolution, either the most recent prices by specifying the
47
+ # `:number` option, or those from a date range by specifying the `:from` and `:to` options.
47
48
  #
48
- # @param [:minute, :minute_2, :minute_3, :minute_5, :minute_10, :minute_15, :minute_30, :hour, :hour_2, :hour_3,
49
- # :hour_4, :day, :week, :month] resolution The resolution of the historical prices to return.
50
- # @param [Fixnum] num_points The number of historical prices to return.
49
+ # @param [Hash] options The options hash.
50
+ # @option options [:minute, :minute_2, :minute_3, :minute_5, :minute_10, :minute_15, :minute_30, :hour, :hour_2,
51
+ # :hour_3, :hour_4, :day, :week, :month] :resolution The resolution of the prices to return.
52
+ # Required.
53
+ # @option options [Fixnum] :number The number of historical prices to return. If this is specified then the `:from`
54
+ # and `:to` options must not be specified.
55
+ # @option options [Time] :from The start of the period to return prices for.
56
+ # @option options [Time] :to The end of the period to return prices for.
51
57
  #
52
58
  # @return [HistoricalPriceResult]
53
- def recent_prices(resolution, num_points)
54
- validate_historical_price_resolution resolution
59
+ def historical_prices(options)
60
+ validate_historical_prices_options options
55
61
 
56
- url = "prices/#{instrument.epic}/#{resolution.to_s.upcase}/#{num_points.to_i}"
62
+ options[:max] = options.delete(:number) if options.key? :number
63
+ options[:from] = options[:from].utc.strftime '%FT%T' if options.key? :from
64
+ options[:to] = options[:to].utc.strftime '%FT%T' if options.key? :to
57
65
 
58
- @dealing_platform.instantiate_models HistoricalPriceResult, @dealing_platform.session.get(url, API_V2)
66
+ @dealing_platform.instantiate_models HistoricalPriceResult, historical_prices_response(options)
59
67
  end
60
68
 
61
- # Returns historical prices for this market at a specified resolution over a specified time period.
62
- #
63
- # @param [:minute, :minute_2, :minute_3, :minute_5, :minute_10, :minute_15, :minute_30, :hour, :hour_2, :hour_3,
64
- # :hour_4, :day, :week, :month] resolution The resolution of the historical prices to return.
65
- # @param [Time] start_time The start of the desired time period.
66
- # @param [Time] end_time The end of the desired time period.
67
- #
68
- # @return [HistoricalPriceResult]
69
- def prices_in_date_range(resolution, start_time, end_time)
70
- validate_historical_price_resolution resolution
69
+ private
71
70
 
72
- start_time = format_time start_time
73
- end_time = format_time end_time
71
+ # Validates the options passed to {#historical_prices}.
72
+ def validate_historical_prices_options(options)
73
+ resolutions = [:second, :minute, :minute_2, :minute_3, :minute_5, :minute_10, :minute_15, :minute_30, :hour,
74
+ :hour_2, :hour_3, :hour_4, :day, :week, :month]
74
75
 
75
- url = "prices/#{instrument.epic}/#{resolution.to_s.upcase}/#{start_time}/#{end_time}"
76
+ raise ArgumentError, 'resolution is invalid' unless resolutions.include? options[:resolution]
76
77
 
77
- @dealing_platform.instantiate_models HistoricalPriceResult, @dealing_platform.session.get(url, API_V2)
78
+ if options.keys != [:resolution, :from, :to] && options.keys != [:resolution, :number]
79
+ raise ArgumentError, 'options must specify either :number or :from and :to'
80
+ end
78
81
  end
79
82
 
80
- private
83
+ # Returns the API response to a request for historical prices.
84
+ def historical_prices_response(options)
85
+ url = "prices/#{instrument.epic}?#{options.map { |key, value| "#{key}=#{value.to_s.upcase}" }.join '&'}"
81
86
 
82
- # Validates whether the passed argument is a valid historical price resolution.
83
- #
84
- # @param [Symbol] resolution The candidate historical price resolution to validate.
85
- def validate_historical_price_resolution(resolution)
86
- resolutions = [:minute, :minute_2, :minute_3, :minute_5, :minute_10, :minute_15, :minute_30, :hour, :hour_2,
87
- :hour_3, :hour_4, :day, :week, :month]
88
-
89
- raise ArgumentError, 'resolution is invalid' unless resolutions.include? resolution
90
- end
91
-
92
- # Takes a `Time` and formats it for the historical prices API URLs.
93
- #
94
- # @param [Time] time The `Time` to format.
95
- def format_time(time)
96
- time.utc.strftime '%F %T'
87
+ @dealing_platform.session.get url, API_V3
97
88
  end
98
89
  end
99
90
  end
@@ -18,7 +18,8 @@ module IGMarkets
18
18
  attribute :percentage_change, Float
19
19
  attribute :scaling_factor, Float
20
20
  attribute :streaming_prices_available, Boolean
21
- attribute :update_time
22
21
  attribute :update_time_utc
22
+
23
+ deprecated_attribute :update_time
23
24
  end
24
25
  end
@@ -11,16 +11,14 @@ module IGMarkets
11
11
  #
12
12
  # @param [Hash] attributes The attribute values to set on this new model.
13
13
  def initialize(attributes = {})
14
- defined_attribute_names = self.class.defined_attribute_names
15
-
16
- defined_attribute_names.each do |name|
14
+ self.class.defined_attribute_names.each do |name|
17
15
  send "#{name}=", attributes[name]
18
16
  end
19
17
 
20
- (attributes.keys - defined_attribute_names).map do |attribute|
21
- value = attributes[attribute]
18
+ attributes.each do |name, value|
19
+ next if respond_to? "#{name}="
22
20
 
23
- raise ArgumentError, "unknown attribute: #{self.class.name}##{attribute}, value: #{inspect_value value}"
21
+ raise ArgumentError, "unknown attribute: #{self.class.name}##{name}, value: #{inspect_value value}"
24
22
  end
25
23
  end
26
24
 
@@ -58,7 +56,7 @@ module IGMarkets
58
56
  # Returns the {#inspect} string for the given value.
59
57
  def inspect_value(value)
60
58
  if value.is_a? Time
61
- value.utc.strftime '%F %T %Z'
59
+ value.localtime.strftime '%F %T %Z'
62
60
  elsif value.is_a? Date
63
61
  value.strftime '%F'
64
62
  else
@@ -107,10 +105,6 @@ module IGMarkets
107
105
  # Optional.
108
106
  # @option options [String] :format When `type` is `Date` or `Time` this specifies the format for parsing String
109
107
  # and `Fixnum` instances assigned to this attribute.
110
- # @option options [String, Proc] :time_zone When `type` is `Time` this specifies the time zone to append to
111
- # `String` values assigned to this attribute prior to parsing them with `:format`. Defaults to
112
- # `+0000` (UTC) unless `:format` is `%Q`. Can be a `Proc` that returns the time zone string to
113
- # use.
114
108
  #
115
109
  # @macro [attach] attribute
116
110
  # The $1 attribute.
@@ -123,6 +117,15 @@ module IGMarkets
123
117
  self.defined_attributes[name] = options.merge type: type
124
118
  end
125
119
 
120
+ # Defines a no-op setter method for each of the passed attribute names. This is used to silently allow deprecated
121
+ # attributes to be set on the model but not have them be otherwise part of the model's structure.
122
+ def deprecated_attribute(*names)
123
+ names.each do |name|
124
+ define_method "#{name}=" do |_value|
125
+ end
126
+ end
127
+ end
128
+
126
129
  private
127
130
 
128
131
  def define_attribute_reader(name)
@@ -135,7 +138,7 @@ module IGMarkets
135
138
  define_method "#{name}=" do |value|
136
139
  value = nil if Array(options.fetch(:nil_if, [])).include? value
137
140
 
138
- value = typecaster.call value, options, self, name
141
+ value = typecaster.call value, options, name
139
142
 
140
143
  allowed_values = options[:allowed_values]
141
144
  if !value.nil? && allowed_values
@@ -8,9 +8,9 @@ module IGMarkets
8
8
  if [Boolean, String, Fixnum, Float, Symbol, Date, Time].include? type
9
9
  method "typecaster_#{type.to_s.gsub(/\AIGMarkets::/, '').downcase}"
10
10
  elsif type
11
- lambda do |value, _options, model, name|
11
+ lambda do |value, _options, name|
12
12
  if Array(value).any? { |entry| !entry.is_a? type }
13
- raise ArgumentError, "incorrect type set on #{model.class}##{name}: #{value.inspect}"
13
+ raise ArgumentError, "incorrect type set on #{self}##{name}: #{value.inspect}"
14
14
  end
15
15
 
16
16
  value
@@ -18,75 +18,76 @@ module IGMarkets
18
18
  end
19
19
  end
20
20
 
21
- def typecaster_boolean(value, _options, _model, _name)
21
+ def typecaster_boolean(value, _options, _name)
22
22
  return value if [nil, true, false].include? value
23
23
 
24
- raise ArgumentError, "#{self}: invalid boolean value: #{value}"
24
+ raise ArgumentError, "#{self}##{name}: invalid boolean value: #{value}"
25
25
  end
26
26
 
27
- def typecaster_string(value, options, _model, _name)
27
+ def typecaster_string(value, options, _name)
28
28
  return nil if value.nil?
29
29
 
30
- if options.key? :regex
31
- raise ArgumentError, "#{self}: invalid string value: #{value}" unless options[:regex].match value.to_s
30
+ if options.key?(:regex) && !options[:regex].match(value.to_s)
31
+ raise ArgumentError, "#{self}##{name}: invalid string value: #{value}"
32
32
  end
33
33
 
34
34
  value.to_s
35
35
  end
36
36
 
37
- def typecaster_fixnum(value, _options, _model, _name)
37
+ def typecaster_fixnum(value, _options, _name)
38
38
  return nil if value.nil?
39
39
 
40
40
  value.to_s.to_i
41
41
  end
42
42
 
43
- def typecaster_float(value, _options, _model, _name)
43
+ def typecaster_float(value, _options, _name)
44
44
  return nil if value.nil? || value == ''
45
45
 
46
46
  Float(value)
47
47
  end
48
48
 
49
- def typecaster_symbol(value, _options, _model, _name)
49
+ def typecaster_symbol(value, _options, _name)
50
50
  return nil if value.nil?
51
51
 
52
52
  value.to_s.downcase.to_sym
53
53
  end
54
54
 
55
- def typecaster_date(value, options, _model, _name)
56
- raise ArgumentError, "#{self}: invalid or missing date format" unless options[:format].is_a? String
55
+ def typecaster_date(value, options, name)
56
+ raise ArgumentError, "#{self}##{name}: invalid or missing date format" unless options[:format].is_a? String
57
57
 
58
58
  if value.is_a? String
59
59
  begin
60
60
  Date.strptime value, options[:format]
61
61
  rescue ArgumentError
62
- raise ArgumentError, "#{self}: failed parsing date '#{value}' with format '#{options[:format]}'"
62
+ raise ArgumentError, "#{self}##{name}: failed parsing date: #{value}"
63
63
  end
64
64
  else
65
65
  value
66
66
  end
67
67
  end
68
68
 
69
- def typecaster_time(value, options, model, name)
70
- raise ArgumentError, "#{self}: invalid or missing time format" unless options[:format].is_a? String
69
+ def typecaster_time(value, options, name)
70
+ raise ArgumentError, "#{self}##{name}: invalid or missing time format" unless options[:format].is_a? String
71
71
 
72
72
  if value.is_a?(String) || value.is_a?(Fixnum)
73
- parse_time_from_string value.to_s, options, model, name
73
+ parse_time_from_string value.to_s, options, name
74
74
  else
75
75
  value
76
76
  end
77
77
  end
78
78
 
79
- def parse_time_from_string(value, options, model, name)
79
+ def parse_time_from_string(value, options, name)
80
80
  format = options[:format]
81
81
 
82
- time_zone = options[:time_zone]
83
- time_zone ||= '+0000' unless format == '%Q'
84
- time_zone = model.instance_exec(&time_zone) if time_zone.is_a? Proc
82
+ unless format == '%Q'
83
+ format += '%z'
84
+ value += '+0000'
85
+ end
85
86
 
86
87
  begin
87
- Time.strptime "#{value}#{time_zone}", "#{format}#{'%z' if time_zone}"
88
+ Time.strptime value, format
88
89
  rescue ArgumentError
89
- raise ArgumentError, "#{self}##{name}: failed parsing time '#{value}' with format '#{format}'"
90
+ raise ArgumentError, "#{self}##{name}: failed parsing time: #{value}"
90
91
  end
91
92
  end
92
93
  end
@@ -4,7 +4,6 @@ module IGMarkets
4
4
  class Position < Model
5
5
  attribute :contract_size, Float
6
6
  attribute :controlled_risk, Boolean
7
- attribute :created_date, Time, format: '%Y/%m/%d %T:%L'
8
7
  attribute :created_date_utc, Time, format: '%FT%T'
9
8
  attribute :currency, String, regex: Regex::CURRENCY
10
9
  attribute :deal_id
@@ -18,6 +17,8 @@ module IGMarkets
18
17
 
19
18
  attribute :market, MarketOverview
20
19
 
20
+ deprecated_attribute :created_date
21
+
21
22
  # Returns whether this position has a trailing stop.
22
23
  def trailing_stop?
23
24
  !trailing_step.nil? && !trailing_stop_distance.nil?
@@ -1,13 +1,13 @@
1
1
  module IGMarkets
2
2
  # Contains details on a sprint market position. Returned by {DealingPlatform::SprintMarketPositionMethods#all}.
3
3
  class SprintMarketPosition < Model
4
- attribute :created_date, Time, format: '%Y/%m/%d %T:%L', time_zone: -> { @dealing_platform.account_time_zone }
4
+ attribute :created_date, Time, format: '%FT%T'
5
5
  attribute :currency, String, regex: Regex::CURRENCY
6
6
  attribute :deal_id
7
7
  attribute :description
8
8
  attribute :direction, Symbol, allowed_values: [:buy, :sell]
9
9
  attribute :epic, String, regex: Regex::EPIC
10
- attribute :expiry_time, Time, format: '%Y/%m/%d %T:%L', time_zone: -> { @dealing_platform.account_time_zone }
10
+ attribute :expiry_time, Time, format: '%FT%T'
11
11
  attribute :instrument_name
12
12
  attribute :market_status, Symbol, allowed_values: Market::Snapshot.allowed_values(:market_status)
13
13
  attribute :payout_amount, Float
@@ -1,20 +1,21 @@
1
1
  module IGMarkets
2
2
  # Contains details on a single transaction that occurred on an IG Markets account. Returned by
3
- # {DealingPlatform::AccountMethods#transactions_in_date_range} and
4
- # {DealingPlatform::AccountMethods#recent_transactions}.
3
+ # {DealingPlatform::AccountMethods#transactions}.
5
4
  class Transaction < Model
6
5
  attribute :cash_transaction, Boolean
7
6
  attribute :close_level, String, nil_if: %w(- 0)
8
7
  attribute :currency
9
- attribute :date, Date, format: '%d/%m/%y'
8
+ attribute :date_utc, Time, format: '%FT%T'
10
9
  attribute :instrument_name
11
10
  attribute :open_level, String, nil_if: %w(- 0)
12
- attribute :period, Time, nil_if: '-', format: '%d/%m/%y %T', time_zone: -> { @dealing_platform.account_time_zone }
11
+ attribute :period, Time, nil_if: '-', format: '%FT%T'
13
12
  attribute :profit_and_loss
14
13
  attribute :reference
15
14
  attribute :size, String, nil_if: '-'
16
15
  attribute :transaction_type, Symbol, allowed_values: [:deal, :depo, :dividend, :exchange, :with]
17
16
 
17
+ deprecated_attribute :date
18
+
18
19
  # Returns whether or not this transaction was an interest payment. Interest payments can be either deposits or
19
20
  # withdrawals depending on the underlying instrument and currencies involved. Interest payments are identified by
20
21
  # the presence of the word `interest` in {#instrument_name}.
@@ -1,4 +1,4 @@
1
1
  module IGMarkets
2
2
  # The version of this gem.
3
- VERSION = '0.6'.freeze
3
+ VERSION = '0.7'.freeze
4
4
  end
@@ -2,7 +2,6 @@ module IGMarkets
2
2
  # Contains details on a working order. Returned by {DealingPlatform::WorkingOrderMethods#all} and
3
3
  # {DealingPlatform::WorkingOrderMethods#[]}.
4
4
  class WorkingOrder < Model
5
- attribute :created_date, Time, format: '%Y/%m/%d %T:%L'
6
5
  attribute :created_date_utc, Time, format: '%FT%T'
7
6
  attribute :currency_code, String, regex: Regex::CURRENCY
8
7
  attribute :deal_id
@@ -10,7 +9,6 @@ module IGMarkets
10
9
  attribute :dma, Boolean
11
10
  attribute :epic, String, regex: Regex::EPIC
12
11
  attribute :good_till_date, Time, format: '%Y/%m/%d %R'
13
- attribute :good_till_date_iso, Time, format: '%FT%R'
14
12
  attribute :guaranteed_stop, Boolean
15
13
  attribute :limit_distance, Fixnum
16
14
  attribute :order_level, Float
@@ -21,6 +19,8 @@ module IGMarkets
21
19
 
22
20
  attribute :market, MarketOverview
23
21
 
22
+ deprecated_attribute :created_date, :good_till_date_iso
23
+
24
24
  # Deletes this working order.
25
25
  #
26
26
  # @return [String] The deal reference of the deletion operation. Use {DealingPlatform#deal_confirmation} to check
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.6'
4
+ version: '0.7'
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-27 00:00:00.000000000 Z
11
+ date: 2016-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.10'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.10'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rest-client
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -198,6 +212,7 @@ files:
198
212
  - lib/ig_markets/cli/commands/account_command.rb
199
213
  - lib/ig_markets/cli/commands/activities_command.rb
200
214
  - lib/ig_markets/cli/commands/confirmation_command.rb
215
+ - lib/ig_markets/cli/commands/console_command.rb
201
216
  - lib/ig_markets/cli/commands/orders_command.rb
202
217
  - lib/ig_markets/cli/commands/positions_command.rb
203
218
  - lib/ig_markets/cli/commands/prices_command.rb