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
@@ -0,0 +1,47 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Helper class that prints out an array of {IGMarkets::MarketOverview} instances in a table.
4
+ class MarketOverviewsTable < Table
5
+ private
6
+
7
+ def default_title
8
+ 'Markets'
9
+ end
10
+
11
+ def headings
12
+ ['Type', 'EPIC', 'Instrument', 'Status', 'Expiry', 'Bid', 'Offer', 'High', 'Low', 'Change (net)', 'Change (%)']
13
+ end
14
+
15
+ def right_aligned_columns
16
+ [5, 6, 7, 8, 9, 10]
17
+ end
18
+
19
+ def row(market_overview)
20
+ [market_overview.instrument_type, market_overview.epic, market_overview.instrument_name,
21
+ market_status(market_overview), market_overview.expiry, levels(market_overview)]
22
+ end
23
+
24
+ def cell_color(value, _model, _row_index, column_index)
25
+ return unless headings[column_index] =~ /Change/
26
+
27
+ if value =~ /-/
28
+ :red
29
+ else
30
+ :green
31
+ end
32
+ end
33
+
34
+ def levels(market_overview)
35
+ [:bid, :offer, :high, :low, :net_change, :percentage_change].map do |attribute|
36
+ Format.level market_overview.send(attribute)
37
+ end
38
+ end
39
+
40
+ def market_status(market_overview)
41
+ { closed: 'Closed', edits_only: 'Edits only', offline: 'Offline', on_auction: 'On auction',
42
+ on_auction_no_edits: 'On auction no edits', suspended: 'Suspended', tradeable: 'Tradeable'
43
+ }.fetch market_overview.market_status
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,83 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Helper class that prints out an array of {IGMarkets::Position} instances in a table. Positions that share an EPIC
4
+ # can be aggregated into one row in the table if desired.
5
+ class PositionsTable < Table
6
+ def initialize(models, options = {})
7
+ models = aggregated_positions(models) if options[:aggregate]
8
+
9
+ super models, options
10
+ end
11
+
12
+ private
13
+
14
+ def default_title
15
+ 'Positions'
16
+ end
17
+
18
+ def headings
19
+ ['Date', 'EPIC', 'Type', 'Direction', 'Size', 'Level', 'Current', 'Limit', 'Stop', 'Profit/loss', 'Deal IDs']
20
+ end
21
+
22
+ def right_aligned_columns
23
+ [4, 5, 6, 7, 8, 9]
24
+ end
25
+
26
+ def row(position)
27
+ [position.created_date_utc, position.market.epic, position.market.instrument_type, position.direction,
28
+ position.size, position_prices(position), profit_loss(position), position.deal_id]
29
+ end
30
+
31
+ def cell_color(value, _model, _row_index, column_index)
32
+ return unless headings[column_index] == 'Profit/loss'
33
+
34
+ if value =~ /-/
35
+ :red
36
+ else
37
+ :green
38
+ end
39
+ end
40
+
41
+ def position_prices(position)
42
+ [:level, :close_level, :limit_level, :stop_level].map do |attribute|
43
+ Format.level position.send(attribute)
44
+ end
45
+ end
46
+
47
+ def profit_loss(position)
48
+ Format.currency position.profit_loss, position.currency
49
+ end
50
+
51
+ def aggregated_positions(positions)
52
+ grouped = positions.group_by do |position|
53
+ position.market.epic
54
+ end
55
+
56
+ grouped.map do |_, group|
57
+ group.size > 1 ? combine_positions(group) : group.first
58
+ end
59
+ end
60
+
61
+ def combine_positions(positions)
62
+ first = positions.first
63
+
64
+ Position.new(contract_size: first.contract_size, currency: first.currency,
65
+ deal_id: combine_position_deal_ids(positions), direction: first.direction,
66
+ level: combine_position_levels(positions), market: first.market,
67
+ size: positions.map(&:size).reduce(:+)).tap do |combined|
68
+ combined.level /= combined.size
69
+ end
70
+ end
71
+
72
+ def combine_position_levels(positions)
73
+ positions.map do |position|
74
+ position.level * position.size
75
+ end.reduce :+
76
+ end
77
+
78
+ def combine_position_deal_ids(positions)
79
+ positions.map(&:deal_id).join ', '
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,55 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Helper class that prints out an array of {IGMarkets::SprintMarketPosition} instances in a table.
4
+ class SprintMarketPositionsTable < Table
5
+ def initialize(sprints, options = {})
6
+ super
7
+
8
+ @markets = options.fetch :markets
9
+ end
10
+
11
+ private
12
+
13
+ def default_title
14
+ 'Sprint market positions'
15
+ end
16
+
17
+ def headings
18
+ ['EPIC', 'Direction', 'Size', 'Strike level', 'Current', 'Expires in (m:ss)', 'Payout', 'Deal ID']
19
+ end
20
+
21
+ def right_aligned_columns
22
+ [2, 3, 4, 5]
23
+ end
24
+
25
+ def row(sprint)
26
+ [sprint.epic, sprint.direction, Format.currency(sprint.size, sprint.currency), levels(sprint),
27
+ Format.seconds(sprint.seconds_till_expiry), Format.currency(sprint.payout_amount, sprint.currency),
28
+ sprint.deal_id]
29
+ end
30
+
31
+ def cell_color(_value, sprint, _row_index, column_index)
32
+ return unless headings[column_index] == 'Payout'
33
+
34
+ if current_level(sprint) > sprint.strike_level && sprint.direction == :buy ||
35
+ current_level(sprint) < sprint.strike_level && sprint.direction == :sell
36
+ :green
37
+ else
38
+ :red
39
+ end
40
+ end
41
+
42
+ def levels(sprint)
43
+ [Format.level(sprint.strike_level), Format.level(current_level(sprint))]
44
+ end
45
+
46
+ def current_level(sprint)
47
+ snapshot = @markets.detect do |market|
48
+ market.instrument.epic == sprint.epic
49
+ end.snapshot
50
+
51
+ (snapshot.bid + snapshot.offer) / 2.0
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,103 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Base class with shared table setup and display methods. Used to print output tables in the command-line client.
4
+ class Table
5
+ # Initializer that takes the array of models to display in this table.
6
+ #
7
+ # @param [Array] models The array of models.
8
+ # @param [Hash] options The options hash.
9
+ # @option options [String] :title The title for this table.
10
+ def initialize(models, options = {})
11
+ @models = models
12
+ @title = options[:title] || default_title
13
+ end
14
+
15
+ # Converts this table into a formatted string.
16
+ def to_s
17
+ table.to_s
18
+ end
19
+
20
+ private
21
+
22
+ def default_title
23
+ end
24
+
25
+ def headings
26
+ end
27
+
28
+ def right_aligned_columns
29
+ end
30
+
31
+ def row(_model)
32
+ end
33
+
34
+ def cell_color(_value, _model, _row_index, _column_index)
35
+ end
36
+
37
+ def table
38
+ Terminal::Table.new(title: @title, headings: headings, rows: rows).tap do |t|
39
+ Array(right_aligned_columns).each do |column|
40
+ t.align_column column, :right
41
+ end
42
+ end
43
+ end
44
+
45
+ def rows
46
+ @models.flatten.each_with_index.map do |model, row_index|
47
+ if model == :separator
48
+ :separator
49
+ else
50
+ row(model).flatten.each_with_index.map do |value, column_index|
51
+ cell_content value, model, row_index, column_index
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def cell_content(value, model, row_index, column_index)
58
+ color = cell_color value, model, row_index, column_index
59
+
60
+ content = format_cell_value(value).strip
61
+
62
+ color ? content.colorize(color) : content
63
+ end
64
+
65
+ def format_cell_value(value)
66
+ return format_boolean(value) if value.is_a?(TrueClass) || value.is_a?(FalseClass)
67
+ return format_float(value) if value.is_a? Float
68
+ return format_time(value) if value.is_a? Time
69
+ return format_date(value) if value.is_a? Date
70
+
71
+ format_string value
72
+ end
73
+
74
+ def format_boolean(value)
75
+ { true => 'Yes', false => 'No' }.fetch value
76
+ end
77
+
78
+ def format_float(value)
79
+ format '%g', value
80
+ end
81
+
82
+ def format_time(value)
83
+ value.utc.strftime '%F %T %Z'
84
+ end
85
+
86
+ def format_date(value)
87
+ value.strftime '%F'
88
+ end
89
+
90
+ def format_string(value)
91
+ value = if value.is_a? Symbol
92
+ value.to_s.tr '_', ' '
93
+ else
94
+ value.to_s
95
+ end
96
+
97
+ return '' if value.empty?
98
+
99
+ value[0].upcase + value[1..-1]
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,41 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Helper class that prints out an array of {IGMarkets::Transaction} instances in a table.
4
+ class TransactionsTable < Table
5
+ private
6
+
7
+ def default_title
8
+ 'Transactions'
9
+ end
10
+
11
+ def headings
12
+ ['Date', 'Reference', 'Type', 'Instrument', 'Size', 'Open', 'Close', 'Profit/loss']
13
+ end
14
+
15
+ def right_aligned_columns
16
+ [4, 5, 6, 7]
17
+ end
18
+
19
+ def row(transaction)
20
+ [transaction.date, transaction.reference, formatted_type(transaction.transaction_type),
21
+ transaction.instrument_name, transaction.size, Format.level(transaction.open_level),
22
+ Format.level(transaction.close_level),
23
+ Format.currency(transaction.profit_and_loss_amount, transaction.currency)]
24
+ end
25
+
26
+ def cell_color(value, _transaction, _row_index, column_index)
27
+ return unless headings[column_index] == 'Profit/loss'
28
+
29
+ if value =~ /-/
30
+ :red
31
+ else
32
+ :green
33
+ end
34
+ end
35
+
36
+ def formatted_type(type)
37
+ { deal: 'Deal', depo: 'Deposit', dividend: 'Dividend', exchange: 'Exchange', with: 'Withdrawal' }.fetch type
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Helper class that prints out an array of {IGMarkets::WorkingOrder} instances in a table.
4
+ class WorkingOrdersTable < Table
5
+ private
6
+
7
+ def default_title
8
+ 'Working orders'
9
+ end
10
+
11
+ def headings
12
+ ['Created date', 'EPIC', 'Currency', 'Type', 'Direction', 'Size', 'Level', 'Limit distance', 'Stop distance',
13
+ 'Good till date', 'Deal ID']
14
+ end
15
+
16
+ def right_aligned_columns
17
+ [5, 6, 7, 8]
18
+ end
19
+
20
+ def row(working_order)
21
+ [working_order.created_date_utc, working_order.epic, working_order.currency_code, working_order.order_type,
22
+ working_order.direction, working_order.order_size, Format.level(working_order.order_level),
23
+ working_order.limit_distance, working_order.stop_distance, working_order.good_till_date, working_order.deal_id]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -21,21 +21,21 @@ module IGMarkets
21
21
  # @param [Date] from_date The start date of the desired date range.
