ig_markets 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/README.md +32 -16
  4. data/bin/ig_markets +29 -0
  5. data/lib/ig_markets.rb +15 -0
  6. data/lib/ig_markets/account_activity.rb +1 -1
  7. data/lib/ig_markets/account_transaction.rb +1 -1
  8. data/lib/ig_markets/application.rb +2 -2
  9. data/lib/ig_markets/cli/account_command.rb +40 -0
  10. data/lib/ig_markets/cli/activities_command.rb +33 -0
  11. data/lib/ig_markets/cli/confirmation_command.rb +32 -0
  12. data/lib/ig_markets/cli/main.rb +39 -0
  13. data/lib/ig_markets/cli/orders_command.rb +28 -0
  14. data/lib/ig_markets/cli/positions_command.rb +20 -0
  15. data/lib/ig_markets/cli/search_command.rb +30 -0
  16. data/lib/ig_markets/cli/sentiment_command.rb +35 -0
  17. data/lib/ig_markets/cli/sprints_command.rb +28 -0
  18. data/lib/ig_markets/cli/transactions_command.rb +56 -0
  19. data/lib/ig_markets/cli/watchlists_command.rb +34 -0
  20. data/lib/ig_markets/deal_confirmation.rb +4 -4
  21. data/lib/ig_markets/dealing_platform/position_methods.rb +3 -3
  22. data/lib/ig_markets/dealing_platform/sprint_market_position_methods.rb +1 -1
  23. data/lib/ig_markets/dealing_platform/watchlist_methods.rb +1 -3
  24. data/lib/ig_markets/dealing_platform/working_order_methods.rb +10 -10
  25. data/lib/ig_markets/format.rb +36 -0
  26. data/lib/ig_markets/historical_price_result.rb +1 -1
  27. data/lib/ig_markets/instrument.rb +2 -2
  28. data/lib/ig_markets/market.rb +10 -10
  29. data/lib/ig_markets/model.rb +23 -60
  30. data/lib/ig_markets/model/typecasters.rb +78 -0
  31. data/lib/ig_markets/payload_formatter.rb +19 -6
  32. data/lib/ig_markets/position.rb +7 -21
  33. data/lib/ig_markets/request_failed_error.rb +1 -1
  34. data/lib/ig_markets/sprint_market_position.rb +18 -3
  35. data/lib/ig_markets/version.rb +1 -1
  36. data/lib/ig_markets/working_order.rb +11 -11
  37. metadata +39 -64
  38. data/.codeclimate.yml +0 -15
  39. data/.gitignore +0 -9
  40. data/.rspec +0 -2
  41. data/.rubocop.yml +0 -2
  42. data/.travis.yml +0 -10
  43. data/.yardopts +0 -4
  44. data/Gemfile +0 -2
  45. data/ig_markets.gemspec +0 -28
  46. data/spec/factories/ig_markets/account.rb +0 -14
  47. data/spec/factories/ig_markets/account_activity.rb +0 -21
  48. data/spec/factories/ig_markets/account_balance.rb +0 -8
  49. data/spec/factories/ig_markets/account_transaction.rb +0 -15
  50. data/spec/factories/ig_markets/application.rb +0 -21
  51. data/spec/factories/ig_markets/client_sentiment.rb +0 -7
  52. data/spec/factories/ig_markets/deal_confirmation.rb +0 -20
  53. data/spec/factories/ig_markets/historical_price_result.rb +0 -7
  54. data/spec/factories/ig_markets/historical_price_result_data_allowance.rb +0 -7
  55. data/spec/factories/ig_markets/historical_price_result_price.rb +0 -7
  56. data/spec/factories/ig_markets/historical_price_result_snapshot.rb +0 -10
  57. data/spec/factories/ig_markets/instrument.rb +0 -32
  58. data/spec/factories/ig_markets/instrument_currency.rb +0 -9
  59. data/spec/factories/ig_markets/instrument_expiry_details.rb +0 -6
  60. data/spec/factories/ig_markets/instrument_margin_deposit_band.rb +0 -8
  61. data/spec/factories/ig_markets/instrument_opening_hours.rb +0 -6
  62. data/spec/factories/ig_markets/instrument_rollover_details.rb +0 -6
  63. data/spec/factories/ig_markets/instrument_slippage_factor.rb +0 -6
  64. data/spec/factories/ig_markets/market.rb +0 -7
  65. data/spec/factories/ig_markets/market_dealing_rules.rb +0 -11
  66. data/spec/factories/ig_markets/market_dealing_rules_rule_details.rb +0 -6
  67. data/spec/factories/ig_markets/market_hierarchy_result.rb +0 -6
  68. data/spec/factories/ig_markets/market_hierarchy_result_hierarchy_node.rb +0 -6
  69. data/spec/factories/ig_markets/market_overview.rb +0 -22
  70. data/spec/factories/ig_markets/market_snapshot.rb +0 -17
  71. data/spec/factories/ig_markets/position.rb +0 -19
  72. data/spec/factories/ig_markets/sprint_market_position.rb +0 -16
  73. data/spec/factories/ig_markets/watchlist.rb +0 -9
  74. data/spec/factories/ig_markets/working_order.rb +0 -21
  75. data/spec/ig_markets/account_transaction_spec.rb +0 -30
  76. data/spec/ig_markets/dealing_platform/account_methods_spec.rb +0 -58
  77. data/spec/ig_markets/dealing_platform/client_sentiment_methods_spec.rb +0 -29
  78. data/spec/ig_markets/dealing_platform/market_methods_spec.rb +0 -80
  79. data/spec/ig_markets/dealing_platform/position_methods_spec.rb +0 -137
  80. data/spec/ig_markets/dealing_platform/sprint_market_position_methods_spec.rb +0 -39
  81. data/spec/ig_markets/dealing_platform/watchlist_methods_spec.rb +0 -89
  82. data/spec/ig_markets/dealing_platform/working_order_methods_spec.rb +0 -120
  83. data/spec/ig_markets/dealing_platform_spec.rb +0 -40
  84. data/spec/ig_markets/model_spec.rb +0 -127
  85. data/spec/ig_markets/password_encryptor_spec.rb +0 -23
  86. data/spec/ig_markets/payload_formatter_spec.rb +0 -19
  87. data/spec/ig_markets/position_spec.rb +0 -37
  88. data/spec/ig_markets/response_parser_spec.rb +0 -13
  89. data/spec/ig_markets/session_spec.rb +0 -134
  90. data/spec/spec_helper.rb +0 -14
  91. data/spec/support/factory_girl.rb +0 -7
  92. data/spec/support/random_test_order.rb +0 -3
