ig_markets 0.1
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 +7 -0
- data/.codeclimate.yml +15 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +10 -0
- data/.yardopts +4 -0
- data/Gemfile +2 -0
- data/LICENSE.md +25 -0
- data/README.md +134 -0
- data/ig_markets.gemspec +28 -0
- data/lib/ig_markets.rb +42 -0
- data/lib/ig_markets/account.rb +23 -0
- data/lib/ig_markets/account_activity.rb +24 -0
- data/lib/ig_markets/account_transaction.rb +49 -0
- data/lib/ig_markets/api_versions.rb +10 -0
- data/lib/ig_markets/application.rb +22 -0
- data/lib/ig_markets/boolean.rb +5 -0
- data/lib/ig_markets/client_sentiment.rb +16 -0
- data/lib/ig_markets/deal_confirmation.rb +41 -0
- data/lib/ig_markets/dealing_platform.rb +105 -0
- data/lib/ig_markets/dealing_platform/account_methods.rb +92 -0
- data/lib/ig_markets/dealing_platform/client_sentiment_methods.rb +26 -0
- data/lib/ig_markets/dealing_platform/market_methods.rb +59 -0
- data/lib/ig_markets/dealing_platform/position_methods.rb +164 -0
- data/lib/ig_markets/dealing_platform/sprint_market_position_methods.rb +46 -0
- data/lib/ig_markets/dealing_platform/watchlist_methods.rb +42 -0
- data/lib/ig_markets/dealing_platform/working_order_methods.rb +115 -0
- data/lib/ig_markets/historical_price_result.rb +33 -0
- data/lib/ig_markets/instrument.rb +89 -0
- data/lib/ig_markets/market.rb +99 -0
- data/lib/ig_markets/market_hierarchy_result.rb +13 -0
- data/lib/ig_markets/market_overview.rb +24 -0
- data/lib/ig_markets/model.rb +185 -0
- data/lib/ig_markets/password_encryptor.rb +31 -0
- data/lib/ig_markets/payload_formatter.rb +38 -0
- data/lib/ig_markets/position.rb +191 -0
- data/lib/ig_markets/regex.rb +10 -0
- data/lib/ig_markets/request_failed_error.rb +21 -0
- data/lib/ig_markets/response_parser.rb +35 -0
- data/lib/ig_markets/session.rb +186 -0
- data/lib/ig_markets/sprint_market_position.rb +17 -0
- data/lib/ig_markets/version.rb +4 -0
- data/lib/ig_markets/watchlist.rb +37 -0
- data/lib/ig_markets/working_order.rb +68 -0
- data/spec/factories/ig_markets/account.rb +14 -0
- data/spec/factories/ig_markets/account_activity.rb +21 -0
- data/spec/factories/ig_markets/account_balance.rb +8 -0
- data/spec/factories/ig_markets/account_transaction.rb +15 -0
- data/spec/factories/ig_markets/application.rb +21 -0
- data/spec/factories/ig_markets/client_sentiment.rb +7 -0
- data/spec/factories/ig_markets/deal_confirmation.rb +20 -0
- data/spec/factories/ig_markets/historical_price_result.rb +7 -0
- data/spec/factories/ig_markets/historical_price_result_data_allowance.rb +7 -0
- data/spec/factories/ig_markets/historical_price_result_price.rb +7 -0
- data/spec/factories/ig_markets/historical_price_result_snapshot.rb +10 -0
- data/spec/factories/ig_markets/instrument.rb +32 -0
- data/spec/factories/ig_markets/instrument_currency.rb +9 -0
- data/spec/factories/ig_markets/instrument_expiry_details.rb +6 -0
- data/spec/factories/ig_markets/instrument_margin_deposit_band.rb +8 -0
- data/spec/factories/ig_markets/instrument_opening_hours.rb +6 -0
- data/spec/factories/ig_markets/instrument_rollover_details.rb +6 -0
- data/spec/factories/ig_markets/instrument_slippage_factor.rb +6 -0
- data/spec/factories/ig_markets/market.rb +7 -0
- data/spec/factories/ig_markets/market_dealing_rules.rb +11 -0
- data/spec/factories/ig_markets/market_dealing_rules_rule_details.rb +6 -0
- data/spec/factories/ig_markets/market_hierarchy_result.rb +6 -0
- data/spec/factories/ig_markets/market_hierarchy_result_hierarchy_node.rb +6 -0
- data/spec/factories/ig_markets/market_overview.rb +22 -0
- data/spec/factories/ig_markets/market_snapshot.rb +17 -0
- data/spec/factories/ig_markets/position.rb +19 -0
- data/spec/factories/ig_markets/sprint_market_position.rb +16 -0
- data/spec/factories/ig_markets/watchlist.rb +9 -0
- data/spec/factories/ig_markets/working_order.rb +21 -0
- data/spec/ig_markets/account_transaction_spec.rb +30 -0
- data/spec/ig_markets/dealing_platform/account_methods_spec.rb +58 -0
- data/spec/ig_markets/dealing_platform/client_sentiment_methods_spec.rb +29 -0
- data/spec/ig_markets/dealing_platform/market_methods_spec.rb +80 -0
- data/spec/ig_markets/dealing_platform/position_methods_spec.rb +137 -0
- data/spec/ig_markets/dealing_platform/sprint_market_position_methods_spec.rb +39 -0
- data/spec/ig_markets/dealing_platform/watchlist_methods_spec.rb +89 -0
- data/spec/ig_markets/dealing_platform/working_order_methods_spec.rb +120 -0
- data/spec/ig_markets/dealing_platform_spec.rb +40 -0
- data/spec/ig_markets/model_spec.rb +127 -0
- data/spec/ig_markets/password_encryptor_spec.rb +23 -0
- data/spec/ig_markets/payload_formatter_spec.rb +19 -0
- data/spec/ig_markets/position_spec.rb +37 -0
- data/spec/ig_markets/response_parser_spec.rb +13 -0
- data/spec/ig_markets/session_spec.rb +134 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/factory_girl.rb +7 -0
- data/spec/support/random_test_order.rb +3 -0
- metadata +261 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
module IGMarkets
|
2
|
+
# Contains details on client sentiment for a single market. Returned by {DealingPlatform::ClientSentimentMethods#[]}
|
3
|
+
# and {#related_sentiments}.
|
4
|
+
class ClientSentiment < Model
|
5
|
+
attribute :long_position_percentage, Float
|
6
|
+
attribute :market_id
|
7
|
+
attribute :short_position_percentage, Float
|
8
|
+
|
9
|
+
# Returns client sentiments for markets that are related to this one.
|
10
|
+
#
|
11
|
+
# @return [Array<ClientSentiment>]
|
12
|
+
def related_sentiments
|
13
|
+
@dealing_platform.gather "clientsentiment/related/#{market_id}", :client_sentiments, ClientSentiment
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module IGMarkets
|
2
|
+
# Contains details on a single dealing event. Returned by {DealingPlatform#deal_confirmation}.
|
3
|
+
class DealConfirmation < Model
|
4
|
+
# Contains details on a specific deal that was affected by a dealing event. Returned by {#affected_deals}.
|
5
|
+
class AffectedDeal < Model
|
6
|
+
attribute :deal_id
|
7
|
+
attribute :status, Symbol, allowed_values: [:amended, :deleted, :fully_closed, :opened, :partially_closed]
|
8
|
+
end
|
9
|
+
|
10
|
+
attribute :affected_deals, AffectedDeal
|
11
|
+
attribute :deal_id
|
12
|
+
attribute :deal_reference
|
13
|
+
attribute :deal_status, Symbol, allowed_values: [:accepted, :fund_account, :rejected]
|
14
|
+
attribute :direction, Symbol, allowed_values: [:buy, :sell]
|
15
|
+
attribute :epic
|
16
|
+
attribute :expiry, DateTime, nil_if: '-', format: '%d-%b-%y'
|
17
|
+
attribute :guaranteed_stop, Boolean
|
18
|
+
attribute :level, Float
|
19
|
+
attribute :limit_distance, Float
|
20
|
+
attribute :limit_level, Float
|
21
|
+
attribute :reason, Symbol, allowed_values: [
|
22
|
+
:account_not_enabled_to_trading, :attached_order_level_error, :attached_order_trailing_stop_error,
|
23
|
+
:cannot_change_stop_type, :cannot_remove_stop, :closing_only_trades_accepted_on_this_market, :conflicting_order,
|
24
|
+
:cr_spacing, :duplicate_order_error, :exchange_manual_override, :finance_repeat_dealing,
|
25
|
+
:force_open_on_same_market_different_currency, :general_error, :good_till_date_in_the_past, :instrument_not_found,
|
26
|
+
:insufficient_funds, :level_tolerance_error, :manual_order_timeout, :market_closed, :market_closed_with_edits,
|
27
|
+
:market_closing, :market_not_borrowable, :market_offline, :market_phone_only, :market_rolled,
|
28
|
+
:market_unavailable_to_client, :max_auto_size_exceeded, :minimum_order_size_error, :move_away_only_limit,
|
29
|
+
:move_away_only_stop, :move_away_only_trigger_level, :opposing_direction_orders_not_allowed,
|
30
|
+
:opposing_positions_not_allowed, :order_locked, :order_not_found, :over_normal_market_size,
|
31
|
+
:partially_closed_position_not_deleted, :position_not_available_to_close, :position_not_found,
|
32
|
+
:reject_spreadbet_order_on_cfd_account, :size_increment, :sprint_market_expiry_after_market_close,
|
33
|
+
:stop_or_limit_not_allowed, :stop_required_error, :strike_level_tolerance, :success, :trailing_stop_not_allowed,
|
34
|
+
:unknown, :wrong_side_of_market]
|
35
|
+
attribute :size, Fixnum
|
36
|
+
attribute :status, Symbol, allowed_values: [:amended, :closed, :deleted, :open, :partially_closed]
|
37
|
+
attribute :stop_distance, Float
|
38
|
+
attribute :stop_level, Float
|
39
|
+
attribute :trailing_stop, Boolean
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module IGMarkets
|
2
|
+
# This is the primary class for interacting with the IG Markets API. After signing in using {#sign_in} most
|
3
|
+
# functionality is accessed via the following top-level methods:
|
4
|
+
#
|
5
|
+
# - {#account}
|
6
|
+
# - {#client_sentiment}
|
7
|
+
# - {#markets}
|
8
|
+
# - {#positions}
|
9
|
+
# - {#sprint_market_positions}
|
10
|
+
# - {#watchlists}
|
11
|
+
# - {#working_orders}
|
12
|
+
#
|
13
|
+
# See `README.md` for examples.
|
14
|
+
#
|
15
|
+
# If any errors occur while executing requests to the IG Markets API then {RequestFailedError} will be raised.
|
16
|
+
class DealingPlatform
|
17
|
+
# @return [Session] The session used by this dealing platform.
|
18
|
+
attr_reader :session
|
19
|
+
|
20
|
+
# @return [AccountMethods] Methods for working with the logged in account.
|
21
|
+
attr_reader :account
|
22
|
+
|
23
|
+
# @return [ClientSentimentMethods] Methods for working with client sentiment.
|
24
|
+
attr_reader :client_sentiment
|
25
|
+
|
26
|
+
# @return [MarketMethods] Methods for working with markets.
|
27
|
+
attr_reader :markets
|
28
|
+
|
29
|
+
# @return [PositionMethods] Methods for working with positions.
|
30
|
+
attr_reader :positions
|
31
|
+
|
32
|
+
# @return [SprintMarketPositionMethods] Methods for working with sprint market positions.
|
33
|
+
attr_reader :sprint_market_positions
|
34
|
+
|
35
|
+
# @return [WatchlistMethods] Methods for working with watchlists.
|
36
|
+
attr_reader :watchlists
|
37
|
+
|
38
|
+
# @return [WorkingOrderMethods] Methods for working with working orders.
|
39
|
+
attr_reader :working_orders
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
@session = Session.new
|
43
|
+
|
44
|
+
@account = AccountMethods.new self
|
45
|
+
@client_sentiment = ClientSentimentMethods.new self
|
46
|
+
@markets = MarketMethods.new self
|
47
|
+
@positions = PositionMethods.new self
|
48
|
+
@sprint_market_positions = SprintMarketPositionMethods.new self
|
49
|
+
@watchlists = WatchlistMethods.new self
|
50
|
+
@working_orders = WorkingOrderMethods.new self
|
51
|
+
end
|
52
|
+
|
53
|
+
# Signs in to the IG Markets Dealing Platform, either the production platform or the demo platform.
|
54
|
+
#
|
55
|
+
# @param [String] username The account username.
|
56
|
+
# @param [String] password The account password.
|
57
|
+
# @param [String] api_key The account API key.
|
58
|
+
# @param [:production, :demo] platform The platform to use.
|
59
|
+
def sign_in(username, password, api_key, platform)
|
60
|
+
session.username = username
|
61
|
+
session.password = password
|
62
|
+
session.api_key = api_key
|
63
|
+
session.platform = platform
|
64
|
+
|
65
|
+
session.sign_in
|
66
|
+
end
|
67
|
+
|
68
|
+
# Signs out of the IG Markets Dealing Platform, ending any current session.
|
69
|
+
def sign_out
|
70
|
+
session.sign_out
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns a full deal confirmation for the specified deal reference.
|
74
|
+
#
|
75
|
+
# @return [DealConfirmation]
|
76
|
+
def deal_confirmation(deal_reference)
|
77
|
+
DealConfirmation.from session.get "confirms/#{deal_reference}", API_V1
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns details on the IG Markets applications for the accounts associated with this login.
|
81
|
+
#
|
82
|
+
# @return [Array<Application>]
|
83
|
+
def applications
|
84
|
+
Application.from session.get 'operations/application', API_V1
|
85
|
+
end
|
86
|
+
|
87
|
+
# Sends a GET request to a URL then takes a single key from the returned hash and converts its contents to an array
|
88
|
+
# of type `klass`.
|
89
|
+
#
|
90
|
+
# @param [String] url The URL to send a GET request to.
|
91
|
+
# @param [Symbol] collection The name of the top level symbol that contains the array of data to return.
|
92
|
+
# @param [Class] klass The type to return.
|
93
|
+
# @param [API_V1, API_V2, API_V3] api_version The API version to target for the request.
|
94
|
+
#
|
95
|
+
# @return [Array]
|
96
|
+
def gather(url, collection, klass, api_version = API_V1)
|
97
|
+
klass.from(session.get(url, api_version).fetch(collection)).tap do |result|
|
98
|
+
# Set @dealing_platform on all the results
|
99
|
+
result.each do |item|
|
100
|
+
item.instance_variable_set :@dealing_platform, self
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module IGMarkets
|
2
|
+
class DealingPlatform
|
3
|
+
# Provides methods for working with the logged in account. Returned by {DealingPlatform#account}.
|
4
|
+
class AccountMethods
|
5
|
+
# Initializes this helper class with the specified dealing platform.
|
6
|
+
#
|
7
|
+
# @param [DealingPlatform] dealing_platform The dealing platform.
|
8
|
+
def initialize(dealing_platform)
|
9
|
+
@dealing_platform = dealing_platform
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns all accounts associated with the current IG Markets login.
|
13
|
+
#
|
14
|
+
# @return [Array<Account>]
|
15
|
+
def all
|
16
|
+
@dealing_platform.gather 'accounts', :accounts, Account
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns all account activities that occurred in the specified date range.
|
20
|
+
#
|
21
|
+
# @param [Date] from_date The start date of the desired date range.
|
22
|
+
# @param [Date] to_date The end date of the desired date range.
|
23
|
+
#
|
24
|
+
# @return [Array<AccountActivity>]
|
25
|
+
def activities_in_date_range(from_date, to_date)
|
26
|
+
from_date = format_date from_date
|
27
|
+
to_date = format_date to_date
|
28
|
+
|
29
|
+
@dealing_platform.gather "history/activity/#{from_date}/#{to_date}", :activities, AccountActivity
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns all account activities that occurred in the most recent specified number of seconds.
|
33
|
+
#
|
34
|
+
# @param [Integer, Float] seconds The number of seconds to return recent activities for.
|
35
|
+
#
|
36
|
+
# @return [Array<AccountActivity>]
|
37
|
+
def recent_activities(seconds)
|
38
|
+
@dealing_platform.gather "history/activity/#{(seconds * 1000.0).to_i}", :activities, AccountActivity
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns all transactions that occurred in the specified date range.
|
42
|
+
#
|
43
|
+
# @param [Date] from_date The start date of the desired date range.
|
44
|
+
# @param [Date] to_date The end date of the desired date range.
|
45
|
+
# @param [:all, :all_deal, :deposit, :withdrawal] transaction_type The type of transactions to return.
|
46
|
+
#
|
47
|
+
# @return [Array<AccountTransaction>]
|
48
|
+
def transactions_in_date_range(from_date, to_date, transaction_type = :all)
|
49
|
+
validate_transaction_type transaction_type
|
50
|
+
|
51
|
+
from_date = format_date from_date
|
52
|
+
to_date = format_date to_date
|
53
|
+
|
54
|
+
url = "history/transactions/#{transaction_type.to_s.upcase}/#{from_date}/#{to_date}"
|
55
|
+
|
56
|
+
@dealing_platform.gather url, :transactions, AccountTransaction
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns all transactions that occurred in the last specified number of seconds.
|
60
|
+
#
|
61
|
+
# @param [Integer, Float] seconds The number of seconds to return recent transactions for.
|
62
|
+
# @param [:all, :all_deal, :deposit, :withdrawal] transaction_type The type of transactions to return.
|
63
|
+
#
|
64
|
+
# @return [Array<AccountTransaction>]
|
65
|
+
def recent_transactions(seconds, transaction_type = :all)
|
66
|
+
validate_transaction_type transaction_type
|
67
|
+
|
68
|
+
url = "history/transactions/#{transaction_type.to_s.upcase}/#{(seconds * 1000.0).to_i}"
|
69
|
+
|
70
|
+
@dealing_platform.gather url, :transactions, AccountTransaction
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
# Validates whether the passed argument is a valid transaction type.
|
76
|
+
#
|
77
|
+
# @param [Symbol] type The candidate transaction type to validate.
|
78
|
+
def validate_transaction_type(type)
|
79
|
+
raise ArgumentError, 'transaction type is invalid' unless [:all, :all_deal, :deposit, :withdrawal].include? type
|
80
|
+
end
|
81
|
+
|
82
|
+
# Formats the passed `Date` as a string in the manner needed for building IG Markets URLs.
|
83
|
+
#
|
84
|
+
# @param [Date] date The date to format.
|
85
|
+
#
|
86
|
+
# @return [String]
|
87
|
+
def format_date(date)
|
88
|
+
date.strftime '%d-%m-%Y'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module IGMarkets
|
2
|
+
class DealingPlatform
|
3
|
+
# Provides methods for working with client sentiment. Returned by {DealingPlatform#client_sentiment}.
|
4
|
+
class ClientSentimentMethods
|
5
|
+
# Initializes this helper class with the specified dealing platform.
|
6
|
+
#
|
7
|
+
# @param [DealingPlatform] dealing_platform The dealing platform.
|
8
|
+
def initialize(dealing_platform)
|
9
|
+
@dealing_platform = dealing_platform
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns the client sentiment for a market.
|
13
|
+
#
|
14
|
+
# @param [String] market_id The ID of the market to return client sentiment for.
|
15
|
+
#
|
16
|
+
# @return [ClientSentiment]
|
17
|
+
def [](market_id)
|
18
|
+
result = @dealing_platform.session.get "clientsentiment/#{market_id}", API_V1
|
19
|
+
|
20
|
+
ClientSentiment.from(result).tap do |client_sentiment|
|
21
|
+
client_sentiment.instance_variable_set :@dealing_platform, @dealing_platform
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module IGMarkets
|
2
|
+
class DealingPlatform
|
3
|
+
# Provides methods for working with markets. Returned by {DealingPlatform#markets}.
|
4
|
+
class MarketMethods
|
5
|
+
# Initializes this helper class with the specified dealing platform.
|
6
|
+
#
|
7
|
+
# @param [DealingPlatform] dealing_platform The dealing platform.
|
8
|
+
def initialize(dealing_platform)
|
9
|
+
@dealing_platform = dealing_platform
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns details on the market hierarchy directly under the specified node.
|
13
|
+
#
|
14
|
+
# @param [String] node_id The ID of the node to return the market hierarchy for. If `nil` then details on the root
|
15
|
+
# node of the hierarchy will be returned.
|
16
|
+
#
|
17
|
+
# @return [MarketHierarchyResult]
|
18
|
+
def hierarchy(node_id = nil)
|
19
|
+
url = ['marketnavigation', node_id].compact.join '/'
|
20
|
+
|
21
|
+
MarketHierarchyResult.from @dealing_platform.session.get(url, API_V1)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns details for the markets with the passed EPICs.
|
25
|
+
#
|
26
|
+
# @param [Array<String>] epics The EPICs of the markets to return details for.
|
27
|
+
#
|
28
|
+
# @return [Array<Market>]
|
29
|
+
def find(*epics)
|
30
|
+
raise ArgumentError, 'at least one EPIC must be specified' if epics.empty?
|
31
|
+
|
32
|
+
epics.each do |epic|
|
33
|
+
raise ArgumentError, "invalid EPIC: #{epic}" unless epic.to_s =~ Regex::EPIC
|
34
|
+
end
|
35
|
+
|
36
|
+
@dealing_platform.gather "markets?epics=#{epics.join(',')}", :market_details, Market, API_V2
|
37
|
+
end
|
38
|
+
|
39
|
+
# Searches markets using a search term and returns an array of results.
|
40
|
+
#
|
41
|
+
# @param [String] search_term The search term to use.
|
42
|
+
#
|
43
|
+
# @return [Array<MarketOverview>]
|
44
|
+
def search(search_term)
|
45
|
+
@dealing_platform.gather "markets?searchTerm=#{search_term}", :markets, MarketOverview
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns market details for the market with the specified EPIC, or `nil` if there is no market with that EPIC.
|
49
|
+
# Internally a call to {#find} is made.
|
50
|
+
#
|
51
|
+
# @param [String] epic The EPIC of the market to return details for.
|
52
|
+
#
|
53
|
+
# @return [Market]
|
54
|
+
def [](epic)
|
55
|
+
find(epic)[0]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module IGMarkets
|
2
|
+
class DealingPlatform
|
3
|
+
# Provides methods for working with positions. Returned by {DealingPlatform#positions}.
|
4
|
+
class PositionMethods
|
5
|
+
# Initializes this helper class with the specified dealing platform.
|
6
|
+
#
|
7
|
+
# @param [DealingPlatform] dealing_platform The dealing platform.
|
8
|
+
def initialize(dealing_platform)
|
9
|
+
@dealing_platform = dealing_platform
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns all positions.
|
13
|
+
#
|
14
|
+
# @return [Array<Position>]
|
15
|
+
def all
|
16
|
+
@dealing_platform.session.get('positions', API_V2).fetch(:positions).map do |attributes|
|
17
|
+
position_from_attributes attributes
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the position with the specified deal ID, or `nil` if there is no position with that ID.
|
22
|
+
#
|
23
|
+
# @param [String] deal_id The deal ID of the working order to return.
|
24
|
+
#
|
25
|
+
# @return [Position]
|
26
|
+
def [](deal_id)
|
27
|
+
attributes = @dealing_platform.session.get "positions/#{deal_id}", API_V2
|
28
|
+
|
29
|
+
position_from_attributes attributes
|
30
|
+
end
|
31
|
+
|
32
|
+
# Creates a new position.
|
33
|
+
#
|
34
|
+
# @param [Hash] attributes The attributes for the new position.
|
35
|
+
# @option attributes [String] :currency_code The 3 character currency code, must be one of the instrument's
|
36
|
+
# currencies (see {Instrument#currencies}). Required.
|
37
|
+
# @option attributes [:buy, :sell] :direction The position direction. Required.
|
38
|
+
# @option attributes [String] :epic The EPIC of the instrument to create a position for. Required.
|
39
|
+
# @option attributes [DateTime] :expiry The expiry date of the instrument, if it has one. Optional.
|
40
|
+
# @option attributes [Boolean] :force_open Whether a force open is required. Defaults to `false`.
|
41
|
+
# @option attributes [Boolean] :guaranteed_stop Whether a guaranteed stop is required. Defaults to `false`.
|
42
|
+
# @option attributes [Float] :level Required if and only if `:order_type` is `:limit` or `:quote`.
|
43
|
+
# @option attributes [Fixnum] :limit_distance The distance away in pips to place the limit. If this is set then
|
44
|
+
# `:limit_level` must be `nil`. Optional.
|
45
|
+
# @option attributes [Float] :limit_level The limit level. If this is set then `:limit_distance` must be `nil`.
|
46
|
+
# Optional.
|
47
|
+
# @option attributes [:limit, :market, :quote] :order_type The order type. `:market` indicates to fill the order
|
48
|
+
# at current market level(s). `:limit` indicates to fill at the price specified by `:level`
|
49
|
+
# (or a more favorable one). `:quote` is only permitted following agreement with IG Markets.
|
50
|
+
# Defaults to `:market`.
|
51
|
+
# @option attributes [String] :quote_id The Lightstreamer quote ID. Required when `:order_type` is `:quote`.
|
52
|
+
# @option attributes [Float] :size The size of the position to create. Required.
|
53
|
+
# @option attributes [Fixnum] :stop_distance The distance away in pips to place the stop. If this is set then
|
54
|
+
# `:stop_level` must be `nil`. Optional.
|
55
|
+
# @option attributes [Float] :stop_level The stop level. If this is set then `:stop_distance` must be `nil`.
|
56
|
+
# Optional.
|
57
|
+
# @option attributes [:execute_and_eliminate, :fill_or_kill] :time_in_force The order fill strategy.
|
58
|
+
# `:execute_and_eliminate` will fill this order as much as possible within the constraints set
|
59
|
+
# by `:order_type`, `:level` and `:quote_id`, which may result in only part of the requested
|
60
|
+
# order being filled. `:fill_or_kill` will try to fill the whole order within the constraints,
|
61
|
+
# however if this is not possible then the order will not be filled at all. If `:order_type` is
|
62
|
+
# `:market` (the default) then `:time_in_force` will be automatically set to
|
63
|
+
# `:execute_and_eliminate`.
|
64
|
+
# @option attributes [Boolean] :trailing_stop Whether to use a trailing stop. Defaults to false. Optional.
|
65
|
+
# @option attributes [Fixnum] :trailing_stop_increment The increment step in pips for the trailing stop. Required
|
66
|
+
# when `:trailing_stop` is `true`.
|
67
|
+
#
|
68
|
+
# @return [String] The resulting deal reference, use {DealingPlatform#deal_confirmation} to check the result of
|
69
|
+
# the position creation.
|
70
|
+
def create(attributes)
|
71
|
+
attributes[:force_open] = false unless attributes.key? :force_open
|
72
|
+
attributes[:guaranteed_stop] = false unless attributes.key? :guaranteed_stop
|
73
|
+
attributes[:order_type] ||= :market
|
74
|
+
attributes[:time_in_force] = :execute_and_eliminate if attributes[:order_type] == :market
|
75
|
+
|
76
|
+
model = PositionCreateAttributes.new attributes
|
77
|
+
model.validate
|
78
|
+
|
79
|
+
payload = PayloadFormatter.format model
|
80
|
+
payload[:expiry] ||= '-'
|
81
|
+
|
82
|
+
@dealing_platform.session.post('positions/otc', payload, API_V2).fetch(:deal_reference)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Internal model used by {#create}
|
88
|
+
class PositionCreateAttributes < Model
|
89
|
+
attribute :currency_code, String, regex: Regex::CURRENCY
|
90
|
+
attribute :direction, Symbol, allowed_values: [:buy, :sell]
|
91
|
+
attribute :epic, String, regex: Regex::EPIC
|
92
|
+
attribute :expiry, DateTime, format: '%d-%b-%y'
|
93
|
+
attribute :force_open, Boolean
|
94
|
+
attribute :guaranteed_stop, Boolean
|
95
|
+
attribute :level, Float
|
96
|
+
attribute :limit_distance, Fixnum
|
97
|
+
attribute :limit_level, Float
|
98
|
+
attribute :order_type, Symbol, allowed_values: [:limit, :market, :quote]
|
99
|
+
attribute :quote_id
|
100
|
+
attribute :size, Fixnum
|
101
|
+
attribute :stop_distance, Fixnum
|
102
|
+
attribute :stop_level, Float
|
103
|
+
attribute :time_in_force, Symbol, allowed_values: [:execute_and_eliminate, :fill_or_kill]
|
104
|
+
attribute :trailing_stop, Boolean
|
105
|
+
attribute :trailing_stop_increment, Fixnum
|
106
|
+
|
107
|
+
# Runs a series of validations on this model's attributes to check whether it is ready to be sent to the IG
|
108
|
+
# Markets API.
|
109
|
+
def validate
|
110
|
+
validate_required_attributes_present
|
111
|
+
Position.validate_order_type_constraints attributes
|
112
|
+
validate_trailing_stop_constraints
|
113
|
+
validate_stop_and_limit_constraints
|
114
|
+
validate_guaranteed_stop_constraints
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
# Checks that all required attributes for position creation are present.
|
120
|
+
def validate_required_attributes_present
|
121
|
+
required = [:currency_code, :direction, :epic, :force_open, :guaranteed_stop, :order_type, :size,
|
122
|
+
:time_in_force]
|
123
|
+
|
124
|
+
required.each do |attribute|
|
125
|
+
raise ArgumentError, "#{attribute} attribute must be set" if attributes[attribute].nil?
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Checks that attributes associated with the trailing stops are valid.
|
130
|
+
def validate_trailing_stop_constraints
|
131
|
+
if trailing_stop
|
132
|
+
raise ArgumentError, 'do not set stop_level when trailing_stop is true' if stop_level
|
133
|
+
raise ArgumentError, 'set stop_distance when trailing_stop is true' unless stop_distance
|
134
|
+
end
|
135
|
+
|
136
|
+
if trailing_stop == trailing_stop_increment.nil?
|
137
|
+
raise ArgumentError, 'set trailing_stop_increment if and only if trailing_stop is true'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Checks that attributes associated with the stop and limit are valid.
|
142
|
+
def validate_stop_and_limit_constraints
|
143
|
+
raise ArgumentError, 'set only one of limit_level and limit_distance' if limit_level && limit_distance
|
144
|
+
raise ArgumentError, 'set only one of stop_level and stop_distance' if stop_level && stop_distance
|
145
|
+
end
|
146
|
+
|
147
|
+
# Checks that attributes associated with the guaranteed stop are valid.
|
148
|
+
def validate_guaranteed_stop_constraints
|
149
|
+
if guaranteed_stop && !(stop_level.nil? ^ stop_distance.nil?)
|
150
|
+
raise ArgumentError, 'set exactly one of stop_level or stop_distance when guaranteed_stop is true'
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def position_from_attributes(attributes)
|
156
|
+
Position.new(attributes.fetch(:position).merge(market: attributes.fetch(:market))).tap do |position|
|
157
|
+
position.instance_variable_set :@dealing_platform, @dealing_platform
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
private_constant :PositionCreateAttributes
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|