unageanu-clickclient_scrap 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README +8 -2
- data/lib/clickclient_scrap.rb +231 -74
- data/lib/jiji_plugin.rb +130 -0
- data/test/test_FxSession.rb +38 -0
- data/test/test_jiji_plugin.rb +77 -0
- metadata +19 -6
data/README
CHANGED
data/lib/clickclient_scrap.rb
CHANGED
@@ -5,6 +5,7 @@ end
|
|
5
5
|
require 'mechanize'
|
6
6
|
require 'date'
|
7
7
|
require 'kconv'
|
8
|
+
require 'set'
|
8
9
|
|
9
10
|
#
|
10
11
|
#=== クリック証券アクセスクライアント
|
@@ -17,10 +18,10 @@ require 'kconv'
|
|
17
18
|
#
|
18
19
|
#====基本的な使い方
|
19
20
|
#
|
20
|
-
# require '
|
21
|
+
# require 'clickclient_scrap'
|
21
22
|
#
|
22
23
|
# c = ClickClient::Client.new
|
23
|
-
# # c =
|
24
|
+
# # c = ClickClientScrap::Client.new https://<プロキシホスト>:<プロキシポート> # プロキシを利用する場合
|
24
25
|
# c.fx_session( "<ユーザー名>", "<パスワード>" ) { | fx_session |
|
25
26
|
# # 通貨ペア一覧取得
|
26
27
|
# list = fx_session.list_rates
|
@@ -31,7 +32,7 @@ require 'kconv'
|
|
31
32
|
#- 本ライブラリの利用は自己責任でお願いします。
|
32
33
|
#- ライブラリの不備・不具合等によるあらゆる損害について、作成者は責任を負いません。
|
33
34
|
#
|
34
|
-
module
|
35
|
+
module ClickClientScrap
|
35
36
|
|
36
37
|
# クライアント
|
37
38
|
class Client
|
@@ -44,7 +45,7 @@ module ClickClient
|
|
44
45
|
#*proxy*:: プロキシホストを利用する場合、そのホスト名とパスを指定します。
|
45
46
|
# 例) https://proxyhost.com:80
|
46
47
|
#
|
47
|
-
def initialize( proxy=nil
|
48
|
+
def initialize( proxy=nil )
|
48
49
|
@client = WWW::Mechanize.new {|c|
|
49
50
|
# プロキシ
|
50
51
|
if proxy
|
@@ -52,25 +53,30 @@ module ClickClient
|
|
52
53
|
c.set_proxy( uri.host, uri.port )
|
53
54
|
end
|
54
55
|
}
|
56
|
+
@client.keep_alive = false
|
55
57
|
@client.user_agent_alias = 'Windows IE 7'
|
56
58
|
@host_name = DEFAULT_HOST_NAME
|
57
59
|
end
|
58
60
|
|
59
61
|
#ログインし、セッションを開始します。
|
60
62
|
#-ブロックを指定した場合、引数としてセッションを指定してブロックを実行します。ブロック実行後、ログアウトします。
|
61
|
-
#-そうでない場合、セッションを返却します。この場合、
|
63
|
+
#-そうでない場合、セッションを返却します。この場合、ClickClientScrap::FX::FxSession#logoutを実行しログアウトしてください。
|
62
64
|
#
|
63
|
-
|
64
|
-
|
65
|
+
#userid:: ユーザーID
|
66
|
+
#password:: パスワード
|
67
|
+
#options:: オプション
|
68
|
+
#戻り値:: ClickClientScrap::FX::FxSession
|
69
|
+
def fx_session( userid, password, options={}, &block )
|
65
70
|
page = @client.get(@host_name)
|
66
|
-
|
71
|
+
ClickClientScrap::Client.error(page) if page.forms.length <= 0
|
67
72
|
form = page.forms.first
|
68
73
|
form.j_username = userid
|
69
74
|
form.j_password = password
|
70
75
|
result = @client.submit(form, form.buttons.first)
|
71
76
|
if result.body.toutf8 =~ /<META HTTP-EQUIV="REFRESH" CONTENT="0;URL=([^"]*)">/
|
72
77
|
result = @client.get($1)
|
73
|
-
|
78
|
+
ClickClientScrap::Client.error( result ) if result.links.size <= 0
|
79
|
+
session = FX::FxSession.new( @client, result.links, options )
|
74
80
|
if block_given?
|
75
81
|
begin
|
76
82
|
yield session
|
@@ -81,7 +87,7 @@ module ClickClient
|
|
81
87
|
return session
|
82
88
|
end
|
83
89
|
else
|
84
|
-
|
90
|
+
ClickClientScrap::Client.error( result )
|
85
91
|
end
|
86
92
|
end
|
87
93
|
def self.error( page )
|
@@ -177,21 +183,26 @@ module ClickClient
|
|
177
183
|
#Client#fx_sessionのブロックの引数として渡されます。詳細はClient#fx_sessionを参照ください。
|
178
184
|
class FxSession
|
179
185
|
|
180
|
-
def initialize( client, links )
|
186
|
+
def initialize( client, links, options={} )
|
181
187
|
@client = client
|
182
188
|
@links = links
|
189
|
+
@options = options
|
183
190
|
end
|
184
191
|
|
185
192
|
#レート一覧を取得します。
|
186
193
|
#
|
187
|
-
#戻り値:: 通貨ペアをキーとする
|
194
|
+
#戻り値:: 通貨ペアをキーとするClickClientScrap::FX::Rateのハッシュ。
|
188
195
|
def list_rates
|
189
|
-
result =
|
190
|
-
|
191
|
-
|
192
|
-
|
196
|
+
result = link_click( "1" )
|
197
|
+
if !@last_update_time_of_swaps \
|
198
|
+
|| Time.now.to_i - @last_update_time_of_swaps > (@options[:swap_update_interval] || 60*60)
|
199
|
+
@swaps = list_swaps
|
200
|
+
@last_update_time_of_swaps = Time.now.to_i
|
201
|
+
end
|
193
202
|
reg = />([A-Z]+\/[A-Z]+)<\/a>[^\-\.\d]*?([\d]+\.[\d]+\-[\d]+)/
|
194
|
-
|
203
|
+
tokens = result.body.toutf8.scan( reg )
|
204
|
+
ClickClientScrap::Client.error( result ) if !tokens || tokens.empty?
|
205
|
+
return tokens.inject({}) {|r,l|
|
195
206
|
pair = to_pair( l[0] )
|
196
207
|
swap = @swaps[pair]
|
197
208
|
rate = FxSession.convert_rate l[1]
|
@@ -219,11 +230,9 @@ module ClickClient
|
|
219
230
|
|
220
231
|
#スワップの一覧を取得します。
|
221
232
|
#
|
222
|
-
#戻り値:: 通貨ペアをキーとする
|
233
|
+
#戻り値:: 通貨ペアをキーとするClickClientScrap::FX::Swapのハッシュ。
|
223
234
|
def list_swaps
|
224
|
-
result =
|
225
|
-
i.attributes["accesskey"] == "8"
|
226
|
-
})
|
235
|
+
result = link_click( "8" )
|
227
236
|
reg = /<dd>([A-Z]+\/[A-Z]+) <font[^>]*>売<\/font>[^\-\d]*?([\-\d]+)[^\-\d]*<font[^>]*>買<\/font>[^\-\d]*([\-\d]+)[^\-\d]*<\/dd>/
|
228
237
|
return result.body.toutf8.scan( reg ).inject({}) {|r,l|
|
229
238
|
pair = to_pair( l[0] )
|
@@ -235,46 +244,46 @@ module ClickClient
|
|
235
244
|
#注文を行います。
|
236
245
|
#
|
237
246
|
#currency_pair_code:: 通貨ペアコード(必須)
|
238
|
-
#sell_or_buy:: 売買区分。
|
247
|
+
#sell_or_buy:: 売買区分。ClickClientScrap::FX::BUY,ClickClientScrap::FX::SELLのいずれかを指定します。(必須)
|
239
248
|
#unit:: 取引数量(必須)
|
240
249
|
#options:: 注文のオプション。注文方法に応じて以下の情報を設定できます。
|
241
250
|
# - <b>成り行き注文</b>
|
242
251
|
# - <tt>:slippage</tt> .. スリッページ (オプション)。何pips以内かを整数で指定します。
|
243
252
|
# - <b>通常注文</b> ※注文レートが設定されていれば通常取引となります。
|
244
253
|
# - <tt>:rate</tt> .. 注文レート(必須)
|
245
|
-
# - <tt>:execution_expression</tt> .. 執行条件。
|
246
|
-
# - <tt>:expiration_type</tt> .. 有効期限。
|
247
|
-
# - <tt>:expiration_date</tt> .. 有効期限が「日付指定(
|
254
|
+
# - <tt>:execution_expression</tt> .. 執行条件。ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
|
255
|
+
# - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
|
256
|
+
# - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
|
248
257
|
# - <b>OCO注文</b> ※逆指値レートが設定されていればOCO取引となります。
|
249
258
|
# - <tt>:rate</tt> .. 注文レート(必須)
|
250
259
|
# - <tt>:stop_order_rate</tt> .. 逆指値レート(必須)
|
251
|
-
# - <tt>:expiration_type</tt> .. 有効期限。
|
252
|
-
# - <tt>:expiration_date</tt> .. 有効期限が「日付指定(
|
260
|
+
# - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
|
261
|
+
# - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
|
253
262
|
# - <b>IFD注文</b> ※決済取引の指定があればIFD取引となります。
|
254
263
|
# - <tt>:rate</tt> .. 注文レート(必須)
|
255
|
-
# - <tt>:execution_expression</tt> .. 執行条件。
|
256
|
-
# - <tt>:expiration_type</tt> .. 有効期限。
|
257
|
-
# - <tt>:expiration_date</tt> .. 有効期限が「日付指定(
|
264
|
+
# - <tt>:execution_expression</tt> .. 執行条件。ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
|
265
|
+
# - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
|
266
|
+
# - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
|
258
267
|
# - <tt>:settle</tt> .. 決済取引の指定。マップで指定します。
|
259
268
|
# - <tt>:unit</tt> .. 決済取引の取引数量(必須)
|
260
|
-
# - <tt>:sell_or_buy</tt> .. 決済取引の売買区分。
|
269
|
+
# - <tt>:sell_or_buy</tt> .. 決済取引の売買区分。ClickClientScrap::FX::BUY,ClickClientScrap::FX::SELLのいずれかを指定します。(必須)
|
261
270
|
# - <tt>:rate</tt> .. 決済取引の注文レート(必須)
|
262
|
-
# - <tt>:execution_expression</tt> .. 決済取引の執行条件。
|
263
|
-
# - <tt>:expiration_type</tt> .. 決済取引の有効期限。
|
264
|
-
# - <tt>:expiration_date</tt> .. 決済取引の有効期限が「日付指定(
|
271
|
+
# - <tt>:execution_expression</tt> .. 決済取引の執行条件。ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
|
272
|
+
# - <tt>:expiration_type</tt> .. 決済取引の有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
|
273
|
+
# - <tt>:expiration_date</tt> .. 決済取引の有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
|
265
274
|
# - <b>IFD-OCO注文</b> ※決済取引の指定と逆指値レートの指定があればIFD-OCO取引となります。
|
266
275
|
# - <tt>:rate</tt> .. 注文レート(必須)
|
267
|
-
# - <tt>:execution_expression</tt> .. 執行条件。
|
268
|
-
# - <tt>:expiration_type</tt> .. 有効期限。
|
269
|
-
# - <tt>:expiration_date</tt> .. 有効期限が「日付指定(
|
276
|
+
# - <tt>:execution_expression</tt> .. 執行条件。ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
|
277
|
+
# - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
|
278
|
+
# - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
|
270
279
|
# - <tt>:settle</tt> .. 決済取引の指定。マップで指定します。
|
271
280
|
# - <tt>:unit</tt> .. 決済取引の取引数量(必須)
|
272
|
-
# - <tt>:sell_or_buy</tt> .. 決済取引の売買区分。
|
281
|
+
# - <tt>:sell_or_buy</tt> .. 決済取引の売買区分。ClickClientScrap::FX::BUY,ClickClientScrap::FX::SELLのいずれかを指定します。(必須)
|
273
282
|
# - <tt>:rate</tt> .. 決済取引の注文レート(必須)
|
274
283
|
# - <tt>:stop_order_rate</tt> .. 決済取引の逆指値レート(必須)
|
275
|
-
# - <tt>:expiration_type</tt> .. 決済取引の有効期限。
|
276
|
-
# - <tt>:expiration_date</tt> .. 決済取引の有効期限が「日付指定(
|
277
|
-
#戻り値::
|
284
|
+
# - <tt>:expiration_type</tt> .. 決済取引の有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
|
285
|
+
# - <tt>:expiration_date</tt> .. 決済取引の有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
|
286
|
+
#戻り値:: ClickClientScrap::FX::OrderResult
|
278
287
|
#
|
279
288
|
def order ( currency_pair_code, sell_or_buy, unit, options={} )
|
280
289
|
|
@@ -312,11 +321,14 @@ module ClickClient
|
|
312
321
|
# 成り行き
|
313
322
|
type = ORDER_TYPE_MARKET_ORDER
|
314
323
|
end
|
315
|
-
|
324
|
+
|
325
|
+
#注文前の注文一覧
|
326
|
+
before = list_orders( ORDER_CONDITION_ON_ORDER ).inject(Set.new) {|s,o| s << o[0]; s }
|
327
|
+
|
316
328
|
# レート一覧
|
317
|
-
result =
|
318
|
-
|
319
|
-
|
329
|
+
result = link_click( "1" )
|
330
|
+
|
331
|
+
ClickClientScrap::Client.error( result ) if result.forms.empty?
|
320
332
|
form = result.forms.first
|
321
333
|
|
322
334
|
# 通貨ペア
|
@@ -331,27 +343,28 @@ module ClickClient
|
|
331
343
|
|
332
344
|
# 詳細設定画面へ
|
333
345
|
result = @client.submit(form)
|
346
|
+
ClickClientScrap::Client.error( result ) if result.forms.empty?
|
334
347
|
form = result.forms.first
|
335
348
|
case type
|
336
349
|
when ORDER_TYPE_MARKET_ORDER
|
337
350
|
# 成り行き
|
338
351
|
form["P002"] = unit.to_s # 取り引き数量
|
339
|
-
form["P005.0"] = sell_or_buy ==
|
352
|
+
form["P005.0"] = sell_or_buy == ClickClientScrap::FX::SELL ? "1" : "0" #売り/買い
|
340
353
|
form["P007"] = options[:slippage].to_s if ( options && options[:slippage] != nil ) # スリッページ
|
341
354
|
when ORDER_TYPE_NORMAL
|
342
355
|
# 指値
|
343
356
|
form["P003"] = options[:rate].to_s # レート
|
344
357
|
form["P005"] = unit.to_s # 取り引き数量
|
345
|
-
form["P002.0"] = sell_or_buy ==
|
358
|
+
form["P002.0"] = sell_or_buy == ClickClientScrap::FX::SELL ? "1" : "0" #売り/買い
|
346
359
|
exp = options[:execution_expression]
|
347
|
-
form["P004.0"] = exp ==
|
360
|
+
form["P004.0"] = exp == ClickClientScrap::FX::EXECUTION_EXPRESSION_REVERSE_LIMIT_ORDER ? "2" : "1" #指値/逆指値
|
348
361
|
set_expiration( form, options, "P008", "P009" ) # 有効期限
|
349
362
|
when ORDER_TYPE_OCO
|
350
363
|
# OCO
|
351
364
|
form["P003"] = options[:rate].to_s # レート
|
352
365
|
form["P005"] = options[:stop_order_rate].to_s # 逆指値レート
|
353
366
|
form["P007"] = unit.to_s # 取り引き数量
|
354
|
-
form["P002.0"] = sell_or_buy ==
|
367
|
+
form["P002.0"] = sell_or_buy == ClickClientScrap::FX::SELL ? "1" : "0" #売り/買い
|
355
368
|
set_expiration( form, options, "P010", "P011" ) # 有効期限
|
356
369
|
else
|
357
370
|
raise "not supported yet."
|
@@ -359,10 +372,14 @@ module ClickClient
|
|
359
372
|
|
360
373
|
# 確認画面へ
|
361
374
|
result = @client.submit(form)
|
375
|
+
ClickClientScrap::Client.error( result ) if result.forms.empty?
|
362
376
|
result = @client.submit(result.forms.first)
|
363
|
-
|
364
|
-
|
365
|
-
|
377
|
+
ClickClientScrap::Client.error( result ) unless result.body.toutf8 =~ /注文受付完了/
|
378
|
+
|
379
|
+
#注文前の一覧と注文後の一覧を比較して注文を割り出す。
|
380
|
+
#成り行き注文の場合、即座に約定するのでnilになる(タイミングによっては取得できるかも)
|
381
|
+
tmp = list_orders( ORDER_CONDITION_ON_ORDER ).find {|o| !before.include?(o[0]) }
|
382
|
+
return OrderResult.new( tmp ? tmp[1].order_no : nil )
|
366
383
|
end
|
367
384
|
|
368
385
|
# 有効期限を設定する
|
@@ -372,11 +389,11 @@ module ClickClient
|
|
372
389
|
#input_date:: 有効期限が日付指定の場合に、日付を入力するinput要素名
|
373
390
|
def set_expiration( form, options, input_type, input_date )
|
374
391
|
case options[:expiration_type]
|
375
|
-
when
|
392
|
+
when ClickClientScrap::FX::EXPIRATION_TYPE_TODAY
|
376
393
|
form[input_type] = "0"
|
377
|
-
when
|
394
|
+
when ClickClientScrap::FX::EXPIRATION_TYPE_WEEK_END
|
378
395
|
form[input_type] = "1"
|
379
|
-
when
|
396
|
+
when ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED
|
380
397
|
form[input_type] = "3"
|
381
398
|
raise "options[:expiration_date] is required." unless options[:expiration_date]
|
382
399
|
form["#{input_date}.Y"] = options[:expiration_date].year
|
@@ -388,50 +405,176 @@ module ClickClient
|
|
388
405
|
end
|
389
406
|
end
|
390
407
|
|
408
|
+
#
|
409
|
+
#=== 注文をキャンセルします。
|
410
|
+
#
|
411
|
+
#order_no:: 注文番号
|
412
|
+
#戻り値:: なし
|
413
|
+
#
|
414
|
+
def cancel_order( order_no )
|
415
|
+
|
416
|
+
raise "order_no is nil." unless order_no
|
417
|
+
|
418
|
+
# 注文一覧
|
419
|
+
result = link_click( "2" )
|
420
|
+
ClickClientScrap::Client.error( result ) if result.forms.empty?
|
421
|
+
form = result.forms.first
|
422
|
+
form["P002"] = ORDER_CONDITION_ON_ORDER
|
423
|
+
result = @client.submit(form)
|
424
|
+
|
425
|
+
# 対象となる注文をクリック
|
426
|
+
link = result.links.find {|l|
|
427
|
+
l.href =~ /[^"]*GKEY=([a-zA-Z0-9]*)[^"]*/ && $1 == order_no
|
428
|
+
}
|
429
|
+
raise "illegal order_no. order_no=#{order_no}" unless link
|
430
|
+
result = @client.click(link)
|
431
|
+
ClickClientScrap::Client.error( result ) if result.forms.empty?
|
432
|
+
|
433
|
+
# キャンセル
|
434
|
+
form = result.forms[1]
|
435
|
+
result = @client.submit(form)
|
436
|
+
ClickClientScrap::Client.error( result ) if result.forms.empty?
|
437
|
+
form = result.forms.first
|
438
|
+
result = @client.submit(form)
|
439
|
+
ClickClientScrap::Client.error( result ) unless result.body.toutf8 =~ /注文取消受付完了/
|
440
|
+
end
|
441
|
+
|
442
|
+
|
443
|
+
#
|
444
|
+
#=== 決済注文を行います。
|
445
|
+
#
|
446
|
+
#*open_interest_id*:: 決済する建玉番号
|
447
|
+
#*unit*:: 取引数量
|
448
|
+
#*options*:: 決済注文のオプション。注文方法に応じて以下の情報を設定できます。
|
449
|
+
# - <b>成り行き注文</b>
|
450
|
+
# - <tt>:slippage</tt> .. スリッページ (オプション)
|
451
|
+
# - <tt>:slippage_base_rate</tt> .. スリッページの基準となる取引レート(スリッページが指定された場合、必須。)
|
452
|
+
# - <b>通常注文</b> ※注文レートが設定されていれば通常取引となります。
|
453
|
+
# - <tt>:rate</tt> .. 注文レート(必須)
|
454
|
+
# - <tt>:execution_expression</tt> .. 執行条件。ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
|
455
|
+
# - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
|
456
|
+
# - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
|
457
|
+
# - <b>OCO注文</b> ※注文レートと逆指値レートが設定されていればOCO取引となります。
|
458
|
+
# - <tt>:rate</tt> .. 注文レート(必須)
|
459
|
+
# - <tt>:stop_order_rate</tt> .. 逆指値レート(必須)
|
460
|
+
# - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
|
461
|
+
# - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
|
462
|
+
#<b>戻り値</b>:: なし
|
463
|
+
#
|
464
|
+
def settle ( open_interest_id, unit, options={} )
|
465
|
+
if ( options[:rate] != nil && options[:stop_order_rate] != nil )
|
466
|
+
# レートと逆指値レートが指定されていればOCO取引
|
467
|
+
raise "options[:expiration_type] is required." if options[:expiration_type] == nil
|
468
|
+
elsif ( options[:rate] != nil )
|
469
|
+
# レートが指定されていれば通常取引
|
470
|
+
raise "options[:execution_expression] is required." if options[:execution_expression] == nil
|
471
|
+
raise "options[:expiration_type] is required." if options[:expiration_type] == nil
|
472
|
+
else
|
473
|
+
# 成り行き
|
474
|
+
if ( options[:slippage] != nil )
|
475
|
+
raise "if you use a slippage, options[:slippage_base_rate] is required." if options[:slippage_base_rate] == nil
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
# 建玉一覧
|
480
|
+
result = link_click( "3" )
|
481
|
+
|
482
|
+
# 対象となる建玉をクリック
|
483
|
+
link = result.links.find {|l|
|
484
|
+
l.href =~ /[^"]*ORDERNO=([a-zA-Z0-9]*)[^"]*/ && $1 == open_interest_id
|
485
|
+
}
|
486
|
+
raise "illegal open_interest_id. open_interest_id=#{open_interest_id}" unless link
|
487
|
+
result = @client.click(link)
|
488
|
+
|
489
|
+
# 決済
|
490
|
+
form = result.forms.first
|
491
|
+
form["P100"] = "00" # 成り行き TODO 通常(01),OCO取引(21)対応
|
492
|
+
result = @client.submit(form)
|
493
|
+
ClickClientScrap::Client.error( result ) if result.forms.empty?
|
494
|
+
|
495
|
+
# 設定
|
496
|
+
form = result.forms.first
|
497
|
+
form["L111"] = unit.to_s
|
498
|
+
form["P005"] = options[:slippage].to_s if options[:slippage]
|
499
|
+
result = @client.submit(form)
|
500
|
+
ClickClientScrap::Client.error( result ) if result.forms.empty?
|
501
|
+
|
502
|
+
# 確認
|
503
|
+
form = result.forms.first
|
504
|
+
result = @client.submit(form)
|
505
|
+
ClickClientScrap::Client.error( result ) unless result.body.toutf8 =~ /完了/
|
506
|
+
end
|
507
|
+
|
508
|
+
|
391
509
|
#
|
392
510
|
#=== 注文一覧を取得します。
|
393
511
|
#
|
394
512
|
#order_condition_code:: 注文状況コード(必須)
|
395
513
|
#currency_pair_code:: 通貨ペアコード
|
396
|
-
#戻り値:: 注文番号をキーとする
|
514
|
+
#戻り値:: 注文番号をキーとするClickClientScrap::FX::Orderのハッシュ。
|
397
515
|
#
|
398
|
-
def list_orders( order_condition_code=
|
399
|
-
|
400
|
-
|
401
|
-
i.attributes["accesskey"] == "2"
|
402
|
-
})
|
516
|
+
def list_orders( order_condition_code=ClickClientScrap::FX::ORDER_CONDITION_ALL, currency_pair_code=nil )
|
517
|
+
result = link_click( "2" )
|
518
|
+
ClickClientScrap::Client.error( result ) if result.forms.empty?
|
403
519
|
form = result.forms.first
|
404
520
|
form["P001"] = "" # TODO currency_pair_codeでの絞り込み
|
405
521
|
form["P002"] = order_condition_code
|
406
|
-
|
522
|
+
result = @client.submit(form)
|
523
|
+
|
407
524
|
list = result.body.toutf8.scan( /<a href="[^"]*GKEY=([a-zA-Z0-9]*)">([A-Z]{3}\/[A-Z]{3}) ([^<]*)<\/a><br>[^;]*;([^<]*)<font[^>]*>([^<]*)<\/font>([^@]*)@([\d\.]*)([^\s]*) ([^<]*)<br>/m )
|
408
525
|
tmp = {}
|
409
526
|
list.each {|i|
|
410
527
|
order_no = i[0]
|
411
528
|
order_type = to_order_type_code(i[2])
|
412
|
-
trade_type = i[3] == "新" ?
|
529
|
+
trade_type = i[3] == "新" ? ClickClientScrap::FX::TRADE_TYPE_NEW : ClickClientScrap::FX::TRADE_TYPE_SETTLEMENT
|
413
530
|
pair = to_pair( i[1] )
|
414
|
-
sell_or_buy = i[4] == "売" ?
|
531
|
+
sell_or_buy = i[4] == "売" ? ClickClientScrap::FX::SELL : ClickClientScrap::FX::BUY
|
415
532
|
count = pair == :ZARJPY ? i[5].to_i/10 : i[5].to_i
|
416
533
|
rate = i[6].to_f
|
417
534
|
execution_expression = if i[7] == "指"
|
418
|
-
|
535
|
+
ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER
|
419
536
|
elsif i[7] == "逆"
|
420
|
-
|
537
|
+
ClickClientScrap::FX::EXECUTION_EXPRESSION_REVERSE_LIMIT_ORDER
|
421
538
|
else
|
422
|
-
|
539
|
+
ClickClientScrap::FX::EXECUTION_EXPRESSION_MARKET_ORDER
|
423
540
|
end
|
424
541
|
tmp[order_no] = Order.new( order_no, trade_type, order_type, execution_expression, sell_or_buy, pair, count, rate, i[8])
|
425
542
|
}
|
426
543
|
return tmp
|
427
544
|
end
|
428
545
|
|
546
|
+
#
|
547
|
+
#=== 建玉一覧を取得します。
|
548
|
+
#
|
549
|
+
#currency_pair_code:: 通貨ペアコード
|
550
|
+
#戻り値:: 建玉IDをキーとするClickClientScrap::FX::OpenInterestのハッシュ。
|
551
|
+
#
|
552
|
+
def list_open_interests( currency_pair_code=nil )
|
553
|
+
result = link_click( "3" )
|
554
|
+
ClickClientScrap::Client.error( result ) if result.forms.empty?
|
555
|
+
form = result.forms.first
|
556
|
+
form["P001"] = "" # TODO currency_pair_codeでの絞り込み
|
557
|
+
result = @client.submit(form)
|
558
|
+
|
559
|
+
list = result.body.toutf8.scan( /<a href="[^"]*">([A-Z]{3}\/[A-Z]{3}):([^<]*)<\/a><br>[^;]*;<font[^>]*>([^<]*)<\/font>([\d\.]*)[^\s@]*@([\d\.]*).*?<font[^>]*>([^<]*)<\/font>/m )
|
560
|
+
tmp = {}
|
561
|
+
list.each {|i|
|
562
|
+
open_interest_id = i[1]
|
563
|
+
pair = to_pair( i[0] )
|
564
|
+
sell_or_buy = i[2] == "売" ? ClickClientScrap::FX::SELL : ClickClientScrap::FX::BUY
|
565
|
+
count = i[3].to_i
|
566
|
+
rate = i[4].to_f
|
567
|
+
profit_or_loss = i[5].to_i
|
568
|
+
tmp[open_interest_id] = OpenInterest.new(open_interest_id, pair, sell_or_buy, count, rate, profit_or_loss )
|
569
|
+
}
|
570
|
+
return tmp
|
571
|
+
end
|
429
572
|
|
430
573
|
# ログアウトします。
|
431
574
|
def logout
|
432
575
|
@client.click( @links.find {|i|
|
433
576
|
i.text == "\303\233\302\270\303\236\302\261\302\263\303\204"
|
434
|
-
})
|
577
|
+
})
|
435
578
|
end
|
436
579
|
|
437
580
|
private
|
@@ -444,28 +587,42 @@ module ClickClient
|
|
444
587
|
def to_order_type_code( order_type )
|
445
588
|
return case order_type
|
446
589
|
when "成行注文"
|
447
|
-
|
590
|
+
ClickClientScrap::FX::ORDER_TYPE_MARKET_ORDER
|
448
591
|
when "通常注文"
|
449
|
-
|
592
|
+
ClickClientScrap::FX::ORDER_TYPE_NORMAL
|
450
593
|
when "OCO注文"
|
451
|
-
|
594
|
+
ClickClientScrap::FX::ORDER_TYPE_OCO
|
452
595
|
when "IFD注文"
|
453
|
-
|
596
|
+
ClickClientScrap::FX::ORDER_TYPE_IFD
|
454
597
|
when "IFD-OCO注文"
|
455
|
-
|
598
|
+
ClickClientScrap::FX::ORDER_TYPE_IFD_OCO
|
456
599
|
else
|
457
600
|
raise "illegal order_type. order_type=#{order_type}"
|
458
601
|
end
|
459
602
|
end
|
603
|
+
|
604
|
+
def link_click( no )
|
605
|
+
link = @links.find {|i|
|
606
|
+
i.attributes["accesskey"] == no
|
607
|
+
}
|
608
|
+
raise "link isnot found. accesskey=#{no}" unless link
|
609
|
+
@client.click( link )
|
610
|
+
end
|
460
611
|
end
|
461
612
|
|
613
|
+
# オプション
|
614
|
+
attr :options, true
|
615
|
+
|
462
616
|
#=== スワップ
|
463
617
|
Swap = Struct.new(:pair, :sell_swap, :buy_swap)
|
464
618
|
#=== レート
|
465
619
|
Rate = Struct.new(:pair, :bid_rate, :ask_rate, :sell_swap, :buy_swap )
|
466
620
|
#===注文
|
467
621
|
Order = Struct.new(:order_no, :trade_type, :order_type, :execution_expression, :sell_or_buy, :pair, :count, :rate, :order_state )
|
468
|
-
|
622
|
+
#===注文結果
|
623
|
+
OrderResult = Struct.new(:order_no )
|
624
|
+
#===建玉
|
625
|
+
OpenInterest = Struct.new(:open_interest_id, :pair, :sell_or_buy, :count, :rate, :profit_or_loss )
|
469
626
|
end
|
470
627
|
end
|
471
628
|
|
data/lib/jiji_plugin.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'jiji/plugin/securities_plugin'
|
4
|
+
require 'clickclient_scrap'
|
5
|
+
require 'thread'
|
6
|
+
|
7
|
+
# クリック証券アクセスプラグイン
|
8
|
+
class ClickSecuritiesPlugin
|
9
|
+
include JIJI::Plugin::SecuritiesPlugin
|
10
|
+
|
11
|
+
#プラグインの識別子を返します。
|
12
|
+
def plugin_id
|
13
|
+
:click_securities
|
14
|
+
end
|
15
|
+
#プラグインの表示名を返します。
|
16
|
+
def display_name
|
17
|
+
"CLICK Securities"
|
18
|
+
end
|
19
|
+
#「jiji setting」でユーザーに入力を要求するデータの情報を返します。
|
20
|
+
def input_infos
|
21
|
+
[ Input.new( :user, "Please input a user name of CLICK Securities.", false, nil ),
|
22
|
+
Input.new( :password, "Please input a password of CLICK Securities.", true, nil ),
|
23
|
+
Input.new( :proxy, "Please input a proxy. example: https://example.com:80 (default: nil )", false, nil ) ]
|
24
|
+
end
|
25
|
+
|
26
|
+
#プラグインを初期化します。
|
27
|
+
def init_plugin( props, logger )
|
28
|
+
@session = ClickSecuritiesPluginSession.new( props, logger )
|
29
|
+
end
|
30
|
+
#プラグインを破棄します。
|
31
|
+
def destroy_plugin
|
32
|
+
@session.close
|
33
|
+
end
|
34
|
+
|
35
|
+
#利用可能な通貨ペア一覧を取得します。
|
36
|
+
def list_pairs
|
37
|
+
return ALL_PAIRS.map {|pair|
|
38
|
+
Pair.new( pair, pair == ClickClientScrap::FX::ZARJPY ? 100000 : 10000 )
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
#現在のレートを取得します。
|
43
|
+
def list_rates
|
44
|
+
@session.list_rates.inject({}) {|r,p|
|
45
|
+
r[p[0]] = Rate.new( p[1].bid_rate, p[1].ask_rate, p[1].sell_swap, p[1].buy_swap )
|
46
|
+
r
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
#成り行きで発注を行います。
|
51
|
+
def order( pair, sell_or_buy, count )
|
52
|
+
|
53
|
+
# 建玉一覧を取得
|
54
|
+
before = @session.list_open_interests.inject( Set.new ) {|s,i| s << i[0]; s }
|
55
|
+
# 発注
|
56
|
+
@session.order( pair, sell_or_buy == :buy ? ClickClientScrap::FX::BUY : ClickClientScrap::FX::SELL, count )
|
57
|
+
# 建玉を特定
|
58
|
+
position = nil
|
59
|
+
# 10s待っても取得できなければあきらめる
|
60
|
+
20.times {|i|
|
61
|
+
sleep 0.5
|
62
|
+
position = @session.list_open_interests.find {|i| !before.include?(i[0]) }
|
63
|
+
break if position
|
64
|
+
}
|
65
|
+
raise "order fialed." unless position
|
66
|
+
return JIJI::Plugin::SecuritiesPlugin::Position.new( position[1].open_interest_id )
|
67
|
+
end
|
68
|
+
|
69
|
+
#建玉を決済します。
|
70
|
+
def commit( position_id, count )
|
71
|
+
@session.settle( position_id, count )
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
ALL_PAIRS = [
|
77
|
+
ClickClientScrap::FX::USDJPY, ClickClientScrap::FX::EURJPY,
|
78
|
+
ClickClientScrap::FX::GBPJPY, ClickClientScrap::FX::AUDJPY,
|
79
|
+
ClickClientScrap::FX::NZDJPY, ClickClientScrap::FX::CADJPY,
|
80
|
+
ClickClientScrap::FX::CHFJPY, ClickClientScrap::FX::ZARJPY,
|
81
|
+
ClickClientScrap::FX::EURUSD, ClickClientScrap::FX::GBPUSD,
|
82
|
+
ClickClientScrap::FX::AUDUSD, ClickClientScrap::FX::EURCHF,
|
83
|
+
ClickClientScrap::FX::GBPCHF, ClickClientScrap::FX::USDCHF
|
84
|
+
]
|
85
|
+
end
|
86
|
+
|
87
|
+
class ClickSecuritiesPluginSession
|
88
|
+
def initialize( props, logger )
|
89
|
+
@props = props
|
90
|
+
@logger = logger
|
91
|
+
@m = Mutex.new
|
92
|
+
end
|
93
|
+
def method_missing( name, *args )
|
94
|
+
@m.synchronize {
|
95
|
+
begin
|
96
|
+
session.send( name, *args )
|
97
|
+
rescue
|
98
|
+
# エラーになった場合はセッションを再作成する
|
99
|
+
close
|
100
|
+
raise $!
|
101
|
+
end
|
102
|
+
}
|
103
|
+
end
|
104
|
+
def close
|
105
|
+
begin
|
106
|
+
@session.logout if @session
|
107
|
+
rescue
|
108
|
+
@logger.error $!
|
109
|
+
ensure
|
110
|
+
@session = nil
|
111
|
+
@client = nil
|
112
|
+
end
|
113
|
+
end
|
114
|
+
def session
|
115
|
+
begin
|
116
|
+
@client ||= ClickClientScrap::Client.new(
|
117
|
+
@props.key?(:proxy) ? @props[:proxy] : nil )
|
118
|
+
@session ||= @client.fx_session( @props[:user], @props[:password] )
|
119
|
+
rescue
|
120
|
+
@logger.error $!
|
121
|
+
raise $!
|
122
|
+
end
|
123
|
+
@session
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
JIJI::Plugin.register(
|
128
|
+
JIJI::Plugin::SecuritiesPlugin::FUTURE_NAME,
|
129
|
+
ClickSecuritiesPlugin.new )
|
130
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
$: << "../lib"
|
4
|
+
|
5
|
+
require "runit/testcase"
|
6
|
+
require "runit/cui/testrunner"
|
7
|
+
require 'clickclient_scrap'
|
8
|
+
|
9
|
+
class FxSessionTest < RUNIT::TestCase
|
10
|
+
|
11
|
+
#convert_rate のテスト
|
12
|
+
def test_convert_rate
|
13
|
+
rate = ClickClient::FX::FxSession.convert_rate("123.34-37").map {|i| i.to_s }
|
14
|
+
assert_equals rate, [ "123.34", "123.37" ]
|
15
|
+
|
16
|
+
rate = ClickClient::FX::FxSession.convert_rate("123.534-37").map {|i| i.to_s }
|
17
|
+
assert_equals rate, [ "123.534", "123.537" ]
|
18
|
+
|
19
|
+
rate = ClickClient::FX::FxSession.convert_rate("123.594-02").map {|i| i.to_s }
|
20
|
+
assert_equals rate, [ "123.594", "123.602" ]
|
21
|
+
|
22
|
+
rate = ClickClient::FX::FxSession.convert_rate("123.00-02").map {|i| i.to_s }
|
23
|
+
assert_equals rate, [ "123.0", "123.02" ]
|
24
|
+
|
25
|
+
rate = ClickClient::FX::FxSession.convert_rate("123.34-33").map {|i| i.to_s }
|
26
|
+
assert_equals rate, [ "123.34", "124.33" ]
|
27
|
+
|
28
|
+
rate = ClickClient::FX::FxSession.convert_rate("123.34-34").map {|i| i.to_s }
|
29
|
+
assert_equals rate, [ "123.34", "123.34" ]
|
30
|
+
|
31
|
+
rate = ClickClient::FX::FxSession.convert_rate("0.334-335").map {|i| i.to_s }
|
32
|
+
assert_equals rate, [ "0.334", "0.335" ]
|
33
|
+
|
34
|
+
rate = ClickClient::FX::FxSession.convert_rate("0.334-333").map {|i| i.to_s }
|
35
|
+
assert_equals rate, [ "0.334", "1.333" ]
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
$: << "../lib"
|
4
|
+
|
5
|
+
require "runit/testcase"
|
6
|
+
require "logger"
|
7
|
+
require "runit/cui/testrunner"
|
8
|
+
require 'clickclient_scrap'
|
9
|
+
require 'jiji/plugin/plugin_loader'
|
10
|
+
require 'jiji/plugin/securities_plugin'
|
11
|
+
|
12
|
+
# jijiプラグインのテスト
|
13
|
+
# ※実際に取引を行うので注意!
|
14
|
+
class JIJIPluginTest < RUNIT::TestCase
|
15
|
+
|
16
|
+
def setup
|
17
|
+
@logger = Logger.new STDOUT
|
18
|
+
@user = IO.read( "../sample/user" )
|
19
|
+
@pass = IO.read( "../sample/pass" )
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_basic
|
23
|
+
# ロード
|
24
|
+
JIJI::Plugin::Loader.new.load
|
25
|
+
plugins = JIJI::Plugin.get( JIJI::Plugin::SecuritiesPlugin::FUTURE_NAME )
|
26
|
+
plugin = plugins.find {|i| i.plugin_id == :click_securities }
|
27
|
+
assert_not_nil plugin
|
28
|
+
assert_equals plugin.display_name, "CLICK Securities"
|
29
|
+
|
30
|
+
begin
|
31
|
+
plugin.init_plugin( {:user=>@user, :password=>@pass}, @logger )
|
32
|
+
|
33
|
+
# 利用可能な通貨ペア一覧とレート
|
34
|
+
pairs = plugin.list_pairs
|
35
|
+
rates = plugin.list_rates
|
36
|
+
pairs.each {|p|
|
37
|
+
# 利用可能とされたペアのレートが取得できていることを確認
|
38
|
+
assert_not_nil p.name
|
39
|
+
assert_not_nil p.trade_unit
|
40
|
+
assert_not_nil rates[p.name]
|
41
|
+
assert_not_nil rates[p.name].bid
|
42
|
+
assert_not_nil rates[p.name].ask
|
43
|
+
assert_not_nil rates[p.name].sell_swap
|
44
|
+
assert_not_nil rates[p.name].buy_swap
|
45
|
+
}
|
46
|
+
sleep 1
|
47
|
+
|
48
|
+
3.times {
|
49
|
+
rates = plugin.list_rates
|
50
|
+
pairs.each {|p|
|
51
|
+
# 利用可能とされたペアのレートが取得できていることを確認
|
52
|
+
assert_not_nil p.name
|
53
|
+
assert_not_nil p.trade_unit
|
54
|
+
assert_not_nil rates[p.name]
|
55
|
+
assert_not_nil rates[p.name].bid
|
56
|
+
assert_not_nil rates[p.name].ask
|
57
|
+
assert_not_nil rates[p.name].sell_swap
|
58
|
+
assert_not_nil rates[p.name].buy_swap
|
59
|
+
}
|
60
|
+
sleep 10
|
61
|
+
}
|
62
|
+
|
63
|
+
# 売り/買い
|
64
|
+
# sell = plugin.order( :EURJPY, :sell, 1 )
|
65
|
+
# buy = plugin.order( :EURJPY, :buy, 1 )
|
66
|
+
# assert_not_nil sell.position_id
|
67
|
+
# assert_not_nil buy.position_id
|
68
|
+
#
|
69
|
+
# # 約定
|
70
|
+
# plugin.commit sell.position_id, 1
|
71
|
+
# plugin.commit buy.position_id, 1
|
72
|
+
ensure
|
73
|
+
plugin.destroy_plugin
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unageanu-clickclient_scrap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Masaya Yamauchi
|
@@ -9,10 +9,19 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-05-16 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: mechanize
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - "="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.9.0
|
24
|
+
version:
|
16
25
|
description:
|
17
26
|
email: y-masaya@red.hot.co.jp
|
18
27
|
executables: []
|
@@ -25,6 +34,9 @@ files:
|
|
25
34
|
- README
|
26
35
|
- ChangeLog
|
27
36
|
- lib/clickclient_scrap.rb
|
37
|
+
- lib/jiji_plugin.rb
|
38
|
+
- test/test_FxSession.rb
|
39
|
+
- test/test_jiji_plugin.rb
|
28
40
|
has_rdoc: true
|
29
41
|
homepage: http://github.com/unageanu/clickclient_scrap/tree/master
|
30
42
|
post_install_message:
|
@@ -52,5 +64,6 @@ rubygems_version: 1.2.0
|
|
52
64
|
signing_key:
|
53
65
|
specification_version: 2
|
54
66
|
summary: click securities client library for ruby.
|
55
|
-
test_files:
|
56
|
-
|
67
|
+
test_files:
|
68
|
+
- test/test_FxSession.rb
|
69
|
+
- test/test_jiji_plugin.rb
|