@@ -0,0 +1,35 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Implements the `ig_markets sentiment` command.
4
+ class Main
5
+ desc 'sentiment', 'Prints sentiment for the specified market'
6
+
7
+ option :market, aliases: '-m', required: true, desc: 'The name of the market to print sentiment for'
8
+ option :related, aliases: '-r', type: :boolean, desc: 'Whether to print sentiment for related markets as well'
9
+
10
+ def sentiment
11
+ begin_session do
12
+ result = dealing_platform.client_sentiment[options[:market]]
13
+
14
+ print_sentiment result
15
+
16
+ if options[:related]
17
+ result.related_sentiments.each do |model|
18
+ print_sentiment model
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def print_sentiment(model)
27
+ print <<-END
28
+ #{model.market_id}: \
29
+ longs: #{model.long_position_percentage}%, \
30
+ shorts: #{model.short_position_percentage}%
31
+ END
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Implements the `ig_markets sprints` command.
4
+ class Main
5
+ desc 'sprints', 'Prints open sprint market positions'
6
+
7
+ def sprints
8
+ begin_session do
9
+ dealing_platform.sprint_market_positions.all.each do |sprint|
10
+ print_sprint_market_position sprint
11
+ end
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def print_sprint_market_position(sprint)
18
+ print <<-END
19
+ #{sprint.deal_id}: \
20
+ #{Format.currency sprint.size, sprint.currency} on #{sprint.epic} \
21
+ to be #{{ buy: 'above', sell: 'below' }.fetch(sprint.direction)} #{sprint.strike_level} \
22
+ in #{Format.seconds sprint.seconds_till_expiry}, \
23
+ payout: #{Format.currency sprint.payout_amount, sprint.currency}
24
+ END
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,56 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Implements the `ig_markets transactions` command.
4
+ class Main
5
+ desc 'transactions', 'Prints recent transactions'
6
+
7
+ option :days, default: 3, type: :numeric, desc: 'The number of days to print recent transactions for'
8
+
9
+ def transactions
10
+ begin_session do
11
+ transactions = dealing_platform.account.recent_transactions(seconds).sort_by(&:date)
12
+
13
+ transactions.each do |transaction|
14
+ print_transaction transaction
15
+ end
16
+
17
+ print_transaction_totals transactions
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def transaction_totals(transactions)
24
+ transactions.each_with_object({}) do |transaction, hash|
25
+ profit_loss = transaction.profit_and_loss_amount
26
+
27
+ currency = (hash[transaction.currency] ||= Hash.new(0))
28
+
29
+ currency[:delta] += profit_loss
30
+ currency[:interest] += profit_loss if transaction.interest?
31
+ end
32
+ end
33
+
34
+ def print_transaction(transaction)
35
+ print <<-END
36
+ #{transaction.reference}: #{transaction.date.strftime '%Y-%m-%d'}, \
37
+ #{transaction.formatted_transaction_type}, \
38
+ #{"#{transaction.size} of " if transaction.size}\
39
+ #{transaction.instrument_name}, \
40
+ profit/loss: #{Format.currency transaction.profit_and_loss_amount, transaction.currency}
41
+ END
42
+ end
43
+
44
+ def print_transaction_totals(transactions)
45
+ transaction_totals(transactions).each do |currency, value|
46
+ print <<-END
47
+
48
+ Totals for currency '#{currency}':
49
+ Interest: #{Format.currency value[:interest], currency}
50
+ Profit/loss: #{Format.currency value[:delta], currency}
51
+ END
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,34 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Implements the `ig_markets watchlists` command.
4
+ class Main
5
+ desc 'watchlists', 'Prints all watchlists and their markets'
6
+
7
+ def watchlists
8
+ begin_session do
9
+ dealing_platform.watchlists.all.each do |watchlist|
10
+ print_watchlist watchlist
11
+
12
+ watchlist.markets.each do |market|
13
+ print ' - '
14
+ print_market_overview market
15
+ end
16
+ print "\n"
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def print_watchlist(watchlist)
24
+ print <<-END
25
+ #{watchlist.id}: \
26
+ #{watchlist.name}, \
27
+ editable: #{watchlist.editable}, \
28
+ deleteable: #{watchlist.deleteable}, \
29
+ default: #{watchlist.default_system_watchlist}
30
+ END
31
+ end
32
+ end
33
+ end
34
+ end
@@ -13,10 +13,10 @@ module IGMarkets
13
13
  attribute :deal_status, Symbol, allowed_values: [:accepted, :fund_account, :rejected]
