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 CHANGED
@@ -9,8 +9,14 @@
9
9
  - ログアウト
10
10
  - レート一覧の取得
11
11
  - 注文一覧の取得
12
- - 指値注文
13
- - OCO注文
12
+ - 建玉一覧の取得
13
+ - 注文
14
+ -- 成行
15
+ -- 指値
16
+ -- OCO
17
+ - 注文のキャンセル
18
+ - 決済
19
+ -- 成行
14
20
 
15
21
  ===注意事項
16
22
  - スクレイピングによるアクセスのため、Webサイトの仕様変更等により
@@ -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 'clickclient'
21
+ # require 'clickclient_scrap'
21
22
  #
22
23
  # c = ClickClient::Client.new
23
- # # c = ClickClient::Client.new https://<プロキシホスト>:<プロキシポート> # プロキシを利用する場合
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 ClickClient
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
- #-そうでない場合、セッションを返却します。この場合、ClickClient::FX::FxSession#logoutを実行しログアウトしてください。
63
+ #-そうでない場合、セッションを返却します。この場合、ClickClientScrap::FX::FxSession#logoutを実行しログアウトしてください。
62
64
  #
63
- #戻り値:: ClickClient::FX::FxSession
64
- def fx_session( userid, password, &block )
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
- ClickClient::Client.error(page) if page.forms.length <= 0
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
- session = FX::FxSession.new( @client, result.links )
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
- ClickClient::Client.error( result )
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
- #戻り値:: 通貨ペアをキーとするClickClient::FX::Rateのハッシュ。
194
+ #戻り値:: 通貨ペアをキーとするClickClientScrap::FX::Rateのハッシュ。
188
195
  def list_rates
189
- result = @client.click( @links.find {|i|
190
- i.attributes["accesskey"] == "1"
191
- })
192
- @swaps = list_swaps unless @swaps
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
- return result.body.toutf8.scan( reg ).inject({}) {|r,l|
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
- #戻り値:: 通貨ペアをキーとするClickClient::FX::Swapのハッシュ。
233
+ #戻り値:: 通貨ペアをキーとするClickClientScrap::FX::Swapのハッシュ。
223
234
  def list_swaps
224
- result = @client.click( @links.find {|i|
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:: 売買区分。ClickClient::FX::BUY,ClickClient::FX::SELLのいずれかを指定します。(必須)
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> .. 執行条件。ClickClient::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
246
- # - <tt>:expiration_type</tt> .. 有効期限。ClickClient::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
247
- # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClient::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
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> .. 有効期限。ClickClient::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
252
- # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClient::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
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> .. 執行条件。ClickClient::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
256
- # - <tt>:expiration_type</tt> .. 有効期限。ClickClient::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
257
- # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClient::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
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> .. 決済取引の売買区分。ClickClient::FX::BUY,ClickClient::FX::SELLのいずれかを指定します。(必須)
269
+ # - <tt>:sell_or_buy</tt> .. 決済取引の売買区分。ClickClientScrap::FX::BUY,ClickClientScrap::FX::SELLのいずれかを指定します。(必須)
261
270
  # - <tt>:rate</tt> .. 決済取引の注文レート(必須)
262
- # - <tt>:execution_expression</tt> .. 決済取引の執行条件。ClickClient::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
263
- # - <tt>:expiration_type</tt> .. 決済取引の有効期限。ClickClient::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
264
- # - <tt>:expiration_date</tt> .. 決済取引の有効期限が「日付指定(ClickClient::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
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> .. 執行条件。ClickClient::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
268
- # - <tt>:expiration_type</tt> .. 有効期限。ClickClient::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
269
- # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClient::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
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> .. 決済取引の売買区分。ClickClient::FX::BUY,ClickClient::FX::SELLのいずれかを指定します。(必須)
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> .. 決済取引の有効期限。ClickClient::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
276
- # - <tt>:expiration_date</tt> .. 決済取引の有効期限が「日付指定(ClickClient::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
277
- #戻り値:: ClickClient::FX::OrderResult TODO
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 = @client.click( @links.find {|i|
318
- i.attributes["accesskey"] == "1"
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 == ClickClient::FX::SELL ? "1" : "0" #売り/買い
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 == ClickClient::FX::SELL ? "1" : "0" #売り/買い
358
+ form["P002.0"] = sell_or_buy == ClickClientScrap::FX::SELL ? "1" : "0" #売り/買い
346
359
  exp = options[:execution_expression]
347
- form["P004.0"] = exp == ClickClient::FX::EXECUTION_EXPRESSION_REVERSE_LIMIT_ORDER ? "2" : "1" #指値/逆指値
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 == ClickClient::FX::SELL ? "1" : "0" #売り/買い
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
- #puts result.body.toutf8
364
- ClickClient::Client.error( result ) unless result.body.toutf8 =~ /注文受付完了/
365
- # TODO 結果を返す・・・どうするかな。
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 ClickClient::FX::EXPIRATION_TYPE_TODAY
392
+ when ClickClientScrap::FX::EXPIRATION_TYPE_TODAY
376
393
  form[input_type] = "0"
377
- when ClickClient::FX::EXPIRATION_TYPE_WEEK_END
394
+ when ClickClientScrap::FX::EXPIRATION_TYPE_WEEK_END
378
395
  form[input_type] = "1"
379
- when ClickClient::FX::EXPIRATION_TYPE_SPECIFIED
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
- #戻り値:: 注文番号をキーとするClickClient::FX::Orderのハッシュ。
514
+ #戻り値:: 注文番号をキーとするClickClientScrap::FX::Orderのハッシュ。
397
515
  #
398
- def list_orders( order_condition_code=ClickClient::FX::ORDER_CONDITION_ALL, currency_pair_code=nil )
399
-
400
- result = @client.click( @links.find {|i|
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
- #puts result.body.toutf8
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] == "新" ? ClickClient::FX::TRADE_TYPE_NEW : ClickClient::FX::TRADE_TYPE_SETTLEMENT
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] == "売" ? ClickClient::FX::SELL : ClickClient::FX::BUY
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
- ClickClient::FX::EXECUTION_EXPRESSION_LIMIT_ORDER
535
+ ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER
419
536
  elsif i[7] == "逆"
420
- ClickClient::FX::EXECUTION_EXPRESSION_REVERSE_LIMIT_ORDER
537
+ ClickClientScrap::FX::EXECUTION_EXPRESSION_REVERSE_LIMIT_ORDER
421
538
  else
422
- ClickClient::FX::EXECUTION_EXPRESSION_MARKET_ORDER
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
- ClickClient::FX::ORDER_TYPE_MARKET_ORDER
590
+ ClickClientScrap::FX::ORDER_TYPE_MARKET_ORDER
448
591
  when "通常注文"
449
- ClickClient::FX::ORDER_TYPE_NORMAL
592
+ ClickClientScrap::FX::ORDER_TYPE_NORMAL
450
593
  when "OCO注文"
451
- ClickClient::FX::ORDER_TYPE_OCO
594
+ ClickClientScrap::FX::ORDER_TYPE_OCO
452
595
  when "IFD注文"
453
- ClickClient::FX:: ORDER_TYPE_IFD
596
+ ClickClientScrap::FX::ORDER_TYPE_IFD
454
597
  when "IFD-OCO注文"
455
- ClickClient::FX:: ORDER_TYPE_IFD_OCO
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
 
@@ -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.2
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-04-08 00:00:00 -07:00
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