em-nordnet 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ module Em
2
+ module Nordnet
3
+ class Config < Struct.new :url, :user, :password, :pem, :service
4
+ def credentials
5
+ @credentials ||= generate_credentials
6
+ end
7
+
8
+ private
9
+ def generate_credentials
10
+ auth = [user, password, Time.now.to_i*1000]
11
+ encoded = auth.map { |val| Base64.encode64 val.to_s.strip }.join(':')
12
+ Base64.encode64 OpenSSL::PKey::RSA.new(pem).public_encrypt encoded
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,53 @@
1
+ module Em
2
+ module Nordnet
3
+ module Feed
4
+ module Connection
5
+ attr_reader :session, :callbacks
6
+
7
+ def initialize(session)
8
+ @session = session
9
+ @callbacks = []
10
+ end
11
+
12
+ def on_tick &block
13
+ @callbacks << block
14
+ end
15
+
16
+ def subscribe args
17
+ send_data generate(:subscribe, args)
18
+ end
19
+
20
+ def connection_completed
21
+ start_tls
22
+ end
23
+
24
+ def ssl_handshake_completed
25
+ params = { session_key: session, service: Nordnet.config.service }
26
+ send_data generate(:login, params)
27
+ end
28
+
29
+ def receive_data json
30
+ callbacks.each { |callback| callback.call parse(json) }
31
+ end
32
+
33
+ def unbind
34
+ EventMachine::stop_event_loop
35
+ end
36
+
37
+ def send_data data
38
+ super "%s\n" % data # All writes must end with linefeed
39
+ end
40
+
41
+ private
42
+
43
+ def generate command, args ={}
44
+ MultiJson.dump cmd: command.to_s, args: args
45
+ end
46
+
47
+ def parse json
48
+ MultiJson.load(json, symbolize_keys: true)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,56 @@
1
+ module Em
2
+ module Nordnet
3
+ module Feed
4
+ class DSL
5
+ attr_reader :socket, :session, :callbacks
6
+
7
+ def initialize
8
+ @callbacks = initialize_callbacks
9
+
10
+ socket.on_tick { |data| run_callback data }
11
+ end
12
+
13
+ def socket
14
+ @socket = EventMachine::connect hostname, port, Connection, session
15
+ end
16
+
17
+ def session
18
+ @session ||= Nordnet.api.session
19
+ end
20
+
21
+ def on_heartbeat &block
22
+ add_callback :heartbeat, block
23
+ end
24
+
25
+ def add_callback type, block
26
+ @callbacks[type] << block
27
+ end
28
+
29
+ def types
30
+ raise NotImplementedError
31
+ end
32
+
33
+ def hostname
34
+ raise NotImplementedError
35
+ end
36
+
37
+ def port
38
+ raise NotImplementedError
39
+ end
40
+
41
+ private
42
+
43
+ def initialize_callbacks
44
+ types.inject heartbeat: [] do |callbacks, type|
45
+ callbacks[type] = []
46
+ end
47
+ end
48
+
49
+ def run_callback data
50
+ type, args = data[:cmd], data[:args]
51
+ callbacks[type].each { |callback| callback.call args }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,27 @@
1
+ module Em
2
+ module Nordnet
3
+ module Feed
4
+ class Private < Em::Nordnet::Feed::DSL
5
+ def types
6
+ [:trade, :order]
7
+ end
8
+
9
+ def hostname
10
+ Nordnet.api.private_feed[:hostname]
11
+ end
12
+
13
+ def port
14
+ Nordnet.api.private_feed[:port]
15
+ end
16
+
17
+ def on_trade &block
18
+ add_callback :trade, block
19
+ end
20
+
21
+ def on_order &block
22
+ add_callback :order, block
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,43 @@
1
+ module Em
2
+ module Nordnet
3
+ module Feed
4
+ class Public < Em::Nordnet::Feed::DSL
5
+ def types
6
+ [:price, :depth, :trade, :index, :news, :trading_status]
7
+ end
8
+
9
+ def hostname
10
+ Nordnet.api.public_feed[:hostname]
11
+ end
12
+
13
+ def port
14
+ Nordnet.api.public_feed[:port]
15
+ end
16
+
17
+ def on_price &block
18
+ add_callback :price, block
19
+ end
20
+
21
+ def on_trade &block
22
+ add_callback :trade, block
23
+ end
24
+
25
+ def on_depth &block
26
+ add_callback :depth, block
27
+ end
28
+
29
+ def on_index &block
30
+ add_callback :index, block
31
+ end
32
+
33
+ def on_news &block
34
+ add_callback :news, block
35
+ end
36
+
37
+ def on_trading_status &block
38
+ add_callback :trading_status, block
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ module Em::Nordnet::Goliath
2
+ class FeedPlugin
3
+ @@connection = nil
4
+
5
+ class << self
6
+ def connection
7
+ @@connection
8
+ end
9
+ end
10
+
11
+ def initialize port, config, status, logger
12
+ @port, @config, @status, @logger = port, config, status, logger
13
+ end
14
+
15
+ def run
16
+ @@connection = feed_connection
17
+ end
18
+
19
+ private
20
+
21
+ def feed_connection
22
+ raise NotImplementedError
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ module Em::Nordnet::Goliath
2
+ class PrivateFeed < FeedPlugin
3
+ private
4
+ def feed_connection
5
+ Nordnet::Feed::Private.new
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ module Em::Nordnet::Goliath
2
+ class PublicFeed < FeedPlugin
3
+ class << self
4
+ def subscribe type, instrument, &block
5
+ instrument.subscribe_params(type).tap do |args|
6
+ connection.add_callback type, &block
7
+ connection.subscribe args
8
+ end
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def feed_connection
15
+ Nordnet::Feed::Public.new
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ module Em
2
+ module Nordnet
3
+ class Instrument
4
+ attr_accessor :id, :market_id, :currency, :shortname, :is_in_code, :longname, :ticksize_id
5
+
6
+ def self.search symbol, options={}
7
+ search_options = {query: symbol, type: 'A', country: ['SE','US']}.merge options
8
+ results = Nordnet.api.instrument_search(search_options)
9
+ results.map { |attributes| new attributes }
10
+ end
11
+
12
+ def initialize attributes={}
13
+ self.id = attributes[:id] || attributes[:identifier]
14
+ self.market_id = attributes[:market_id] || attributes[:marketID]
15
+ self.currency = attributes[:currency]
16
+ self.shortname = attributes[:shortname]
17
+ self.longname = attributes[:longname]
18
+ self.is_in_code = attributes[:is_in_code] || attributes[:isinCode]
19
+ self.ticksize_id = attributes[:ticksize_id] || attributes[:ticksizeID]
20
+ end
21
+
22
+ def subscribe_params type
23
+ { 't' => type.to_s, 'i' => id, 'm' => market_id }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,48 @@
1
+ module Em
2
+ module Nordnet
3
+ class Order
4
+ BUY = 'BUY'.freeze
5
+ SELL = 'SELL'.freeze
6
+
7
+ attr_accessor :id, :account, :instrument, :price, :volume, :status
8
+
9
+ class << self
10
+ def all account_id
11
+ orders = Nordnet.api.orders(account_id)
12
+ orders.map { |attributes| new attributes }
13
+ end
14
+
15
+ def buy attributes={}
16
+ symbol, price, volume = attributes[:symbol], attributes[:price], attributes[:volume]
17
+
18
+ instrument = Instrument.search(symbol).first
19
+ account = attributes[:account] || Account.default
20
+
21
+ order = Order.new account: account, instrument: instrument, price: price, volume: volume
22
+ order.buy!
23
+ end
24
+ end
25
+
26
+ def initialize attributes={}
27
+ self.id = attributes[:id] || attributes[:orderID]
28
+ self.account = attributes[:account]
29
+ self.instrument = attributes[:instrument]
30
+ self.price = attributes[:price]
31
+ self.volume = attributes[:volume]
32
+ self.status = attributes[:status]
33
+ end
34
+
35
+ def buy!
36
+ self.status = Nordnet.api.create_order account.id,
37
+ identifier: instrument.id,
38
+ marketID: instrument.market_id,
39
+ price: price,
40
+ volume: volume,
41
+ side: BUY,
42
+ currency: instrument.currency
43
+
44
+ status
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,5 @@
1
+ module Em
2
+ module Nordnet
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Em::Nordnet::Account do
4
+ let(:attributes) { { id: 123, alias: 'alias', default: true } }
5
+ before { Em::Nordnet.stub(:api).and_return stub(accounts: [attributes]) }
6
+
7
+ subject { Em::Nordnet::Account.new attributes }
8
+
9
+ describe 'initialization' do
10
+ its(:id) { should == 123 }
11
+ its(:alias) { should == 'alias' }
12
+ its(:default) { should be_true }
13
+ end
14
+
15
+ describe '.all' do
16
+ it 'should returns a collection of accounts fetched with the API' do
17
+ Em::Nordnet::Account.all.first.should be_instance_of(Em::Nordnet::Account)
18
+ end
19
+ it 'should be memoized' do
20
+ Em::Nordnet::Account.all.should be Em::Nordnet::Account.all
21
+ end
22
+ end
23
+
24
+ it '.default', 'should return the account that is set to default' do
25
+ account = Em::Nordnet::Account.default
26
+ account.should be_instance_of Em::Nordnet::Account
27
+ account.default.should be_true
28
+ end
29
+
30
+ it '#orders', 'should return a collection of orders connected to the account' do
31
+ Em::Nordnet::Order.should_receive(:all).with(123)
32
+ subject.orders
33
+ end
34
+ end
@@ -0,0 +1,179 @@
1
+ require 'spec_helper'
2
+
3
+ describe Em::Nordnet::Api, type: 'api authenticated' do
4
+ let(:api) { Em::Nordnet::Api.new }
5
+ subject { api }
6
+
7
+ let(:account_id) { 4711 }
8
+
9
+ describe 'initialization' do
10
+ its(:credentials) { should_not be_nil }
11
+ its(:session) { should_not be_nil }
12
+ its(:expires_in) { should_not be_nil }
13
+ end
14
+
15
+ describe '#login' do
16
+ its(:session) { should == 'cf147c690f65fe1068f40ef23f23648ebf8c7f02' }
17
+ its(:expires_in) { should == 300 }
18
+ it { api.login.should == login_result }
19
+ end
20
+
21
+ describe '#touch' do
22
+ before { stub_touch(api.session) }
23
+ it { api.touch.should == touch_result }
24
+ end
25
+
26
+ describe '#realtime_access' do
27
+ before { stub_realtime_access }
28
+ it { api.realtime_access.should == realtime_access_result }
29
+ end
30
+
31
+ describe '#news_sources' do
32
+ before { stub_news_sources }
33
+ it { api.news_sources.should == news_sources_result }
34
+ end
35
+
36
+ describe '#news_items' do
37
+ let(:params) { {query: 'ERIC'} }
38
+ before { stub_news_items params }
39
+ it { api.news_items.should == news_items_result }
40
+ end
41
+
42
+ describe '#news_item' do
43
+ let(:news_item_id) { 213 }
44
+ before { stub_news_item news_item_id }
45
+ it { api.news_item(news_item_id).should == news_item_result }
46
+ end
47
+
48
+ describe '#accounts' do
49
+ before { stub_accounts }
50
+ it { api.accounts.should == accounts_result }
51
+ end
52
+
53
+ describe '#account' do
54
+ before { stub_account account_id }
55
+ it { api.account(account_id).should == account_result }
56
+ end
57
+
58
+ describe '#ledgers' do
59
+ before { stub_ledgers account_id }
60
+ it { api.ledgers(account_id).should == ledgers_result }
61
+ end
62
+
63
+ describe '#positions' do
64
+ before { stub_positions account_id }
65
+ it { api.positions(account_id).should == positions_result }
66
+ end
67
+
68
+ describe '#orders' do
69
+ before { stub_orders account_id }
70
+ it { api.orders(account_id).should == orders_result }
71
+ end
72
+
73
+ describe '#trades' do
74
+ before { stub_trades account_id }
75
+ it { api.trades(account_id).should == trades_result }
76
+ end
77
+
78
+ describe '#instrument_search' do
79
+ let(:params) { {query: 'abb', type: 'A', country: 'SE'} }
80
+ before { stub_instrument_search params }
81
+ it { api.instrument_search(params).should == instrument_search_result }
82
+ end
83
+
84
+ describe '#instrument_lookup' do
85
+ let(:params) { {identifier: 101, marketID: 11} }
86
+ before { stub_instrument_lookup params }
87
+ it { api.instrument_lookup(params).should == instrument_lookup_result }
88
+ end
89
+
90
+ describe '#multiple_instrument_lookup' do
91
+ let(:list) { "101,11;102,11;" }
92
+ before { stub_multiple_instrument_lookup list }
93
+ it { api.multiple_instrument_lookup(list).should == multiple_instrument_lookup_result }
94
+ end
95
+
96
+ describe '#chart_data' do
97
+ let(:params) { {identifier: 101, marketID: 11} }
98
+ before { stub_chart_data params }
99
+ it { api.chart_data(params).should == chart_data_result }
100
+ end
101
+
102
+ describe '#lists' do
103
+ before { stub_lists }
104
+ it { api.lists.should == lists_result }
105
+ end
106
+
107
+ describe '#list_items' do
108
+ let(:list_id) { 123 }
109
+ before { stub_list_items list_id }
110
+ it { api.list_items.should == list_items_result }
111
+ end
112
+
113
+ describe '#markets' do
114
+ before { stub_markets }
115
+ it { api.markets.should == markets_result }
116
+ end
117
+
118
+ describe '#trading_days' do
119
+ let(:market_id) { 32 }
120
+ before { stub_trading_days market_id }
121
+ it { api.trading_days(market_id).should == trading_days_result }
122
+ end
123
+
124
+ describe '#indices' do
125
+ before { stub_indices }
126
+ it { api.indices.should == indices_result }
127
+ end
128
+
129
+ describe '#ticksizes' do
130
+ let(:instrument_id) { 11002 }
131
+ before { stub_ticksizes instrument_id }
132
+ it { api.ticksizes(instrument_id).should == ticksizes_result }
133
+ end
134
+
135
+ describe '#countries' do
136
+ let(:derivative_type) { 'WNT' }
137
+ before { stub_countries derivative_type }
138
+ it { api.countries(derivative_type).should == countries_result }
139
+ end
140
+
141
+ describe '#underlyings' do
142
+ let(:derivative_type) { 'WNT' }
143
+ let(:country) { 'SE' }
144
+ before { stub_underlyings derivative_type, country }
145
+ it { api.underlyings(derivative_type, country).should == underlyings_result }
146
+ end
147
+
148
+ describe '#derivatives' do
149
+ let(:derivative_type) { 'WNT' }
150
+ let(:params) { {identifier: 101, marketID: 11} }
151
+ before { stub_derivatives derivative_type, params }
152
+ it { api.derivatives(derivative_type, params).should == derivatives_result }
153
+ end
154
+
155
+ describe '#related_markets' do
156
+ let(:params) { {identifier: 101, marketID: 11} }
157
+ before { stub_related_markets params }
158
+ it { api.related_markets(params).should == related_markets_result }
159
+ end
160
+
161
+ describe '#create_order' do
162
+ let(:params) { { identifier: 101, marketID: 11, price: 60, volume: 200, side: 'BUY', currency: 'SEK' } }
163
+ before { stub_create_order account_id, params }
164
+ it { api.create_order(account_id, params).should == create_order_result }
165
+ end
166
+
167
+ describe '#modify_order' do
168
+ let(:order_id) { 6670 }
169
+ let(:params) { { identifier: 101, marketID: 11, price: 100, volume: 200, side: 'BUY', currency: 'SEK' } }
170
+ before { stub_modify_order account_id, order_id, params }
171
+ it { api.modify_order(account_id, order_id, params).should == modify_order_result }
172
+ end
173
+
174
+ describe '#destroy_order' do
175
+ let(:order_id) { 6670 }
176
+ before { stub_destroy_order account_id, order_id }
177
+ it { api.destroy_order(account_id, order_id).should == destroy_order_result }
178
+ end
179
+ end