14
14
  attribute :direction, Symbol, allowed_values: [:buy, :sell]
15
15
  attribute :epic
16
- attribute :expiry, DateTime, nil_if: '-', format: '%d-%b-%y'
16
+ attribute :expiry, Date, nil_if: '-', format: '%d-%b-%y'
17
17
  attribute :guaranteed_stop, Boolean
18
18
  attribute :level, Float
19
- attribute :limit_distance, Float
19
+ attribute :limit_distance, Fixnum
20
20
  attribute :limit_level, Float
21
21
  attribute :reason, Symbol, allowed_values: [
22
22
  :account_not_enabled_to_trading, :attached_order_level_error, :attached_order_trailing_stop_error,
@@ -32,9 +32,9 @@ module IGMarkets
32
32
  :reject_spreadbet_order_on_cfd_account, :size_increment, :sprint_market_expiry_after_market_close,
33
33
  :stop_or_limit_not_allowed, :stop_required_error, :strike_level_tolerance, :success, :trailing_stop_not_allowed,
34
34
  :unknown, :wrong_side_of_market]
35
- attribute :size, Fixnum
35
+ attribute :size, Float
36
36
  attribute :status, Symbol, allowed_values: [:amended, :closed, :deleted, :open, :partially_closed]
37
- attribute :stop_distance, Float
37
+ attribute :stop_distance, Fixnum
38
38
  attribute :stop_level, Float
