unageanu-clickclient_scrap 0.1.2 → 0.1.3
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.
- 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
|