22
22
  # @param [Date] to_date The end date of the desired date range.
23
23
  #
24
- # @return [Array<AccountActivity>]
24
+ # @return [Array<Activity>]
25
25
  def activities_in_date_range(from_date, to_date)
26
26
  from_date = format_date from_date
27
27
  to_date = format_date to_date
28
28
 
29
- @dealing_platform.gather "history/activity/#{from_date}/#{to_date}", :activities, AccountActivity
29
+ @dealing_platform.gather "history/activity/#{from_date}/#{to_date}", :activities, Activity
30
30
  end
31
31
 
32
32
  # Returns all account activities that occurred in the most recent specified number of days.
33
33
  #
34
34
  # @param [Fixnum, Float] days The number of days to return recent activities for.
35
35
  #
36
- # @return [Array<AccountActivity>]
36
+ # @return [Array<Activity>]
37
37
  def recent_activities(days)
38
- @dealing_platform.gather "history/activity/#{milliseconds(days)}", :activities, AccountActivity
38
+ @dealing_platform.gather "history/activity/#{milliseconds(days)}", :activities, Activity
39
39
  end
40
40
 
41
41
  # Returns all transactions that occurred in the specified date range.
@@ -44,7 +44,7 @@ module IGMarkets
44
44
  # @param [Date] to_date The end date of the desired date range.