39
39
  attribute :trailing_stop, Boolean
40
40
  end
@@ -36,7 +36,7 @@ module IGMarkets
36
36
  # currencies (see {Instrument#currencies}). Required.
37
37
  # @option attributes [:buy, :sell] :direction The position direction. Required.
38
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.
39
+ # @option attributes [Time] :expiry The expiry date of the instrument, if it has one. Optional.
40
40
  # @option attributes [Boolean] :force_open Whether a force open is required. Defaults to `false`.
41
41
  # @option attributes [Boolean] :guaranteed_stop Whether a guaranteed stop is required. Defaults to `false`.
42
42
  # @option attributes [Float] :level Required if and only if `:order_type` is `:limit` or `:quote`.
@@ -89,7 +89,7 @@ module IGMarkets
89
89
  attribute :currency_code, String, regex: Regex::CURRENCY
90
90
  attribute :direction, Symbol, allowed_values: [:buy, :sell]
91
91
  attribute :epic, String, regex: Regex::EPIC
92
- attribute :expiry, DateTime, format: '%d-%b-%y'
92
+ attribute :expiry, Time, format: '%d-%b-%y'
93
93
  attribute :force_open, Boolean
94
94
  attribute :guaranteed_stop, Boolean
95
95
  attribute :level, Float
@@ -97,7 +97,7 @@ module IGMarkets
97
97
  attribute :limit_level, Float
98
98
  attribute :order_type, Symbol, allowed_values: [:limit, :market, :quote]
99
99
  attribute :quote_id
100
- attribute :size, Fixnum
100
+ attribute :size, Float
101
101
  attribute :stop_distance, Fixnum
102
102
  attribute :stop_level, Float
103
103
  attribute :time_in_force, Symbol, allowed_values: [:execute_and_eliminate, :fill_or_kill]
@@ -39,7 +39,7 @@ module IGMarkets
39
39
  attribute :epic, String, regex: Regex::EPIC
40
40
  attribute :expiry_period, Symbol, allowed_values: [
41
41
  :one_minute, :two_minutes, :five_minutes, :twenty_minutes, :sixty_minutes]
42
- attribute :size, Fixnum
42
+ attribute :size, Float
43
43
  end
44
44
  end
45
45
  end
@@ -33,9 +33,7 @@ module IGMarkets
33
33
  def create(name, *epics)
34
34
  result = @dealing_platform.session.post 'watchlists', { name: name, epics: epics.flatten }, API_V1
35
35
 
36
- all.detect do |watchlist|
37
- watchlist.id == result.fetch(:watchlist_id)
38
- end
36
+ self[result.fetch(:watchlist_id)]
39
37
  end
40
38
  end
41
39
  end
@@ -40,15 +40,15 @@ module IGMarkets
40
40
  # currencies (see {Instrument#currencies}). Required.
41
41
  # @option attributes [:buy, :sell] :direction Order direction. Required.
42
42
  # @option attributes [String] :epic The EPIC of the instrument for the order. Required.
43
- # @option attributes [DateTime] :expiry The expiry date of the instrument (if applicable). Optional.
43
+ # @option attributes [Time] :expiry The expiry date of the instrument (if applicable). Optional.
44
44
  # @option attributes [Boolean] :force_open Whether a force open is required. Defaults to `false`.
45
- # @option attributes [DateTime] :good_till_date The date that the working order will live till. Must be set if
45
+ # @option attributes [Time] :good_till_date The date that the working order will live till. Must be set if
46
46
  # `:time_in_force` is `:good_till_date`.
47
47
  # @option attributes [Boolean] :guaranteed_stop Whether a guaranteed stop is required. Defaults to `false`.
48
48
  # @option attributes [Float] :level The level at which the order will be triggered.
49
- # @option attributes [Float] :limit_distance The distance away in pips to place the limit. Optional.
49
+ # @option attributes [Fixnum] :limit_distance The distance away in pips to place the limit. Optional.
50
50
  # @option attributes [Float] :size The size of the working order. Required.
