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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -4
- data/README.md +70 -17
- data/lib/ig_markets.rb +27 -15
- data/lib/ig_markets/{account_activity.rb → activity.rb} +1 -1
- data/lib/ig_markets/cli/{account_command.rb → commands/account_command.rb} +5 -4
- data/lib/ig_markets/cli/commands/activities_command.rb +37 -0
- data/lib/ig_markets/cli/commands/confirmation_command.rb +14 -0
- data/lib/ig_markets/cli/{orders_command.rb → commands/orders_command.rb} +16 -21
- data/lib/ig_markets/cli/{positions_command.rb → commands/positions_command.rb} +31 -19
- data/lib/ig_markets/cli/commands/search_command.rb +18 -0
- data/lib/ig_markets/cli/commands/sentiment_command.rb +19 -0
- data/lib/ig_markets/cli/{sprints_command.rb → commands/sprints_command.rb} +10 -8
- data/lib/ig_markets/cli/commands/transactions_command.rb +61 -0
- data/lib/ig_markets/cli/{watchlists_command.rb → commands/watchlists_command.rb} +16 -12
- data/lib/ig_markets/cli/main.rb +65 -15
- data/lib/ig_markets/cli/tables/accounts_table.rb +30 -0
- data/lib/ig_markets/cli/tables/activities_table.rb +29 -0
- data/lib/ig_markets/cli/tables/client_sentiments_table.rb +31 -0
- data/lib/ig_markets/cli/tables/market_overviews_table.rb +47 -0
- data/lib/ig_markets/cli/tables/positions_table.rb +83 -0
- data/lib/ig_markets/cli/tables/sprint_market_positions_table.rb +55 -0
- data/lib/ig_markets/cli/tables/table.rb +103 -0
- data/lib/ig_markets/cli/tables/transactions_table.rb +41 -0
- data/lib/ig_markets/cli/tables/working_orders_table.rb +27 -0
- data/lib/ig_markets/dealing_platform/account_methods.rb +8 -8
- data/lib/ig_markets/dealing_platform/client_sentiment_methods.rb +4 -0
- data/lib/ig_markets/dealing_platform/market_methods.rb +1 -1
- data/lib/ig_markets/format.rb +20 -7
- data/lib/ig_markets/market_overview.rb +1 -1
- data/lib/ig_markets/model.rb +10 -1
- data/lib/ig_markets/model/typecasters.rb +1 -1
- data/lib/ig_markets/position.rb +12 -11
- data/lib/ig_markets/request_printer.rb +56 -0
- data/lib/ig_markets/response_parser.rb +11 -0
- data/lib/ig_markets/session.rb +7 -8
- data/lib/ig_markets/{account_transaction.rb → transaction.rb} +4 -17
- data/lib/ig_markets/version.rb +1 -1
- metadata +54 -16
- data/lib/ig_markets/cli/activities_command.rb +0 -31
- data/lib/ig_markets/cli/confirmation_command.rb +0 -16
- data/lib/ig_markets/cli/output.rb +0 -115
- data/lib/ig_markets/cli/search_command.rb +0 -16
- data/lib/ig_markets/cli/sentiment_command.rb +0 -24
- 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<
|
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,
|
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<
|
36
|
+
# @return [Array<Activity>]
|
37
37
|
def recent_activities(days)
|
38
|
-
@dealing_platform.gather "history/activity/#{milliseconds(days)}", :activities,
|
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<
|
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,
|
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<
|
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,
|
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
|
|
data/lib/ig_markets/format.rb
CHANGED
@@ -3,18 +3,31 @@ module IGMarkets
|
|
3
3
|
module Format
|
4
4
|
module_function
|
5
5
|
|
6
|
-
# Returns a formatted string for the specified
|
7
|
-
#
|
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]
|
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,
|
14
|
-
|
15
|
-
|
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
|
-
"#{
|
30
|
+
"#{currency} #{format '%.2f', amount.to_f}"
|
18
31
|
end
|
19
32
|
end
|
20
33
|
|