45
45
  # @param [:all, :all_deal, :deposit, :withdrawal] transaction_type The type of transactions to return.
46
46
  #
47
- # @return [Array<AccountTransaction>]
47
+ # @return [Array<Transaction>]
48
48
  def transactions_in_date_range(from_date, to_date, transaction_type = :all)
49
49
  validate_transaction_type transaction_type
50
50
 
@@ -53,7 +53,7 @@ module IGMarkets
53
53
 
54
54
  url = "history/transactions/#{transaction_type.to_s.upcase}/#{from_date}/#{to_date}"
55
55
 
56
- @dealing_platform.gather url, :transactions, AccountTransaction
56
+ @dealing_platform.gather url, :transactions, Transaction
57
57
  end
58
58
 
59
59
  # Returns all transactions that occurred in the last specified number of days.
@@ -61,13 +61,13 @@ module IGMarkets
61
61
  # @param [Fixnum, Float] days The number of days to return recent transactions for.
62
62
  # @param [:all, :all_deal, :deposit, :withdrawal] transaction_type The type of transactions to return.
63
63
  #
64
- # @return [Array<AccountTransaction>]
64
+ # @return [Array<Transaction>]
65
65
  def recent_transactions(days, transaction_type = :all)
66
66
  validate_transaction_type transaction_type