51
- # @option attributes [Float] :stop_distance The distance away in pips to place the stop. Optional.
51
+ # @option attributes [Fixnum] :stop_distance The distance away in pips to place the stop. Optional.
52
52
  # @option attributes [:good_till_cancelled, :good_till_date] :time_in_force The lifespan of the working order.
53
53
  # `:good_till_cancelled` means the working order will live until it is explicitly cancelled.
54
54
  # `:good_till_date` means the working order will live until the date specified by
@@ -85,7 +85,7 @@ module IGMarkets
85
85
  raise ArgumentError, "#{attribute} attribute must be set" if attributes[attribute].nil?
86
86
  end
87
87
 
88
- if model.time_in_force == :good_till_date && !model.good_till_date.is_a?(DateTime)
88
+ if model.time_in_force == :good_till_date && !model.good_till_date.is_a?(Time)
89
89
  raise ArgumentError, 'good_till_date must be set when time_in_force is :good_till_date'
90
90
  end
91
91
 
@@ -97,14 +97,14 @@ module IGMarkets
97
97
  attribute :currency_code, String, regex: Regex::CURRENCY
98
98
  attribute :direction, Symbol, allowed_values: [:buy, :sell]
99
99
  attribute :epic, String, regex: Regex::EPIC
100
- attribute :expiry, DateTime, format: '%d-%b-%y'
100
+ attribute :expiry, Time, format: '%d-%b-%y'
101
101
  attribute :force_open, Boolean
102
- attribute :good_till_date, DateTime, format: '%Y-%m-%d %H:%M:%S'
102
+ attribute :good_till_date, Time, format: '%F %T'
103
103
  attribute :guaranteed_stop, Boolean
104
104
  attribute :level, Float
105
- attribute :limit_distance, Float
106
- attribute :size, Fixnum
107
- attribute :stop_distance, Float
105
+ attribute :limit_distance, Fixnum
106
+ attribute :size, Float
107
+ attribute :stop_distance, Fixnum
108
108
  attribute :time_in_force, Symbol, allowed_values: [:good_till_cancelled, :good_till_date]
109
109
  attribute :type, Symbol, allowed_values: [:limit, :stop]
110
110
  end
@@ -0,0 +1,36 @@
1
+ module IGMarkets
2
+ # This module contains shared methods for formatting different content types for display.
3
+ module Format
4
+ module_function
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.
8
+ #
9
+ # @param [Float, Fixnum] amount The currency amount to format.
10
+ # @param [String] symbol The currency symbol.
11
+ #
12
+ # @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}"
16
+ else
17
+ "#{symbol} #{format '%.2f', amount.to_f}"
18
+ end
19
+ end
20
+
21
+ # Returns a formatted string for the specified number of seconds in the format `[<hours>:]<minutes>:<seconds>`.
22
+ #
23
+ # @param [Fixnum] value The number of seconds to format.
24
+ #
25
+ # @return [String]
26
+ def seconds(value)
27
+ result = if value >= 60 * 60
28
+ "#{value / 60 / 60}:#{Kernel.format('%02i', (value / 60) % 60)}"
29
+ else
30
+ (value / 60).to_s
31
+ end
32
+
33
+ result + ':' + Kernel.format('%02i', value % 60)
34
+ end
35
+ end
36
+ end
@@ -23,7 +23,7 @@ module IGMarkets
23
23
  attribute :last_traded_volume, Float
24
24
  attribute :low_price, Price
25
25
  attribute :open_price, Price
26
- attribute :snapshot_time, DateTime, format: '%Y/%m/%d %H:%M:%S'
26
+ attribute :snapshot_time, Time, format: '%Y/%m/%d %T', time_zone: '+1000'
27
27
  end
28
28
 
29
29
  attribute :allowance, DataAllowance
@@ -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, DateTime, format: '%Y/%m/%d %H:%M:%S'
15
+ attribute :last_dealing_date, Time, format: '%FT%R', time_zone: '+1000'
16
16
  attribute :settlement_info
17
17
  end
18
18
 
@@ -60,7 +60,7 @@ module IGMarkets
60
60
  attribute :country
61
61
  attribute :currencies, Currency
62
62
  attribute :epic, String, regex: Regex::EPIC
