forex 0.1.1 → 0.2.0
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/.travis.yml +7 -1
- data/README.md +43 -10
- data/features/step_definitions/tabular_rates_steps.rb +42 -13
- data/features/support/env.rb +1 -0
- data/features/tabular_rates.feature +9 -6
- data/forex.gemspec +2 -0
- data/lib/forex.rb +2 -1
- data/lib/forex/tabular_rates.rb +30 -31
- data/lib/forex/traders/jm/bns.rb +1 -1
- data/lib/forex/traders/jm/cok.rb +24 -0
- data/lib/forex/traders/jm/fgb.rb +1 -1
- data/lib/forex/traders/jm/fxtraders.rb +8 -3
- data/lib/forex/traders/jm/jmmb.rb +7 -2
- data/lib/forex/traders/jm/jnbs.rb +1 -2
- data/lib/forex/traders/jm/ncb.rb +1 -1
- data/lib/forex/traders/jm/sagicor.rb +3 -3
- data/lib/forex/version.rb +1 -1
- data/spec/rates/cok.yml +20 -0
- data/spec/rates/fxtraders.yml +26 -0
- data/spec/vcr/traders/cok.yml +681 -0
- data/spec/vcr/traders/{sagicore.yml → sagicor.yml} +1 -1
- metadata +39 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0805a3b1ecebdfe37fb068a85dc133dd9e500007
|
4
|
+
data.tar.gz: 735001cf921eeeda36f4c1235adce750aff8000b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5cca23e919fe8b9745119b06430c644e7c33d4c5867bd9e844db861984f9a8d405661a0d3a3c2f2d560a19917a88198ac025e5e8b7e23c30fd6ae12c0cd2b23f
|
7
|
+
data.tar.gz: 5e7980edf74dd90a048f833ee74c1c3c280c26bcb223f6137fe45333aa1e6d83df87e5e8324e5c8b99daa6485c9f2b61d307282b05983b429fa72e26e35b3570
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
# Forex
|
2
2
|
|
3
|
+
[](https://codeclimate.com/github/mcmorgan/forex)
|
4
|
+
[](https://travis-ci.org/mcmorgan/forex)
|
5
|
+
[](https://gemnasium.com/mcmorgan/forex)
|
6
|
+
[](http://rubygems.org/gems/forex)
|
7
|
+
|
3
8
|
Provides a simple DSL for managing Foreign Exchange rates (forex) for various
|
4
|
-
traders.
|
9
|
+
traders (Inspired by [CurrrencyJA](https://github.com/kenrick/currencyja)).
|
5
10
|
|
6
11
|
## Installation
|
7
12
|
|
@@ -30,7 +35,7 @@ Forex::Trader.define "NCB" do |t|
|
|
30
35
|
t.name = "National Commercial Bank"
|
31
36
|
t.endpoint = "http://www.jncb.com/rates/foreignexchangerates"
|
32
37
|
|
33
|
-
t.rates_parser =
|
38
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
34
39
|
# process the doc and return rates hash in the following format
|
35
40
|
|
36
41
|
# {
|
@@ -46,18 +51,46 @@ end
|
|
46
51
|
|
47
52
|
# Rates may be fetched for a specific trader
|
48
53
|
Forex::Trader.all['BNS'].fetch
|
54
|
+
```
|
49
55
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
56
|
+
### TabularRates
|
57
|
+
|
58
|
+
`TabularRates` allows you to simplify the process of parsing a table of rates.
|
59
|
+
Simply find the table and pass it along with options of what values are in which
|
60
|
+
columns (zero indexed).
|
61
|
+
|
62
|
+
For example:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
#...
|
66
|
+
|
67
|
+
translations = {
|
68
|
+
'US$' => 'USD',
|
69
|
+
'TT$' => 'TTD',
|
70
|
+
}
|
71
|
+
|
72
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
73
|
+
|
74
|
+
options = {
|
75
|
+
currency_code: 1,
|
76
|
+
sell_cash: 2,
|
77
|
+
buy_cash: 4,
|
78
|
+
buy_draft: 3,
|
79
|
+
}
|
80
|
+
|
81
|
+
table = doc.css(".rates table").first
|
82
|
+
|
83
|
+
# translations is optional
|
84
|
+
Forex::TabularRates.new(table, options).parse_rates(translations)
|
85
|
+
end
|
86
|
+
|
87
|
+
#...
|
54
88
|
```
|
55
89
|
|
56
|
-
##
|
90
|
+
## TODO
|
57
91
|
|
58
|
-
*
|
59
|
-
*
|
60
|
-
* [](https://gemnasium.com/mcmorgan/forex)
|
92
|
+
* Make it possible to have the same trader in multiple countries
|
93
|
+
* Add other traders in Jamaica
|
61
94
|
|
62
95
|
## Contributing
|
63
96
|
|
@@ -7,7 +7,7 @@ When(/^the column options exist for the tabular rates parser:$/) do |options_tab
|
|
7
7
|
end
|
8
8
|
|
9
9
|
Then(/^parsing the table should return the following rates:$/) do |table|
|
10
|
-
|
10
|
+
assert_rates_are_equal_to table
|
11
11
|
end
|
12
12
|
|
13
13
|
Then(/^parsing the table should raise an exception with the message:$/) do |table|
|
@@ -15,20 +15,49 @@ Then(/^parsing the table should raise an exception with the message:$/) do |tabl
|
|
15
15
|
expect { parse_rates }.to raise_error(message)
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
When(/^the currency translations:$/) do |table|
|
19
|
+
@translations = table.raw.each_with_object({}) do |translation, h|
|
20
|
+
key, value = translation
|
21
|
+
h[key] = value
|
22
|
+
end
|
20
23
|
end
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
+
module KnowsTabularRates
|
26
|
+
include Forex
|
27
|
+
|
28
|
+
def translations
|
29
|
+
@translations || {}
|
30
|
+
end
|
25
31
|
|
26
|
-
|
27
|
-
|
28
|
-
currency, rate = *currency_rate
|
29
|
-
rates[currency] = rate.to_f == 0 ? nil : rate.to_f
|
30
|
-
end
|
32
|
+
def parse_rates
|
33
|
+
TabularRates.new(@table, @options).parse_rates(translations)
|
31
34
|
end
|
32
35
|
|
33
|
-
|
34
|
-
|
36
|
+
def assert_rates_are_equal_to(table)
|
37
|
+
table = table.dup # because we modify it below
|
38
|
+
|
39
|
+
convert_rates! table
|
40
|
+
|
41
|
+
formatted_rates = table.hashes.each_with_object({}) do |table_hash, currencies|
|
42
|
+
table_hash.symbolize_keys!
|
43
|
+
|
44
|
+
currencies[table_hash.delete(:currency_code)] =
|
45
|
+
table_hash.each_with_object({}) do |currency_rate, rates|
|
46
|
+
currency, rate = *currency_rate
|
47
|
+
rates[currency] = rate
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
parse_rates.should == formatted_rates
|
52
|
+
end
|
53
|
+
|
54
|
+
def convert_rates!(table)
|
55
|
+
zero_to_nil = ->(value) { value == 0 ? nil : value }
|
56
|
+
|
57
|
+
TabularRates::COLUMN_LABELS.each do |column_label|
|
58
|
+
table.map_column!(column_label) { |value| zero_to_nil.(value.to_f) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
World(KnowsTabularRates)
|
data/features/support/env.rb
CHANGED
@@ -9,12 +9,12 @@ Feature: Tabular Rates
|
|
9
9
|
Given the tabular rates table:
|
10
10
|
"""
|
11
11
|
<table>
|
12
|
-
<tr><td>
|
13
|
-
<tr><td>
|
14
|
-
<tr><td>
|
15
|
-
<tr><td>GBP</td> <td>166.20</td> <td>169.63</td> <td>173.30</td> </tr>
|
16
|
-
<tr><td>CAD</td> <td> 94.51</td> <td> 96.92</td> <td> 99.72</td> </tr>
|
17
|
-
<tr><td>
|
12
|
+
<tr><td> </td> <td>Cash </td> <td>Cheque</td> <td>Cash & Cheque</td></tr>
|
13
|
+
<tr><td> </td> <td>BUY </td> <td>BUY </td> <td>SELL </td> </tr>
|
14
|
+
<tr><td>US$ </td> <td>101.30</td> <td>103.30</td> <td>105.00</td> </tr>
|
15
|
+
<tr><td>GBP </td> <td>166.20</td> <td>169.63</td> <td>173.30</td> </tr>
|
16
|
+
<tr><td>CAD </td> <td> 94.51</td> <td> 96.92</td> <td> 99.72</td> </tr>
|
17
|
+
<tr><td>EURO</td> <td> </td> <td>137.42</td> <td>143.16</td> </tr>
|
18
18
|
</table>
|
19
19
|
"""
|
20
20
|
|
@@ -22,6 +22,9 @@ Feature: Tabular Rates
|
|
22
22
|
When the column options exist for the tabular rates parser:
|
23
23
|
| currency_code | buy_cash | buy_draft | sell_cash | sell_draft |
|
24
24
|
| 0 | 1 | 2 | 3 | 3 |
|
25
|
+
And the currency translations:
|
26
|
+
| US$ | USD |
|
27
|
+
| EURO | EUR |
|
25
28
|
Then parsing the table should return the following rates:
|
26
29
|
| currency_code | buy_cash | buy_draft | sell_cash | sell_draft |
|
27
30
|
| USD | 101.30 | 103.30 | 105.00 | 105.00 |
|
data/forex.gemspec
CHANGED
@@ -26,6 +26,8 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_development_dependency "cucumber"
|
27
27
|
spec.add_development_dependency "vcr"
|
28
28
|
spec.add_development_dependency "webmock"
|
29
|
+
spec.add_development_dependency "pry"
|
30
|
+
spec.add_development_dependency "fivemat"
|
29
31
|
|
30
32
|
spec.add_development_dependency "bundler", "~> 1.3"
|
31
33
|
spec.add_development_dependency "rake"
|
data/lib/forex.rb
CHANGED
@@ -7,4 +7,5 @@ require "forex/tabular_rates"
|
|
7
7
|
require "forex/trader"
|
8
8
|
|
9
9
|
# Traders are automatically loaded
|
10
|
-
|
10
|
+
traders_path = File.join(File.dirname(__FILE__), 'forex', 'traders', '**', '*.rb')
|
11
|
+
Dir[traders_path].each { |file| require file }
|
data/lib/forex/tabular_rates.rb
CHANGED
@@ -4,31 +4,34 @@ module Forex
|
|
4
4
|
|
5
5
|
attr_accessor :table, :options
|
6
6
|
|
7
|
+
COLUMN_LABELS = [
|
8
|
+
:buy_cash,
|
9
|
+
:buy_draft,
|
10
|
+
:sell_cash,
|
11
|
+
:sell_draft
|
12
|
+
]
|
13
|
+
|
7
14
|
DEFAULT_OPTIONS = {
|
8
15
|
currency_code: 0,
|
9
|
-
|
10
|
-
buy_draft: 2,
|
11
|
-
sell_cash: 3,
|
12
|
-
sell_draft: 4,
|
13
|
-
}
|
16
|
+
}.merge(Hash[(COLUMN_LABELS).zip(1..COLUMN_LABELS.size)])
|
14
17
|
|
15
18
|
def initialize(table, options = DEFAULT_OPTIONS)
|
16
19
|
@table = table
|
17
20
|
@options = options.symbolize_keys
|
18
21
|
end
|
19
22
|
|
20
|
-
def parse_rates
|
23
|
+
def parse_rates(translations = {})
|
21
24
|
currency = options.delete(:currency_code) || 0
|
22
25
|
|
23
26
|
table.css('tr').each_with_object({}) do |tr, currencies|
|
24
27
|
cells = tr.css('td')
|
25
28
|
next if cells.empty?
|
26
29
|
|
27
|
-
currency_code = CurrencyCode.new(cells[currency.to_i].content)
|
30
|
+
currency_code = CurrencyCode.new(cells[currency.to_i].content, translations)
|
28
31
|
|
29
32
|
next if currencies.has_key?(currency_code.to_s) || currency_code.invalid?
|
30
33
|
|
31
|
-
currencies[currency_code.to_s] =
|
34
|
+
currencies[currency_code.to_s] = COLUMN_LABELS.each_with_object({}) do |column_label, rates|
|
32
35
|
next unless rate_column = options[column_label]
|
33
36
|
|
34
37
|
rate_node = cells[rate_column.to_i]
|
@@ -39,35 +42,28 @@ module Forex
|
|
39
42
|
end
|
40
43
|
end
|
41
44
|
|
42
|
-
def column_labels
|
43
|
-
[:buy_cash, :buy_draft, :sell_cash, :sell_draft]
|
44
|
-
end
|
45
|
-
|
46
45
|
end
|
47
46
|
|
48
|
-
class Currency
|
49
|
-
|
50
|
-
def initialize(string)
|
51
|
-
@string = string
|
52
|
-
end
|
47
|
+
class Currency < Struct.new(:string)
|
53
48
|
|
54
49
|
# converts the currency to it's storage representation
|
55
50
|
def value
|
56
|
-
|
57
|
-
value == 0.0 ? nil : value
|
51
|
+
converted == 0.0 ? nil : converted
|
58
52
|
end
|
59
53
|
|
60
|
-
|
54
|
+
private
|
61
55
|
|
62
|
-
|
63
|
-
|
64
|
-
@string = string
|
56
|
+
def converted
|
57
|
+
@converted ||= string.strip.to_f
|
65
58
|
end
|
66
59
|
|
60
|
+
end
|
61
|
+
|
62
|
+
class CurrencyCode < Struct.new(:string, :translations)
|
63
|
+
|
67
64
|
# TODO validate the currency codes via http://www.xe.com/iso4217.php
|
68
65
|
def valid?
|
69
|
-
|
70
|
-
!@string.blank? && @string.length == 3
|
66
|
+
!translated.blank? && translated.length == 3
|
71
67
|
end
|
72
68
|
|
73
69
|
def invalid?
|
@@ -75,14 +71,17 @@ module Forex
|
|
75
71
|
end
|
76
72
|
|
77
73
|
def to_s
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
74
|
+
translated
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
82
78
|
|
83
|
-
|
84
|
-
|
79
|
+
def translated
|
80
|
+
@translated ||= translations[converted] || converted
|
81
|
+
end
|
85
82
|
|
83
|
+
def converted
|
84
|
+
@converted ||= string.strip
|
86
85
|
end
|
87
86
|
end
|
88
87
|
end
|
data/lib/forex/traders/jm/bns.rb
CHANGED
@@ -3,7 +3,7 @@ Forex::Trader.define "BNS" do |t|
|
|
3
3
|
t.name = "Bank of Nova Scotia"
|
4
4
|
t.endpoint = "http://www4.scotiabank.com/cgi-bin/ratesTool/depdisplay.cgi?pid=56"
|
5
5
|
|
6
|
-
t.rates_parser =
|
6
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
7
7
|
|
8
8
|
options = {
|
9
9
|
currency_code: 0,
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Forex::Trader.define "COK" do |t|
|
2
|
+
t.base_currency = "JMD"
|
3
|
+
t.name = "City Of Kingston Credit Union"
|
4
|
+
t.endpoint = "http://www.cokcu.com/otherservices/cambio/"
|
5
|
+
|
6
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
7
|
+
|
8
|
+
options = {
|
9
|
+
currency_code: 0,
|
10
|
+
buy_cash: 3,
|
11
|
+
buy_draft: 2,
|
12
|
+
sell_cash: 1,
|
13
|
+
}
|
14
|
+
|
15
|
+
table = doc.css(".table-bordered").first
|
16
|
+
|
17
|
+
translations = {
|
18
|
+
'CI' => 'KYD',
|
19
|
+
'Euro' => 'EUR',
|
20
|
+
}
|
21
|
+
|
22
|
+
Forex::TabularRates.new(table, options).parse_rates(translations)
|
23
|
+
end
|
24
|
+
end
|
data/lib/forex/traders/jm/fgb.rb
CHANGED
@@ -3,7 +3,7 @@ Forex::Trader.define "FGB" do |t|
|
|
3
3
|
t.name = "First Global Bank"
|
4
4
|
t.endpoint = "http://www.firstglobal-bank.com/"
|
5
5
|
|
6
|
-
t.rates_parser =
|
6
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
7
7
|
options = {
|
8
8
|
currency_code: 0,
|
9
9
|
buy_cash: 1,
|
@@ -3,7 +3,7 @@ Forex::Trader.define "FXTRADERS" do |t|
|
|
3
3
|
t.name = "FX Traders"
|
4
4
|
t.endpoint = "http://www.fxtrader.gkmsonline.com/rates"
|
5
5
|
|
6
|
-
t.rates_parser =
|
6
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
7
7
|
|
8
8
|
content_for = ->(type, n, part = nil) do
|
9
9
|
doc.css(
|
@@ -11,10 +11,15 @@ Forex::Trader.define "FXTRADERS" do |t|
|
|
11
11
|
).first.content
|
12
12
|
end
|
13
13
|
|
14
|
+
translations = {
|
15
|
+
'EURO' => 'EUR',
|
16
|
+
}
|
17
|
+
|
14
18
|
(1..5).each_with_object({}) do |n, currencies|
|
15
|
-
|
19
|
+
currency_code = content_for.(:currency, n)
|
20
|
+
currency_code = translations[currency_code] || currency_code
|
16
21
|
|
17
|
-
currencies[
|
22
|
+
currencies[currency_code] = {
|
18
23
|
buy_cash: content_for.(:buying, n).to_f,
|
19
24
|
buy_draft: content_for.(:buying, n, :b).to_f,
|
20
25
|
sell_cash: content_for.(:selling, n).to_f,
|
@@ -3,7 +3,7 @@ Forex::Trader.define "JMMB" do |t|
|
|
3
3
|
t.name = "Jamaica Money Market Brokers"
|
4
4
|
t.endpoint = "http://www.jmmb.com/full_rates.php"
|
5
5
|
|
6
|
-
t.rates_parser =
|
6
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
7
7
|
|
8
8
|
options = {
|
9
9
|
currency_code: 0,
|
@@ -13,12 +13,17 @@ Forex::Trader.define "JMMB" do |t|
|
|
13
13
|
sell_draft: 4
|
14
14
|
}
|
15
15
|
|
16
|
+
translations = {
|
17
|
+
'US$' => 'USD',
|
18
|
+
'TT$' => 'TTD',
|
19
|
+
}
|
20
|
+
|
16
21
|
table =
|
17
22
|
doc.search("[text()*='FX Trading Rates']").first. # Section with rates
|
18
23
|
ancestors('table').first. # Root table for section
|
19
24
|
css("table").first # Rates table
|
20
25
|
|
21
|
-
Forex::TabularRates.new(table, options).parse_rates
|
26
|
+
Forex::TabularRates.new(table, options).parse_rates(translations)
|
22
27
|
end
|
23
28
|
end
|
24
29
|
|
@@ -3,10 +3,9 @@ Forex::Trader.define "JNBS" do |t|
|
|
3
3
|
t.name = "Jamaica National Building Society"
|
4
4
|
t.endpoint = "http://www.jnbs.com/fx-rates-2"
|
5
5
|
|
6
|
-
t.rates_parser =
|
6
|
+
t.rates_parser = ->(doc) do # doc is a nokogiri document
|
7
7
|
|
8
8
|
options = {
|
9
|
-
currency_code: 0,
|
10
9
|
sell_cash: 4,
|
11
10
|
buy_cash: 2,
|
12
11
|
buy_draft: 1,
|