67
67
 
68
68
  url = "history/transactions/#{transaction_type.to_s.upcase}/#{milliseconds(days)}"
69
69
 
70
- @dealing_platform.gather url, :transactions, AccountTransaction
70
+ @dealing_platform.gather url, :transactions, Transaction
71
71
  end
72
72
 
73
73
  private
@@ -18,6 +18,10 @@ module IGMarkets
18
18
  result = @dealing_platform.session.get "clientsentiment/#{market_id}", API_V1
19
19
 
20
20
  ClientSentiment.from(result).tap do |client_sentiment|
21
+ if client_sentiment.long_position_percentage == 0.0 && client_sentiment.short_position_percentage == 0.0
22
+ raise ArgumentError, "unknown market '#{market_id}'"
23
+ end
24
+
21
25
  client_sentiment.instance_variable_set :@dealing_platform, @dealing_platform
22
26
  end
23
27
  end
@@ -29,7 +29,7 @@ module IGMarkets
29
29
  def find(*epics)
30
30
  raise ArgumentError, 'at least one EPIC must be specified' if epics.empty?
31
31
 
32
- epics.each do |epic|
32
+ epics.flatten.each do |epic|
33
33
  raise ArgumentError, "invalid EPIC: #{epic}" unless epic.to_s =~ Regex::EPIC
34
34
  end
35
35
 
@@ -3,18 +3,31 @@ module IGMarkets
3
3
  module Format
4
4
  module_function
5
5
 
6
- # Returns a formatted string for the specified currency amount and currency symbol. Two decimal places are used for
7
- # all currencies except the Japanese Yen.
6
+ # Returns a formatted string for the specified level. At most four decimal places are used to format levels.
7
+ #
8
+ # @param [Float] value The level to format.
9
+ #
10
+ # @return [String] The formatted level, e.g. `"-130.4055"`
11
+ def level(value)
12
+ return '' unless value
13
+
14
+ Float(format('%.4f', value.to_f)).to_s
15
+ end
16
+
17
+ # Returns a formatted string for the specified currency amount and currency. Two decimal places are used for all
18
+ # currencies except the Japanese Yen.
8
19
  #
9
20
  # @param [Float, Fixnum] amount The currency amount to format.
10
- # @param [String] symbol The currency symbol.
21
+ # @param [String] currency The currency.
11
22
  #
12
23
  # @return [String] The formatted currency amount, e.g. `"USD -130.40"`, `"AUD 539.10"`, `"JPY 3560"`.
13
- def currency(amount, symbol)
14
- if ['JPY', '¥'].include? symbol
15
- "#{symbol} #{format '%i', amount.to_i}"
24
+ def currency(amount, currency)
25
+ return '' unless amount
26
+
27
+ if ['JPY', '¥'].include? currency
28
+ "#{currency} #{format '%i', amount.to_i}"
16
29
  else
17
- "#{symbol} #{format '%.2f', amount.to_f}"
30
+ "#{currency} #{format '%.2f', amount.to_f}"
18
31
  end
19
32
  end
20
33