63
- attribute :expiry, DateTime, nil_if: '-', format: '%d-%b-%y'
63
+ attribute :expiry, Date, nil_if: '-', format: '%d-%b-%y'
64
64
  attribute :expiry_details, ExpiryDetails
65
65
  attribute :force_open_allowed, Boolean
66
66
  attribute :lot_size, Float
@@ -62,17 +62,17 @@ module IGMarkets
62
62
  #
63
63
  # @param [:minute, :minute_2, :minute_3, :minute_5, :minute_10, :minute_15, :minute_30, :hour, :hour_2, :hour_3,
64
64
  # :hour_4, :day, :week, :month] resolution The resolution of the historical prices to return.
65
- # @param [DateTime] start_date_time The start of the desired time period.
66
- # @param [DateTime] end_date_time The end of the desired time period.
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
67
  #
68
68
  # @return [HistoricalPriceResult]
69
- def prices_in_date_range(resolution, start_date_time, end_date_time)
69
+ def prices_in_date_range(resolution, start_time, end_time)
70
70
  validate_historical_price_resolution resolution
71
71
 
72
- start_date_time = format_date_time start_date_time
73
- end_date_time = format_date_time end_date_time
72
+ start_time = format_time start_time
73
+ end_time = format_time end_time
74
74
 
75
- url = "prices/#{instrument.epic}/#{resolution.to_s.upcase}/#{start_date_time}/#{end_date_time}"
75
+ url = "prices/#{instrument.epic}/#{resolution.to_s.upcase}/#{start_time}/#{end_time}"
76
76
 
77
77
  HistoricalPriceResult.from @dealing_platform.session.get(url, API_V2)
78
78
  end
@@ -89,11 +89,11 @@ module IGMarkets
89
89
  raise ArgumentError, 'resolution is invalid' unless resolutions.include? resolution
90
90
  end
91
91
 
92
- # Takes a `DateTime` and formats it for the historical prices API URLs.
92
+ # Takes a `Time` and formats it for the historical prices API URLs.
93
93
  #
94
- # @param [DateTime] date_time The `DateTime` to format.
95
- def format_date_time(date_time)
96
- date_time.strftime '%Y-%m-%dT%H:%M:%S'
94
+ # @param [Time] time The `Time` to format.
95
+ def format_time(time)
96
+ time.utc.strftime '%FT%T'
97
97
  end
98
98
  end
99
99
  end
@@ -19,9 +19,8 @@ module IGMarkets
19
19
 
20
20
  (attributes.keys - defined_attribute_names).map do |attribute|
21
21
  value = attributes[attribute]
22
- value = value.inspect unless value.is_a? DateTime
23
22
 
24
- raise ArgumentError, "Unknown attribute: #{self.class.name}##{attribute}, value: #{value}"
23
+ raise ArgumentError, "Unknown attribute: #{self.class.name}##{attribute}, value: #{inspect_value value}"
25
24
  end
26
25
  end
27
26
 
@@ -39,14 +38,25 @@ module IGMarkets
39
38
  # @return [String]
40
39
  def inspect
41
40
  formatted_attributes = self.class.defined_attribute_names.map do |attribute|
42
- value = send attribute
43
-
44
- "#{attribute}: #{value.is_a?(DateTime) ? value : value.inspect}"
41
+ "#{attribute}: #{inspect_value send(attribute)}"
45
42
  end
46
43
 
47
44
  "#<#{self.class.name} #{formatted_attributes.join ', '}>"
48
45
  end
49
46
 
47
+ private
48
+
49
+ # Returns the #inspect string for the given value.
50
+ def inspect_value(value)
51
+ if value.is_a? Time
52
+ value.utc.strftime '%F %T %z'
53
+ elsif value.is_a? Date
54
+ value.strftime '%F'
55
+ else
56
+ value.inspect
57
+ end
58
+ end
59
+
50
60
  class << self
51
61
  # @return [Hash] A hash containing details of all attributes that have been defined on this model.
52
62
  attr_accessor :defined_attributes
@@ -70,15 +80,17 @@ module IGMarkets
70
80
  # Defines setter and getter methods for a new attribute on this class.
71
81
  #
72
82
  # @param [Symbol] name The name of the new attribute.
73
- # @param [Boolean, String, DateTime, Fixnum, Float, Symbol, #from] type The attribute's type.
83
+ # @param [Boolean, String, Date, Time, Fixnum, Float, Symbol, #from] type The attribute's type.
74
84
  # @param [Hash] options The configuration options for the new attribute.
75
85
  # @option options [Array] :allowed_values The set of values that this attribute is allowed to be set to. An
76
86
  # attempt to set this attribute to a value not in this list will raise `ArgumentError`. Optional.
77
87
  # @option options [Array] :nil_if Values that, when set on the attribute, should be converted to `nil`.
78
88
  # @option options [Regexp] :regex When `type` is `String` only values matching this regex will be allowed.
79
89
  # Optional.
80
- # @option options [String] :format When `type` is `DateTime` this specifies the format for parsing String and
81
- # Fixnum instances assigned to this attribute. See `DateTime#strptime` for details.
90
+ # @option options [String] :format When `type` is `Date` or `Time` this specifies the format for parsing String
91
+ # and `Fixnum` instances assigned to this attribute.
92
+ # @option options [String] :time_zone When `type` is `Time` this specifies the time zone to append to
93
+ # `String` values assigned to this attribute prior to parsing them with `:format`. Optional.
82
94
  #
83
95
  # @macro [attach] attribute
84
96
  # The $1 attribute.
@@ -87,7 +99,8 @@ module IGMarkets
87
99
  define_attribute_reader name
88
100
  define_attribute_writer name, type, options
89
101
 
90
- (self.defined_attributes ||= {})[name] = options.merge type: type
102
+ self.defined_attributes ||= {}
103
+ self.defined_attributes[name] = options.merge type: type
91
104
  end
92
105
 
93
106
  # Creates a new Model instance from the specified source, which can take a variety of different forms.
@@ -120,7 +133,7 @@ module IGMarkets
120
133
  define_method "#{name}=" do |value|
121
134
  value = nil if Array(options.fetch(:nil_if, [])).include? value
122
135
 
123
- value = typecaster.call(value, options)
136
+ value = typecaster.call value, options
124
137
 
125
138
  allowed_values = options[:allowed_values]
126
139
  if !value.nil? && allowed_values
@@ -130,56 +143,6 @@ module IGMarkets
130
143
  (@attributes ||= {})[name] = value
131
144
  end
132
145
  end
133
-
134
- def typecaster_for(type)
135
- if [Boolean, String, Fixnum, Float, Symbol, DateTime].include? type
136
- method "typecaster_#{type.to_s.gsub(/\AIGMarkets::/, '').downcase}"
137
- elsif type.respond_to? :from
138
- -> (value, _options) { type.from value }
139
- end
140
- end
141
-
142
- def typecaster_boolean(value, _options)
143
- return value if [nil, true, false].include? value
144
-
145
- raise ArgumentError, "#{self}: invalid boolean value: #{value}"
146
- end
147
-
148
- def typecaster_string(value, options)
149
- return nil if value.nil?
150
-
151
- if options.key? :regex
152
- raise ArgumentError, "#{self}: invalid string value: #{value}" unless options[:regex].match value.to_s
153
- end
154
-
155
- value.to_s
156
- end
157
-
158
- def typecaster_fixnum(value, _options)
159
- value.nil? ? nil : value.to_s.to_i
160
- end
161
-
162
- def typecaster_float(value, _options)
163
- value.nil? ? nil : Float(value)
164
- end
165
-
166
- def typecaster_symbol(value, _options)
167
- value.nil? ? nil : value.to_s.downcase.to_sym
168
- end
169
-
170
- def typecaster_datetime(value, options)
171
- raise ArgumentError, "#{self}: invalid or missing date time format" unless options[:format].is_a? String
172
-
173
- if value.is_a?(String) || value.is_a?(Fixnum)
174
- begin
175
- DateTime.strptime(value.to_s, options[:format])
176
- rescue ArgumentError
177
- raise ArgumentError, "#{self}: failed parsing date '#{value}' with format '#{options[:format]}'"
178
- end
179
- else
180
- value
181
- end
182
- end
183
146
  end
184
147
  end
185
148
  end