clickclient_scrap 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog CHANGED
@@ -1,4 +1,9 @@
1
1
 
2
+ 2010-03-20 0.1.10
3
+ * クリック証券デモ取引用jijiプラグインを実装
4
+ * 建玉が6以上あると建玉一覧の取得、および約定が正しく行えない問題を修正
5
+ * 注文が6以上あると注文一覧の取得、および注文のキャンセルが正しく行えない問題を修正
6
+
2
7
  2009-12-27 0.1.9
3
8
  * レート一覧ページの仕様変更に対応。
4
9
  * スワップが1000を超えた場合に、レート一覧に該当する通貨ペアが含まれない問題を修正。
@@ -1,690 +1,728 @@
1
- begin
2
- require 'rubygems'
3
- rescue LoadError
4
- end
5
- require 'mechanize'
6
- require 'date'
7
- require 'kconv'
8
- require 'set'
9
-
10
- #
11
- #=== クリック証券アクセスクライアント
12
- #
13
- #*Version*:: -
14
- #*License*:: Ruby ライセンスに準拠
15
- #
16
- #クリック証券を利用するためのクライアントライブラリです。携帯向けサイトのスクレイピングにより以下の機能を提供します。
17
- #- 外為証拠金取引(FX)取引
18
- #
19
- #====基本的な使い方
20
- #
21
- # require 'clickclient_scrap'
22
- #
23
- # c = ClickClient::Client.new
24
- # # c = ClickClientScrap::Client.new https://<プロキシホスト>:<プロキシポート> # プロキシを利用する場合
25
- # c.fx_session( "<ユーザー名>", "<パスワード>" ) { | fx_session |
26
- # # 通貨ペア一覧取得
27
- # list = fx_session.list_rates
28
- # puts list
29
- # }
30
- #
31
- #====免責
32
- #- 本ライブラリの利用は自己責任でお願いします。
33
- #- ライブラリの不備・不具合等によるあらゆる損害について、作成者は責任を負いません。
34
- #
35
- module ClickClientScrap
36
-
37
- # クライアント
38
- class Client
39
- # ホスト名
40
- DEFAULT_HOST_NAME = "https://sec-sso.click-sec.com/mf/"
41
- DEFAULT_DEMO_HOST_NAME = "https://www.click-sec.com/m/demo/"
42
-
43
- #
44
- #===コンストラクタ
45
- #
46
- #*proxy*:: プロキシホストを利用する場合、そのホスト名とパスを指定します。
47
- # 例) https://proxyhost.com:80
48
- #
49
- def initialize( proxy=ENV["http_proxy"], demo=false )
50
- @client = WWW::Mechanize.new {|c|
51
- # プロキシ
52
- if proxy
53
- uri = URI.parse( proxy )
54
- c.set_proxy( uri.host, uri.port )
55
- end
56
- }
57
- @client.keep_alive = false
58
- @client.max_history=0
59
- @client.user_agent_alias = 'Windows IE 7'
60
- @demo = demo
61
- @host_name = @demo ? DEFAULT_DEMO_HOST_NAME : DEFAULT_HOST_NAME
62
- end
63
-
64
- #ログインし、セッションを開始します。
65
- #-ブロックを指定した場合、引数としてセッションを指定してブロックを実行します。ブロック実行後、ログアウトします。
66
- #-そうでない場合、セッションを返却します。この場合、ClickClientScrap::FX::FxSession#logoutを実行しログアウトしてください。
67
- #
68
- #userid:: ユーザーID
69
- #password:: パスワード
1
+ begin
2
+ require 'rubygems'
3
+ rescue LoadError
4
+ end
5
+ require 'mechanize'
6
+ require 'date'
7
+ require 'kconv'
8
+ require 'set'
9
+
10
+ #
11
+ #=== クリック証券アクセスクライアント
12
+ #
13
+ #*Version*:: -
14
+ #*License*:: Ruby ライセンスに準拠
15
+ #
16
+ #クリック証券を利用するためのクライアントライブラリです。携帯向けサイトのスクレイピングにより以下の機能を提供します。
17
+ #- 外為証拠金取引(FX)取引
18
+ #
19
+ #====基本的な使い方
20
+ #
21
+ # require 'clickclient_scrap'
22
+ #
23
+ # c = ClickClient::Client.new
24
+ # # c = ClickClientScrap::Client.new https://<プロキシホスト>:<プロキシポート> # プロキシを利用する場合
25
+ # c.fx_session( "<ユーザー名>", "<パスワード>" ) { | fx_session |
26
+ # # 通貨ペア一覧取得
27
+ # list = fx_session.list_rates
28
+ # puts list
29
+ # }
30
+ #
31
+ #====免責
32
+ #- 本ライブラリの利用は自己責任でお願いします。
33
+ #- ライブラリの不備・不具合等によるあらゆる損害について、作成者は責任を負いません。
34
+ #
35
+ module ClickClientScrap
36
+
37
+ # クライアント
38
+ class Client
39
+ # ホスト名
40
+ DEFAULT_HOST_NAME = "https://sec-sso.click-sec.com/mf/"
41
+ DEFAULT_DEMO_HOST_NAME = "https://www.click-sec.com/m/demo/"
42
+
43
+ #
44
+ #===コンストラクタ
45
+ #
46
+ #*proxy*:: プロキシホストを利用する場合、そのホスト名とパスを指定します。
47
+ # 例) https://proxyhost.com:80
48
+ #
49
+ def initialize( proxy=ENV["http_proxy"], demo=false )
50
+ @client = WWW::Mechanize.new {|c|
51
+ # プロキシ
52
+ if proxy
53
+ uri = URI.parse( proxy )
54
+ c.set_proxy( uri.host, uri.port )
55
+ end
56
+ }
57
+ @client.keep_alive = false
58
+ @client.max_history=0
59
+ @client.user_agent_alias = 'Windows IE 7'
60
+ @demo = demo
61
+ @host_name = @demo ? DEFAULT_DEMO_HOST_NAME : DEFAULT_HOST_NAME
62
+ end
63
+
64
+ #ログインし、セッションを開始します。
65
+ #-ブロックを指定した場合、引数としてセッションを指定してブロックを実行します。ブロック実行後、ログアウトします。
66
+ #-そうでない場合、セッションを返却します。この場合、ClickClientScrap::FX::FxSession#logoutを実行しログアウトしてください。
67
+ #
68
+ #userid:: ユーザーID
69
+ #password:: パスワード
70
70
  #options:: オプション
71
- #戻り値:: ClickClientScrap::FX::FxSession
72
- def fx_session( userid, password, options={}, &block )
73
- page = @client.get(@host_name)
74
- ClickClientScrap::Client.error(page) if page.forms.length <= 0
75
- form = page.forms.first
76
- form.j_username = userid
77
- form.j_password = password
78
- result = @client.submit(form, form.buttons.first)
79
- # デモサイトではjsによるリダイレクトは不要。
80
- if !@demo
81
- if result.body.toutf8 =~ /<META HTTP-EQUIV="REFRESH" CONTENT="0;URL=([^"]*)">/
82
- result = @client.get($1)
83
- ClickClientScrap::Client.error( result ) if result.links.size <= 0
84
- else
85
- ClickClientScrap::Client.error( result )
86
- end
87
- end
88
- session = FX::FxSession.new( @client, result.links, options )
89
- if block_given?
90
- begin
91
- yield session
92
- ensure
93
- session.logout
94
- end
95
- else
96
- return session
97
- end
98
- end
99
- def self.error( page )
100
- msgs = page.body.scan( /<font color="red">([^<]*)</ ).flatten
101
- error = !msgs.empty? ? msgs.map{|m| m.strip}.join(",") : page.body
102
- raise "operation failed.detail=#{error}".toutf8
103
- end
104
-
105
- #ホスト名
106
- attr :host_name, true
107
- end
108
-
109
- module FX
110
-
111
- # 通貨ペア: 米ドル-円
112
- USDJPY = :USDJPY
113
- # 通貨ペア: ユーロ-円
114
- EURJPY = :EURJPY
115
- # 通貨ペア: イギリスポンド-円
116
- GBPJPY = :GBPJPY
117
- # 通貨ペア: 豪ドル-円
118
- AUDJPY = :AUDJPY
119
- # 通貨ペア: ニュージーランドドル-円
120
- NZDJPY = :NZDJPY
121
- # 通貨ペア: カナダドル-円
122
- CADJPY = :CADJPY
123
- # 通貨ペア: スイスフラン-円
124
- CHFJPY = :CHFJPY
125
- # 通貨ペア: 南アランド-円
126
- ZARJPY = :ZARJPY
127
- # 通貨ペア: ユーロ-米ドル
128
- EURUSD = :EURUSD
129
- # 通貨ペア: イギリスポンド-米ドル
130
- GBPUSD = :GBPUSD
131
- # 通貨ペア: 豪ドル-米ドル
132
- AUDUSD = :AUDUSD
133
- # 通貨ペア: ユーロ-スイスフラン
134
- EURCHF = :EURCHF
135
- # 通貨ペア: イギリスポンド-スイスフラン
136
- GBPCHF = :GBPCHF
137
- # 通貨ペア: 米ドル-スイスフラン
138
- USDCHF = :USDCHF
139
-
140
- # 売買区分: 買い
141
- BUY = 0
142
- # 売買区分: 売り
143
- SELL = 1
144
-
145
- # 注文タイプ: 通常
146
- ORDER_TYPE_MARKET_ORDER = "00"
147
- # 注文タイプ: 通常
148
- ORDER_TYPE_NORMAL = "01"
149
- # 注文タイプ: IFD
150
- ORDER_TYPE_IFD = "11"
151
- # 注文タイプ: OCO
152
- ORDER_TYPE_OCO = "21"
153
- # 注文タイプ: IFD-OCO
154
- ORDER_TYPE_IFD_OCO = "31"
155
-
156
- # 有効期限: 当日限り
157
- EXPIRATION_TYPE_TODAY = 0
158
- # 有効期限: 週末まで
159
- EXPIRATION_TYPE_WEEK_END = 1
160
- # 有効期限: 無期限
161
- EXPIRATION_TYPE_INFINITY = 2
162
- # 有効期限: 日付指定
163
- EXPIRATION_TYPE_SPECIFIED = 3
164
-
165
- # 注文状況: すべて
166
- ORDER_CONDITION_ALL = ""
167
- # 注文状況: 注文中
168
- ORDER_CONDITION_ON_ORDER = "0"
169
- # 注文状況: 取消済
170
- ORDER_CONDITION_CANCELED = "1"
171
- # 注文状況: 約定
172
- ORDER_CONDITION_EXECUTION = "2"
173
- # 注文状況: 不成立
174
- ORDER_CONDITION_FAILED = "3"
175
-
176
- # トレード種別: 新規
177
- TRADE_TYPE_NEW = "新規"
178
- # トレード種別: 決済
179
- TRADE_TYPE_SETTLEMENT = "決済"
180
-
181
- # 執行条件: 成行
182
- EXECUTION_EXPRESSION_MARKET_ORDER = "成行"
183
- # 執行条件: 指値
184
- EXECUTION_EXPRESSION_LIMIT_ORDER = "指値"
185
- # 執行条件: 逆指値
186
- EXECUTION_EXPRESSION_REVERSE_LIMIT_ORDER = "逆指値"
187
-
188
- #=== FX取引のためのセッションクラス
189
- #Client#fx_sessionのブロックの引数として渡されます。詳細はClient#fx_sessionを参照ください。
190
- class FxSession
191
-
192
- def initialize( client, links, options={} )
193
- @client = client
194
- @links = links
195
- @options = options
196
- end
197
-
198
- #レート一覧を取得します。
199
- #
200
- #戻り値:: 通貨ペアをキーとするClickClientScrap::FX::Rateのハッシュ。
201
- def list_rates
202
- result = link_click( "1" )
203
- if !@last_update_time_of_swaps \
204
- || Time.now.to_i - @last_update_time_of_swaps > (@options[:swap_update_interval] || 60*60)
205
- @swaps = list_swaps
206
- @last_update_time_of_swaps = Time.now.to_i
207
- end
208
- reg = />([A-Z]+\/[A-Z]+)<\/a>[^\-\.\d]*?([\d]+\.[\d]+)\-[^\-\.\d]*([\d\.]+)/
209
- tokens = result.body.toutf8.scan( reg )
210
- ClickClientScrap::Client.error( result ) if !tokens || tokens.empty?
211
- return tokens.inject({}) {|r,l|
212
- pair = to_pair( l[0] )
213
- swap = @swaps[pair]
214
- rate = FxSession.convert_rate "#{l[1]}-#{l[2]}"
215
- if ( rate && swap )
216
- r[pair] = Rate.new( pair, rate[0], rate[1], swap.sell_swap, swap.buy_swap )
217
- end
218
- r
219
- }
220
- end
221
- #12.34-35 形式の文字列をbidレート、askレートに変換する。
222
- def self.convert_rate( str ) #:nodoc:
223
- if str =~ /^([\d]+)\.([\d]+)\-([\d]+)$/
224
- high = $1
225
- low = $2
226
- low2 = $3
227
- bid = high.to_f+(low.to_f/(10**low.length))
228
- ask_low = (low[0...low.length-low2.length] + low2).to_f
229
- if low.to_f > ask_low
230
- ask_low += 10**low2.length
231
- end
232
- ask = high.to_f+(ask_low/10**low.length)
233
- return [bid,ask]
234
- elsif str =~ /^([\d]+\.[\d]+)\-([\d]+\.[\d]+)$/
235
- return [$1.to_f,$2.to_f]
236
- end
237
- end
238
-
239
- #スワップの一覧を取得します。
240
- #
241
- #戻り値:: 通貨ペアをキーとするClickClientScrap::FX::Swapのハッシュ。
242
- def list_swaps
243
- result = link_click( "8" )
244
- reg = /<dd>([A-Z]+\/[A-Z]+) <font[^>]*>売<\/font>[^\-\d]*?([\-\d,]+)[^\-\d]*<font[^>]*>買<\/font>[^\-\d]*([\-\d,]+)[^\-\d]*<\/dd>/
245
- return result.body.toutf8.scan( reg ).inject({}) {|r,l|
246
- pair = to_pair( l[0] )
247
- r[pair] = Swap.new( pair, l[1].sub(/,/,"").to_i, l[2].sub(/,/,"").to_i ); r
248
- }
249
- end
250
-
251
- #
252
- #注文を行います。
253
- #
254
- #currency_pair_code:: 通貨ペアコード(必須)
255
- #sell_or_buy:: 売買区分。ClickClientScrap::FX::BUY,ClickClientScrap::FX::SELLのいずれかを指定します。(必須)
256
- #unit:: 取引数量(必須)
257
- #options:: 注文のオプション。注文方法に応じて以下の情報を設定できます。
258
- # - <b>成り行き注文</b>
259
- # - <tt>:slippage</tt> .. スリッページ (オプション)。何pips以内かを整数で指定します。
260
- # - <b>通常注文</b> ※注文レートが設定されていれば通常取引となります。
261
- # - <tt>:rate</tt> .. 注文レート(必須)
262
- # - <tt>:execution_expression</tt> .. 執行条件。ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
263
- # - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
264
- # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
265
- # - <b>OCO注文</b> ※逆指値レートが設定されていればOCO取引となります。
266
- # - <tt>:rate</tt> .. 注文レート(必須)
267
- # - <tt>:stop_order_rate</tt> .. 逆指値レート(必須)
268
- # - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
269
- # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
270
- # - <b>IFD注文</b> ※決済取引の指定があればIFD取引となります。
271
- # - <tt>:rate</tt> .. 注文レート(必須)
272
- # - <tt>:execution_expression</tt> .. 執行条件。ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
273
- # - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
274
- # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
275
- # - <tt>:settle</tt> .. 決済取引の指定。マップで指定します。
276
- # - <tt>:unit</tt> .. 決済取引の取引数量(必須)
277
- # - <tt>:sell_or_buy</tt> .. 決済取引の売買区分。ClickClientScrap::FX::BUY,ClickClientScrap::FX::SELLのいずれかを指定します。(必須)
278
- # - <tt>:rate</tt> .. 決済取引の注文レート(必須)
279
- # - <tt>:execution_expression</tt> .. 決済取引の執行条件。ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
280
- # - <tt>:expiration_type</tt> .. 決済取引の有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
281
- # - <tt>:expiration_date</tt> .. 決済取引の有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
282
- # - <b>IFD-OCO注文</b> ※決済取引の指定と逆指値レートの指定があればIFD-OCO取引となります。
283
- # - <tt>:rate</tt> .. 注文レート(必須)
284
- # - <tt>:execution_expression</tt> .. 執行条件。ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
285
- # - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
286
- # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
287
- # - <tt>:settle</tt> .. 決済取引の指定。マップで指定します。
288
- # - <tt>:unit</tt> .. 決済取引の取引数量(必須)
289
- # - <tt>:sell_or_buy</tt> .. 決済取引の売買区分。ClickClientScrap::FX::BUY,ClickClientScrap::FX::SELLのいずれかを指定します。(必須)
290
- # - <tt>:rate</tt> .. 決済取引の注文レート(必須)
291
- # - <tt>:stop_order_rate</tt> .. 決済取引の逆指値レート(必須)
292
- # - <tt>:expiration_type</tt> .. 決済取引の有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
293
- # - <tt>:expiration_date</tt> .. 決済取引の有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
294
- #戻り値:: ClickClientScrap::FX::OrderResult
295
- #
296
- def order ( currency_pair_code, sell_or_buy, unit, options={} )
297
-
298
- # 取り引き種別の判別とパラメータチェック
299
- type = ORDER_TYPE_MARKET_ORDER
300
- if ( options && options[:settle] != nil )
301
- if ( options[:settle][:stop_order_rate] != nil)
302
- # 逆指値レートと決済取引の指定があればIFD-OCO取引
303
- raise "options[:settle][:rate] is required." if options[:settle][:rate] == nil
304
- type = ORDER_TYPE_IFD_OCO
305
- else
306
- # 決済取引の指定のみがあればIFD取引
307
- raise "options[:settle][:rate] is required." if options[:settle][:rate] == nil
308
- raise "options[:settle][:execution_expression] is required." if options[:settle][:execution_expression] == nil
309
- type = ORDER_TYPE_IFD
310
- end
311
- raise "options[:rate] is required." if options[:rate] == nil
312
- raise "options[:execution_expression] is required." if options[:execution_expression] == nil
313
- raise "options[:expiration_type] is required." if options[:expiration_type] == nil
314
- raise "options[:settle][:rate] is required." if options[:settle][:rate] == nil
315
- raise "options[:settle][:sell_or_buy] is required." if options[:settle][:sell_or_buy] == nil
316
- raise "options[:settle][:unit] is required." if options[:settle][:unit] == nil
317
- raise "options[:settle][:expiration_type] is required." if options[:expiration_type] == nil
318
- elsif ( options && options[:rate] != nil )
319
- if ( options[:stop_order_rate] != nil )
320
- # 逆指値レートが指定されていればOCO取引
321
- type = ORDER_TYPE_OCO
322
- else
323
- # そうでなければ通常取引
324
- raise "options[:execution_expression] is required." if options[:execution_expression] == nil
325
- type = ORDER_TYPE_NORMAL
326
- end
327
- raise "options[:expiration_type] is required." if options[:expiration_type] == nil
328
- else
329
- # 成り行き
330
- type = ORDER_TYPE_MARKET_ORDER
331
- end
332
-
333
- #注文前の注文一覧
334
- before = list_orders( ORDER_CONDITION_ON_ORDER ).inject(Set.new) {|s,o| s << o[0]; s }
335
-
336
- # レート一覧
337
- result = link_click( "1" )
338
-
339
- ClickClientScrap::Client.error( result ) if result.forms.empty?
340
- form = result.forms.first
341
-
342
- # 通貨ペア
343
- option = form.fields.find{|f| f.name == "P001" }.options.find {|o|
344
- to_pair( o.text.strip ) == currency_pair_code
345
- }
346
- raise "illegal currency_pair_code. currency_pair_code=#{currency_pair_code.to_s}" unless option
347
- option.select
348
-
349
- #注文方式
350
- form["P100"] = type
351
-
352
- # 詳細設定画面へ
353
- result = @client.submit(form)
354
- ClickClientScrap::Client.error( result ) if result.forms.empty?
355
- form = result.forms.first
356
- case type
357
- when ORDER_TYPE_MARKET_ORDER
358
- # 成り行き
359
- form["P003"] = unit.to_s # 取り引き数量
360
- form["P002.0"] = sell_or_buy == ClickClientScrap::FX::SELL ? "1" : "0" #売り/買い
361
- form["P005"] = options[:slippage].to_s if ( options && options[:slippage] != nil ) # スリッページ
362
- when ORDER_TYPE_NORMAL
363
- # 指値
364
- form["P003"] = options[:rate].to_s # レート
365
- form["P005"] = unit.to_s # 取り引き数量
366
- form["P002.0"] = sell_or_buy == ClickClientScrap::FX::SELL ? "1" : "0" #売り/買い
367
- exp = options[:execution_expression]
368
- form["P004.0"] = exp == ClickClientScrap::FX::EXECUTION_EXPRESSION_REVERSE_LIMIT_ORDER ? "2" : "1" #指値/逆指値
369
- set_expiration( form, options, "P008", "P009" ) # 有効期限
370
- when ORDER_TYPE_OCO
371
- # OCO
372
- form["P003"] = options[:rate].to_s # レート
373
- form["P005"] = options[:stop_order_rate].to_s # 逆指値レート
374
- form["P007"] = unit.to_s # 取り引き数量
375
- form["P002.0"] = sell_or_buy == ClickClientScrap::FX::SELL ? "1" : "0" #売り/買い
376
- set_expiration( form, options, "P010", "P011" ) # 有効期限
377
- else
378
- raise "not supported yet."
379
- end
380
-
381
- # 確認画面へ
382
- result = @client.submit(form)
383
- ClickClientScrap::Client.error( result ) if result.forms.empty?
384
- result = @client.submit(result.forms.first)
385
- ClickClientScrap::Client.error( result ) unless result.body.toutf8 =~ /注文受付完了/
386
-
387
- #注文前の一覧と注文後の一覧を比較して注文を割り出す。
388
- #成り行き注文の場合、即座に約定するのでnilになる(タイミングによっては取得できるかも)
389
- tmp = list_orders( ORDER_CONDITION_ON_ORDER ).find {|o| !before.include?(o[0]) }
390
- return OrderResult.new( tmp ? tmp[1].order_no : nil )
391
- end
392
-
393
- # 有効期限を設定する
394
- #form:: フォーム
395
- #options:: パラメータ
396
- #input_type:: 有効期限の種別を入力するinput要素名
397
- #input_date:: 有効期限が日付指定の場合に、日付を入力するinput要素名
398
- def set_expiration( form, options, input_type, input_date )
399
- case options[:expiration_type]
400
- when ClickClientScrap::FX::EXPIRATION_TYPE_TODAY
401
- form[input_type] = "0"
402
- when ClickClientScrap::FX::EXPIRATION_TYPE_WEEK_END
403
- form[input_type] = "1"
404
- when ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED
405
- form[input_type] = "3"
406
- raise "options[:expiration_date] is required." unless options[:expiration_date]
407
- form["#{input_date}.Y"] = options[:expiration_date].year
408
- form["#{input_date}.M"] = options[:expiration_date].month
409
- form["#{input_date}.D"] = options[:expiration_date].day
410
- form["#{input_date}.h"] = options[:expiration_date].respond_to?(:hour) ? options[:expiration_date].hour : "0"
411
- else
412
- form[input_type] = "2"
413
- end
414
- end
415
-
416
- #
417
- #=== 注文をキャンセルします。
418
- #
419
- #order_no:: 注文番号
420
- #戻り値:: なし
421
- #
422
- def cancel_order( order_no )
423
-
424
- raise "order_no is nil." unless order_no
425
-
426
- # 注文一覧
427
- result = link_click( "2" )
428
- ClickClientScrap::Client.error( result ) if result.forms.empty?
429
- form = result.forms.first
430
- form["P002"] = ORDER_CONDITION_ON_ORDER
431
- result = @client.submit(form)
432
-
433
- # 対象となる注文をクリック
434
- link = result.links.find {|l|
435
- l.href =~ /[^"]*GKEY=([a-zA-Z0-9]*)[^"]*/ && $1 == order_no
436
- }
437
- raise "illegal order_no. order_no=#{order_no}" unless link
438
- result = @client.click(link)
439
- ClickClientScrap::Client.error( result ) if result.forms.empty?
440
-
441
- # キャンセル
442
- form = result.forms[1]
443
- result = @client.submit(form)
444
- ClickClientScrap::Client.error( result ) if result.forms.empty?
445
- form = result.forms.first
446
- result = @client.submit(form)
447
- ClickClientScrap::Client.error( result ) unless result.body.toutf8 =~ /注文取消受付完了/
448
- end
449
-
450
-
451
- #
452
- #=== 決済注文を行います。
453
- #
454
- #*open_interest_id*:: 決済する建玉番号
455
- #*unit*:: 取引数量
456
- #*options*:: 決済注文のオプション。注文方法に応じて以下の情報を設定できます。
457
- # - <b>成り行き注文</b>
458
- # - <tt>:slippage</tt> .. スリッページ (オプション)
459
- # - <tt>:slippage_base_rate</tt> .. スリッページの基準となる取引レート(スリッページが指定された場合、必須。)
460
- # - <b>通常注文</b> <b>※未実装</b> ※注文レートが設定されていれば通常取引となります。
461
- # - <tt>:rate</tt> .. 注文レート(必須)
462
- # - <tt>:execution_expression</tt> .. 執行条件。ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
463
- # - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
464
- # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
465
- # - <b>OCO注文</b> <b>※未実装</b> ※注文レートと逆指値レートが設定されていればOCO取引となります。
466
- # - <tt>:rate</tt> .. 注文レート(必須)
467
- # - <tt>:stop_order_rate</tt> .. 逆指値レート(必須)
468
- # - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
469
- # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
470
- #<b>戻り値</b>:: なし
471
- #
472
- def settle ( open_interest_id, unit, options={} )
473
- if ( options[:rate] != nil && options[:stop_order_rate] != nil )
474
- # レートと逆指値レートが指定されていればOCO取引
475
- raise "options[:expiration_type] is required." if options[:expiration_type] == nil
476
- elsif ( options[:rate] != nil )
477
- # レートが指定されていれば通常取引
478
- raise "options[:execution_expression] is required." if options[:execution_expression] == nil
479
- raise "options[:expiration_type] is required." if options[:expiration_type] == nil
480
- else
481
- # 成り行き
482
- if ( options[:slippage] != nil )
483
- raise "if you use a slippage, options[:slippage_base_rate] is required." if options[:slippage_base_rate] == nil
484
- end
485
- end
486
-
487
- # 建玉一覧
488
- result = link_click( "3" )
489
-
490
- # 対象となる建玉をクリック
491
- link = result.links.find {|l|
492
- l.href =~ /[^"]*ORDERNO=([a-zA-Z0-9]*)[^"]*/ && $1 == open_interest_id
493
- }
494
- raise "illegal open_interest_id. open_interest_id=#{open_interest_id}" unless link
495
- result = @client.click(link)
496
-
497
- # 決済
498
- form = result.forms.first
499
- form["P100"] = "00" # 成り行き TODO 通常(01),OCO取引(21)対応
500
- result = @client.submit(form)
501
- ClickClientScrap::Client.error( result ) if result.forms.empty?
502
-
503
- # 設定
504
- form = result.forms.first
505
- form["L111"] = unit.to_s
506
- form["P005"] = options[:slippage].to_s if options[:slippage]
507
- result = @client.submit(form)
508
- ClickClientScrap::Client.error( result ) if result.forms.empty?
509
-
510
- # 確認
511
- form = result.forms.first
512
- result = @client.submit(form)
513
- ClickClientScrap::Client.error( result ) unless result.body.toutf8 =~ /完了/
514
- end
515
-
516
-
517
- #
518
- #=== 注文一覧を取得します。
519
- #
520
- #order_condition_code:: 注文状況コード(必須)
521
- #currency_pair_code:: 通貨ペアコード <b>※未実装</b>
522
- #戻り値:: 注文番号をキーとするClickClientScrap::FX::Orderのハッシュ。
523
- #
524
- def list_orders( order_condition_code=ClickClientScrap::FX::ORDER_CONDITION_ALL, currency_pair_code=nil )
525
- result = link_click( "2" )
526
- ClickClientScrap::Client.error( result ) if result.forms.empty?
527
- form = result.forms.first
528
- form["P001"] = "" # TODO currency_pair_codeでの絞り込み
529
- form["P002"] = order_condition_code
530
- result = @client.submit(form)
531
-
532
- 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 )
533
- tmp = {}
534
- list.each {|i|
535
- order_no = i[0]
536
- order_type = to_order_type_code(i[2])
537
- trade_type = i[3] == "" ? ClickClientScrap::FX::TRADE_TYPE_NEW : ClickClientScrap::FX::TRADE_TYPE_SETTLEMENT
538
- pair = to_pair( i[1] )
539
- sell_or_buy = i[4] == "売" ? ClickClientScrap::FX::SELL : ClickClientScrap::FX::BUY
540
- count = pair == :ZARJPY ? i[5].to_i/10 : i[5].to_i
541
- rate = i[6].to_f
542
- execution_expression = if i[7] == "指"
543
- ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER
544
- elsif i[7] == "逆"
545
- ClickClientScrap::FX::EXECUTION_EXPRESSION_REVERSE_LIMIT_ORDER
546
- else
547
- ClickClientScrap::FX::EXECUTION_EXPRESSION_MARKET_ORDER
548
- end
549
- tmp[order_no] = Order.new( order_no, trade_type, order_type, execution_expression, sell_or_buy, pair, count, rate, i[8])
550
- }
551
- return tmp
552
- end
553
-
554
- #
555
- #=== 建玉一覧を取得します。
556
- #
557
- #currency_pair_code:: 通貨ペアコード。<b>※未実装</b>
558
- #戻り値:: 建玉IDをキーとするClickClientScrap::FX::OpenInterestのハッシュ。
559
- #
560
- def list_open_interests( currency_pair_code=nil )
561
- result = link_click( "3" )
562
- ClickClientScrap::Client.error( result ) if result.forms.empty?
563
- form = result.forms.first
564
- form["P001"] = "" # TODO currency_pair_codeでの絞り込み
565
- result = @client.submit(form)
566
-
567
- list = result.body.toutf8.scan( /<a href="[^"]*">([A-Z]{3}\/[A-Z]{3}):([^<]*)<\/a><br>[^;]*;<font[^>]*>([^<]*)<\/font>([\d\.]*)[^\s@]*@([\d\.]*).*?<font[^>]*>([^<]*)<\/font>/m )
568
- tmp = {}
569
- list.each {|i|
570
- open_interest_id = i[1]
571
- pair = to_pair( i[0] )
572
- sell_or_buy = i[2] == "売" ? ClickClientScrap::FX::SELL : ClickClientScrap::FX::BUY
573
- count = i[3].to_i
574
- rate = i[4].to_f
575
- profit_or_loss = i[5].to_i
576
- tmp[open_interest_id] = OpenInterest.new(open_interest_id, pair, sell_or_buy, count, rate, profit_or_loss )
577
- }
578
- return tmp
579
- end
580
-
581
- #
582
- #=== 余力情報を取得します。
583
- #
584
- #戻り値:: ClickClientScrap::FX::Marginのハッシュ。
585
- #
586
- def get_margin
587
- result = link_click( "7" )
588
- list = result.body.toutf8.scan( /【([^<]*)[^>]*>[^>]*>([^<]*)</m )
589
- values = list.inject({}) {|r,i|
590
- if ( i[0] == "証拠金維持率】" )
591
- r[i[0]] = i[1]
592
- else
593
- r[i[0]] = i[1].gsub(/,/, "").to_i
594
- end
595
- r
596
- }
597
- return Margin.new(
598
- values["時価評価総額】"],
599
- values["建玉評価損益】"],
600
- values["口座残高】"],
601
- values["証拠金維持率】"],
602
- values["余力】"],
603
- values["拘束証拠金】"],
604
- values["必要証拠金】"],
605
- values["注文中必要証拠金】"],
606
- values["振替可能額】"]
607
- )
608
- end
609
-
610
- # ログアウトします。
611
- def logout
612
- @client.click( @links.find {|i|
613
- i.text == "\303\233\302\270\303\236\302\261\302\263\303\204" \
614
- || i.text == "ログアウト"
71
+ #戻り値:: ClickClientScrap::FX::FxSession
72
+ def fx_session( userid, password, options={}, &block )
73
+ page = @client.get(@host_name)
74
+ ClickClientScrap::Client.error(page) if page.forms.length <= 0
75
+ form = page.forms.first
76
+ form.j_username = userid
77
+ form.j_password = password
78
+ result = @client.submit(form, form.buttons.first)
79
+ # デモサイトではjsによるリダイレクトは不要。
80
+ if !@demo
81
+ if result.body.toutf8 =~ /<META HTTP-EQUIV="REFRESH" CONTENT="0;URL=([^"]*)">/
82
+ result = @client.get($1)
83
+ ClickClientScrap::Client.error( result ) if result.links.size <= 0
84
+ else
85
+ ClickClientScrap::Client.error( result )
86
+ end
87
+ end
88
+ session = FX::FxSession.new( @client, result.links, options )
89
+ if block_given?
90
+ begin
91
+ yield session
92
+ ensure
93
+ session.logout
94
+ end
95
+ else
96
+ return session
97
+ end
98
+ end
99
+ def self.error( page )
100
+ msgs = page.body.scan( /<font color="red">([^<]*)</ ).flatten
101
+ error = !msgs.empty? ? msgs.map{|m| m.strip}.join(",") : page.body
102
+ raise "operation failed.detail=#{error}".toutf8
103
+ end
104
+
105
+ #ホスト名
106
+ attr :host_name, true
107
+ end
108
+
109
+ module FX
110
+
111
+ # 通貨ペア: 米ドル-円
112
+ USDJPY = :USDJPY
113
+ # 通貨ペア: ユーロ-円
114
+ EURJPY = :EURJPY
115
+ # 通貨ペア: イギリスポンド-円
116
+ GBPJPY = :GBPJPY
117
+ # 通貨ペア: 豪ドル-円
118
+ AUDJPY = :AUDJPY
119
+ # 通貨ペア: ニュージーランドドル-円
120
+ NZDJPY = :NZDJPY
121
+ # 通貨ペア: カナダドル-円
122
+ CADJPY = :CADJPY
123
+ # 通貨ペア: スイスフラン-円
124
+ CHFJPY = :CHFJPY
125
+ # 通貨ペア: 南アランド-円
126
+ ZARJPY = :ZARJPY
127
+ # 通貨ペア: ユーロ-米ドル
128
+ EURUSD = :EURUSD
129
+ # 通貨ペア: イギリスポンド-米ドル
130
+ GBPUSD = :GBPUSD
131
+ # 通貨ペア: 豪ドル-米ドル
132
+ AUDUSD = :AUDUSD
133
+ # 通貨ペア: ユーロ-スイスフラン
134
+ EURCHF = :EURCHF
135
+ # 通貨ペア: イギリスポンド-スイスフラン
136
+ GBPCHF = :GBPCHF
137
+ # 通貨ペア: 米ドル-スイスフラン
138
+ USDCHF = :USDCHF
139
+
140
+ # 売買区分: 買い
141
+ BUY = 0
142
+ # 売買区分: 売り
143
+ SELL = 1
144
+
145
+ # 注文タイプ: 通常
146
+ ORDER_TYPE_MARKET_ORDER = "00"
147
+ # 注文タイプ: 通常
148
+ ORDER_TYPE_NORMAL = "01"
149
+ # 注文タイプ: IFD
150
+ ORDER_TYPE_IFD = "11"
151
+ # 注文タイプ: OCO
152
+ ORDER_TYPE_OCO = "21"
153
+ # 注文タイプ: IFD-OCO
154
+ ORDER_TYPE_IFD_OCO = "31"
155
+
156
+ # 有効期限: 当日限り
157
+ EXPIRATION_TYPE_TODAY = 0
158
+ # 有効期限: 週末まで
159
+ EXPIRATION_TYPE_WEEK_END = 1
160
+ # 有効期限: 無期限
161
+ EXPIRATION_TYPE_INFINITY = 2
162
+ # 有効期限: 日付指定
163
+ EXPIRATION_TYPE_SPECIFIED = 3
164
+
165
+ # 注文状況: すべて
166
+ ORDER_CONDITION_ALL = ""
167
+ # 注文状況: 注文中
168
+ ORDER_CONDITION_ON_ORDER = "0"
169
+ # 注文状況: 取消済
170
+ ORDER_CONDITION_CANCELED = "1"
171
+ # 注文状況: 約定
172
+ ORDER_CONDITION_EXECUTION = "2"
173
+ # 注文状況: 不成立
174
+ ORDER_CONDITION_FAILED = "3"
175
+
176
+ # トレード種別: 新規
177
+ TRADE_TYPE_NEW = "新規"
178
+ # トレード種別: 決済
179
+ TRADE_TYPE_SETTLEMENT = "決済"
180
+
181
+ # 執行条件: 成行
182
+ EXECUTION_EXPRESSION_MARKET_ORDER = "成行"
183
+ # 執行条件: 指値
184
+ EXECUTION_EXPRESSION_LIMIT_ORDER = "指値"
185
+ # 執行条件: 逆指値
186
+ EXECUTION_EXPRESSION_REVERSE_LIMIT_ORDER = "逆指値"
187
+
188
+ #=== FX取引のためのセッションクラス
189
+ #Client#fx_sessionのブロックの引数として渡されます。詳細はClient#fx_sessionを参照ください。
190
+ class FxSession
191
+
192
+ def initialize( client, links, options={} )
193
+ @client = client
194
+ @links = links
195
+ @options = options
196
+ end
197
+
198
+ #レート一覧を取得します。
199
+ #
200
+ #戻り値:: 通貨ペアをキーとするClickClientScrap::FX::Rateのハッシュ。
201
+ def list_rates
202
+ result = link_click( "1" )
203
+ if !@last_update_time_of_swaps \
204
+ || Time.now.to_i - @last_update_time_of_swaps > (@options[:swap_update_interval] || 60*60)
205
+ @swaps = list_swaps
206
+ @last_update_time_of_swaps = Time.now.to_i
207
+ end
208
+ reg = />([A-Z]+\/[A-Z]+)<\/a>[^\-\.\d]*?([\d]+\.[\d]+)\-[^\-\.\d]*([\d\.]+)/
209
+ tokens = result.body.toutf8.scan( reg )
210
+ ClickClientScrap::Client.error( result ) if !tokens || tokens.empty?
211
+ return tokens.inject({}) {|r,l|
212
+ pair = to_pair( l[0] )
213
+ swap = @swaps[pair]
214
+ rate = FxSession.convert_rate "#{l[1]}-#{l[2]}"
215
+ if ( rate && swap )
216
+ r[pair] = Rate.new( pair, rate[0], rate[1], swap.sell_swap, swap.buy_swap )
217
+ end
218
+ r
219
+ }
220
+ end
221
+ #12.34-35 形式の文字列をbidレート、askレートに変換する。
222
+ def self.convert_rate( str ) #:nodoc:
223
+ if str =~ /^([\d]+)\.([\d]+)\-([\d]+)$/
224
+ high = $1
225
+ low = $2
226
+ low2 = $3
227
+ bid = high.to_f+(low.to_f/(10**low.length))
228
+ ask_low = (low[0...low.length-low2.length] + low2).to_f
229
+ if low.to_f > ask_low
230
+ ask_low += 10**low2.length
231
+ end
232
+ ask = high.to_f+(ask_low/10**low.length)
233
+ return [bid,ask]
234
+ elsif str =~ /^([\d]+\.[\d]+)\-([\d]+\.[\d]+)$/
235
+ return [$1.to_f,$2.to_f]
236
+ end
237
+ end
238
+
239
+ #スワップの一覧を取得します。
240
+ #
241
+ #戻り値:: 通貨ペアをキーとするClickClientScrap::FX::Swapのハッシュ。
242
+ def list_swaps
243
+ result = link_click( "8" )
244
+ reg = /<dd>([A-Z]+\/[A-Z]+) <font[^>]*>売<\/font>[^\-\d]*?([\-\d,]+)[^\-\d]*<font[^>]*>買<\/font>[^\-\d]*([\-\d,]+)[^\-\d]*<\/dd>/
245
+ return result.body.toutf8.scan( reg ).inject({}) {|r,l|
246
+ pair = to_pair( l[0] )
247
+ r[pair] = Swap.new( pair, l[1].sub(/,/,"").to_i, l[2].sub(/,/,"").to_i ); r
248
+ }
249
+ end
250
+
251
+ #
252
+ #注文を行います。
253
+ #
254
+ #currency_pair_code:: 通貨ペアコード(必須)
255
+ #sell_or_buy:: 売買区分。ClickClientScrap::FX::BUY,ClickClientScrap::FX::SELLのいずれかを指定します。(必須)
256
+ #unit:: 取引数量(必須)
257
+ #options:: 注文のオプション。注文方法に応じて以下の情報を設定できます。
258
+ # - <b>成り行き注文</b>
259
+ # - <tt>:slippage</tt> .. スリッページ (オプション)。何pips以内かを整数で指定します。
260
+ # - <b>通常注文</b> ※注文レートが設定されていれば通常取引となります。
261
+ # - <tt>:rate</tt> .. 注文レート(必須)
262
+ # - <tt>:execution_expression</tt> .. 執行条件。ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
263
+ # - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
264
+ # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
265
+ # - <b>OCO注文</b> ※逆指値レートが設定されていればOCO取引となります。
266
+ # - <tt>:rate</tt> .. 注文レート(必須)
267
+ # - <tt>:stop_order_rate</tt> .. 逆指値レート(必須)
268
+ # - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
269
+ # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
270
+ # - <b>IFD注文</b> ※決済取引の指定があればIFD取引となります。
271
+ # - <tt>:rate</tt> .. 注文レート(必須)
272
+ # - <tt>:execution_expression</tt> .. 執行条件。ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
273
+ # - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
274
+ # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
275
+ # - <tt>:settle</tt> .. 決済取引の指定。マップで指定します。
276
+ # - <tt>:unit</tt> .. 決済取引の取引数量(必須)
277
+ # - <tt>:sell_or_buy</tt> .. 決済取引の売買区分。ClickClientScrap::FX::BUY,ClickClientScrap::FX::SELLのいずれかを指定します。(必須)
278
+ # - <tt>:rate</tt> .. 決済取引の注文レート(必須)
279
+ # - <tt>:execution_expression</tt> .. 決済取引の執行条件。ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
280
+ # - <tt>:expiration_type</tt> .. 決済取引の有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
281
+ # - <tt>:expiration_date</tt> .. 決済取引の有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
282
+ # - <b>IFD-OCO注文</b> ※決済取引の指定と逆指値レートの指定があればIFD-OCO取引となります。
283
+ # - <tt>:rate</tt> .. 注文レート(必須)
284
+ # - <tt>:execution_expression</tt> .. 執行条件。ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
285
+ # - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
286
+ # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
287
+ # - <tt>:settle</tt> .. 決済取引の指定。マップで指定します。
288
+ # - <tt>:unit</tt> .. 決済取引の取引数量(必須)
289
+ # - <tt>:sell_or_buy</tt> .. 決済取引の売買区分。ClickClientScrap::FX::BUY,ClickClientScrap::FX::SELLのいずれかを指定します。(必須)
290
+ # - <tt>:rate</tt> .. 決済取引の注文レート(必須)
291
+ # - <tt>:stop_order_rate</tt> .. 決済取引の逆指値レート(必須)
292
+ # - <tt>:expiration_type</tt> .. 決済取引の有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
293
+ # - <tt>:expiration_date</tt> .. 決済取引の有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
294
+ #戻り値:: ClickClientScrap::FX::OrderResult
295
+ #
296
+ def order ( currency_pair_code, sell_or_buy, unit, options={} )
297
+
298
+ # 取り引き種別の判別とパラメータチェック
299
+ type = ORDER_TYPE_MARKET_ORDER
300
+ if ( options && options[:settle] != nil )
301
+ if ( options[:settle][:stop_order_rate] != nil)
302
+ # 逆指値レートと決済取引の指定があればIFD-OCO取引
303
+ raise "options[:settle][:rate] is required." if options[:settle][:rate] == nil
304
+ type = ORDER_TYPE_IFD_OCO
305
+ else
306
+ # 決済取引の指定のみがあればIFD取引
307
+ raise "options[:settle][:rate] is required." if options[:settle][:rate] == nil
308
+ raise "options[:settle][:execution_expression] is required." if options[:settle][:execution_expression] == nil
309
+ type = ORDER_TYPE_IFD
310
+ end
311
+ raise "options[:rate] is required." if options[:rate] == nil
312
+ raise "options[:execution_expression] is required." if options[:execution_expression] == nil
313
+ raise "options[:expiration_type] is required." if options[:expiration_type] == nil
314
+ raise "options[:settle][:rate] is required." if options[:settle][:rate] == nil
315
+ raise "options[:settle][:sell_or_buy] is required." if options[:settle][:sell_or_buy] == nil
316
+ raise "options[:settle][:unit] is required." if options[:settle][:unit] == nil
317
+ raise "options[:settle][:expiration_type] is required." if options[:expiration_type] == nil
318
+ elsif ( options && options[:rate] != nil )
319
+ if ( options[:stop_order_rate] != nil )
320
+ # 逆指値レートが指定されていればOCO取引
321
+ type = ORDER_TYPE_OCO
322
+ else
323
+ # そうでなければ通常取引
324
+ raise "options[:execution_expression] is required." if options[:execution_expression] == nil
325
+ type = ORDER_TYPE_NORMAL
326
+ end
327
+ raise "options[:expiration_type] is required." if options[:expiration_type] == nil
328
+ else
329
+ # 成り行き
330
+ type = ORDER_TYPE_MARKET_ORDER
331
+ end
332
+
333
+ #注文前の注文一覧
334
+ before = list_orders( ORDER_CONDITION_ON_ORDER ).inject(Set.new) {|s,o| s << o[0]; s }
335
+
336
+ # レート一覧
337
+ result = link_click( "1" )
338
+
339
+ ClickClientScrap::Client.error( result ) if result.forms.empty?
340
+ form = result.forms.first
341
+
342
+ # 通貨ペア
343
+ option = form.fields.find{|f| f.name == "P001" }.options.find {|o|
344
+ to_pair( o.text.strip ) == currency_pair_code
345
+ }
346
+ raise "illegal currency_pair_code. currency_pair_code=#{currency_pair_code.to_s}" unless option
347
+ option.select
348
+
349
+ #注文方式
350
+ form["P100"] = type
351
+
352
+ # 詳細設定画面へ
353
+ result = @client.submit(form)
354
+ ClickClientScrap::Client.error( result ) if result.forms.empty?
355
+ form = result.forms.first
356
+ case type
357
+ when ORDER_TYPE_MARKET_ORDER
358
+ # 成り行き
359
+ form["P003"] = unit.to_s # 取り引き数量
360
+ form["P002.0"] = sell_or_buy == ClickClientScrap::FX::SELL ? "1" : "0" #売り/買い
361
+ form["P005"] = options[:slippage].to_s if ( options && options[:slippage] != nil ) # スリッページ
362
+ when ORDER_TYPE_NORMAL
363
+ # 指値
364
+ form["P003"] = options[:rate].to_s # レート
365
+ form["P005"] = unit.to_s # 取り引き数量
366
+ form["P002.0"] = sell_or_buy == ClickClientScrap::FX::SELL ? "1" : "0" #売り/買い
367
+ exp = options[:execution_expression]
368
+ form["P004.0"] = exp == ClickClientScrap::FX::EXECUTION_EXPRESSION_REVERSE_LIMIT_ORDER ? "2" : "1" #指値/逆指値
369
+ set_expiration( form, options, "P008", "P009" ) # 有効期限
370
+ when ORDER_TYPE_OCO
371
+ # OCO
372
+ form["P003"] = options[:rate].to_s # レート
373
+ form["P005"] = options[:stop_order_rate].to_s # 逆指値レート
374
+ form["P007"] = unit.to_s # 取り引き数量
375
+ form["P002.0"] = sell_or_buy == ClickClientScrap::FX::SELL ? "1" : "0" #売り/買い
376
+ set_expiration( form, options, "P010", "P011" ) # 有効期限
377
+ else
378
+ raise "not supported yet."
379
+ end
380
+
381
+ # 確認画面へ
382
+ result = @client.submit(form)
383
+ ClickClientScrap::Client.error( result ) if result.forms.empty?
384
+ result = @client.submit(result.forms.first)
385
+ ClickClientScrap::Client.error( result ) unless result.body.toutf8 =~ /注文受付完了/
386
+
387
+ #注文前の一覧と注文後の一覧を比較して注文を割り出す。
388
+ #成り行き注文の場合、即座に約定するのでnilになる(タイミングによっては取得できるかも)
389
+ tmp = list_orders( ORDER_CONDITION_ON_ORDER ).find {|o| !before.include?(o[0]) }
390
+ return OrderResult.new( tmp ? tmp[1].order_no : nil )
391
+ end
392
+
393
+ # 有効期限を設定する
394
+ #form:: フォーム
395
+ #options:: パラメータ
396
+ #input_type:: 有効期限の種別を入力するinput要素名
397
+ #input_date:: 有効期限が日付指定の場合に、日付を入力するinput要素名
398
+ def set_expiration( form, options, input_type, input_date )
399
+ case options[:expiration_type]
400
+ when ClickClientScrap::FX::EXPIRATION_TYPE_TODAY
401
+ form[input_type] = "0"
402
+ when ClickClientScrap::FX::EXPIRATION_TYPE_WEEK_END
403
+ form[input_type] = "1"
404
+ when ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED
405
+ form[input_type] = "3"
406
+ raise "options[:expiration_date] is required." unless options[:expiration_date]
407
+ form["#{input_date}.Y"] = options[:expiration_date].year
408
+ form["#{input_date}.M"] = options[:expiration_date].month
409
+ form["#{input_date}.D"] = options[:expiration_date].day
410
+ form["#{input_date}.h"] = options[:expiration_date].respond_to?(:hour) ? options[:expiration_date].hour : "0"
411
+ else
412
+ form[input_type] = "2"
413
+ end
414
+ end
415
+
416
+ #
417
+ #=== 注文をキャンセルします。
418
+ #
419
+ #order_no:: 注文番号
420
+ #戻り値:: なし
421
+ #
422
+ def cancel_order( order_no )
423
+
424
+ raise "order_no is nil." unless order_no
425
+
426
+ # 注文一覧
427
+ result = link_click( "2" )
428
+ ClickClientScrap::Client.error( result ) if result.forms.empty?
429
+ form = result.forms.first
430
+ form["P002"] = ORDER_CONDITION_ON_ORDER
431
+ result = @client.submit(form)
432
+
433
+ # 対象となる注文をクリック
434
+ link = nil
435
+ each_page( result ) {|page|
436
+ link = page.links.find {|l|
437
+ l.href =~ /[^"]*GKEY=([a-zA-Z0-9]*)[^"]*/ && $1 == order_no
438
+ }
439
+ break if link
440
+ }
441
+ raise "illegal order_no. order_no=#{order_no}" unless link
442
+ result = @client.click(link)
443
+ ClickClientScrap::Client.error( result ) if result.forms.empty?
444
+
445
+ # キャンセル
446
+ form = result.forms[1]
447
+ result = @client.submit(form)
448
+ ClickClientScrap::Client.error( result ) if result.forms.empty?
449
+ form = result.forms.first
450
+ result = @client.submit(form)
451
+ ClickClientScrap::Client.error( result ) unless result.body.toutf8 =~ /注文取消受付完了/
452
+ end
453
+
454
+
455
+ #
456
+ #=== 決済注文を行います。
457
+ #
458
+ #*open_interest_id*:: 決済する建玉番号
459
+ #*unit*:: 取引数量
460
+ #*options*:: 決済注文のオプション。注文方法に応じて以下の情報を設定できます。
461
+ # - <b>成り行き注文</b>
462
+ # - <tt>:slippage</tt> .. スリッページ (オプション)
463
+ # - <tt>:slippage_base_rate</tt> .. スリッページの基準となる取引レート(スリッページが指定された場合、必須。)
464
+ # - <b>通常注文</b> <b>※未実装</b> ※注文レートが設定されていれば通常取引となります。
465
+ # - <tt>:rate</tt> .. 注文レート(必須)
466
+ # - <tt>:execution_expression</tt> .. 執行条件。ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER等を指定します(必須)
467
+ # - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
468
+ # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
469
+ # - <b>OCO注文</b> <b>※未実装</b> ※注文レートと逆指値レートが設定されていればOCO取引となります。
470
+ # - <tt>:rate</tt> .. 注文レート(必須)
471
+ # - <tt>:stop_order_rate</tt> .. 逆指値レート(必須)
472
+ # - <tt>:expiration_type</tt> .. 有効期限。ClickClientScrap::FX::EXPIRATION_TYPE_TODAY等を指定します(必須)
473
+ # - <tt>:expiration_date</tt> .. 有効期限が「日付指定(ClickClientScrap::FX::EXPIRATION_TYPE_SPECIFIED)」の場合の有効期限をDateで指定します。(有効期限が「日付指定」の場合、必須)
474
+ #<b>戻り値</b>:: なし
475
+ #
476
+ def settle ( open_interest_id, unit, options={} )
477
+ if ( options[:rate] != nil && options[:stop_order_rate] != nil )
478
+ # レートと逆指値レートが指定されていればOCO取引
479
+ raise "options[:expiration_type] is required." if options[:expiration_type] == nil
480
+ elsif ( options[:rate] != nil )
481
+ # レートが指定されていれば通常取引
482
+ raise "options[:execution_expression] is required." if options[:execution_expression] == nil
483
+ raise "options[:expiration_type] is required." if options[:expiration_type] == nil
484
+ else
485
+ # 成り行き
486
+ if ( options[:slippage] != nil )
487
+ raise "if you use a slippage, options[:slippage_base_rate] is required." if options[:slippage_base_rate] == nil
488
+ end
489
+ end
490
+
491
+ # 建玉一覧
492
+ result = link_click( "3" )
493
+
494
+ # 対象となる建玉をクリック
495
+ link = nil
496
+ each_page( result ) {|page|
497
+ link = page.links.find {|l|
498
+ l.href =~ /[^"]*ORDERNO=([a-zA-Z0-9]*)[^"]*/ && $1 == open_interest_id
499
+ }
500
+ break if link
501
+ }
502
+ raise "illegal open_interest_id. open_interest_id=#{open_interest_id}" unless link
503
+ result = @client.click(link)
504
+
505
+ # 決済
506
+ form = result.forms.first
507
+ form["P100"] = "00" # 成り行き TODO 通常(01),OCO取引(21)対応
508
+ result = @client.submit(form)
509
+ ClickClientScrap::Client.error( result ) if result.forms.empty?
510
+
511
+ # 設定
512
+ form = result.forms.first
513
+ form["P003"] = unit.to_s
514
+ form["P005"] = options[:slippage].to_s if options[:slippage]
515
+ result = @client.submit(form)
516
+ ClickClientScrap::Client.error( result ) if result.forms.empty?
517
+
518
+ # 確認
519
+ form = result.forms.first
520
+ result = @client.submit(form)
521
+ ClickClientScrap::Client.error( result ) unless result.body.toutf8 =~ /完了/
522
+ end
523
+
524
+
525
+ #
526
+ #=== 注文一覧を取得します。
527
+ #
528
+ #order_condition_code:: 注文状況コード(必須)
529
+ #currency_pair_code:: 通貨ペアコード <b>※未実装</b>
530
+ #戻り値:: 注文番号をキーとするClickClientScrap::FX::Orderのハッシュ。
531
+ #
532
+ def list_orders( order_condition_code=ClickClientScrap::FX::ORDER_CONDITION_ALL, currency_pair_code=nil )
533
+ result = link_click( "2" )
534
+ ClickClientScrap::Client.error( result ) if result.forms.empty?
535
+ form = result.forms.first
536
+ form["P001"] = "" # TODO currency_pair_codeでの絞り込み
537
+ form["P002"] = order_condition_code
538
+ result = @client.submit(form)
539
+
540
+ list = []
541
+ each_page( result ) {|page|
542
+ list += page.body.toutf8.scan( /<a href="[^"]*GKEY=([a-zA-Z0-9]*)">([A-Z]{3}\/[A-Z]{3}) ([^<]*)<\/a><br>[^;]*;([^<]*)<font[^>]*>([^<]*)<\/font>([^@]*)@([\d\.]*)([^\s]*) ([^<]*)<br>([^;]*;([^<]*)<font[^>]*>([^<]*)<\/font>([^@]*)@([\d\.]*)([^\s]*) ([^<]*)<br>)?/m )
543
+ }
544
+ tmp = {}
545
+ list.each {|i|
546
+ order_no = i[0]
547
+ order_type = to_order_type_code(i[2])
548
+ trade_type = i[3] == "新" ? ClickClientScrap::FX::TRADE_TYPE_NEW \
549
+ : ClickClientScrap::FX::TRADE_TYPE_SETTLEMENT
550
+ pair = to_pair( i[1] )
551
+ sell_or_buy = i[4] == "売" ? ClickClientScrap::FX::SELL : ClickClientScrap::FX::BUY
552
+ count = pair == :ZARJPY ? i[5].to_i/10 : i[5].to_i
553
+ rate = i[6].to_f
554
+ execution_expression = to_execution_expression(i[7])
555
+ stop_order_rate = i[13] ? i[13].to_f : nil
556
+ stop_order_execution_expression = i[14] ? to_execution_expression(i[14]) : nil
557
+
558
+ tmp[order_no] = Order.new( order_no, trade_type, order_type, execution_expression, \
559
+ sell_or_buy, pair, count, rate, i[8], stop_order_rate, stop_order_execution_expression)
560
+ }
561
+ return tmp
562
+ end
563
+
564
+ #
565
+ #=== 建玉一覧を取得します。
566
+ #
567
+ #currency_pair_code:: 通貨ペアコード。<b>※未実装</b>
568
+ #戻り値:: 建玉IDをキーとするClickClientScrap::FX::OpenInterestのハッシュ。
569
+ #
570
+ def list_open_interests( currency_pair_code=nil )
571
+ result = link_click( "3" )
572
+ ClickClientScrap::Client.error( result ) if result.forms.empty?
573
+ form = result.forms.first
574
+ form["P001"] = "" # TODO currency_pair_codeでの絞り込み
575
+ result = @client.submit(form)
576
+
577
+ list = []
578
+ each_page( result ) {|page|
579
+ list += page.body.toutf8.scan( /<a href="[^"]*">([A-Z]{3}\/[A-Z]{3}):([^<]*)<\/a><br>[^;]*;<font[^>]*>([^<]*)<\/font>([\d\.]*)[^\s@]*@([\d\.]*).*?<font[^>]*>([^<]*)<\/font>/m )
580
+ }
581
+ tmp = {}
582
+ list.each {|i|
583
+ open_interest_id = i[1]
584
+ pair = to_pair( i[0] )
585
+ sell_or_buy = i[2] == "売" ? ClickClientScrap::FX::SELL : ClickClientScrap::FX::BUY
586
+ count = i[3].to_i
587
+ rate = i[4].to_f
588
+ profit_or_loss = i[5].to_i
589
+ tmp[open_interest_id] = OpenInterest.new(open_interest_id, pair, sell_or_buy, count, rate, profit_or_loss )
590
+ }
591
+ return tmp
592
+ end
593
+
594
+ # すべてのページを列挙する。
595
+ def each_page( page )
596
+ current_page=1
597
+ while ( page )
598
+ yield page
599
+ current_page+=1
600
+ link_to_next = page.links.find{|i|
601
+ i.text == current_page.to_s
602
+ }
603
+ page = link_to_next ? @client.click( link_to_next ) : nil
604
+ end
605
+ end
606
+
607
+ #
608
+ #=== 余力情報を取得します。
609
+ #
610
+ #戻り値:: ClickClientScrap::FX::Marginのハッシュ。
611
+ #
612
+ def get_margin
613
+ result = link_click( "7" )
614
+ list = result.body.toutf8.scan( /【([^<]*)[^>]*>[^>]*>([^<]*)</m )
615
+ values = list.inject({}) {|r,i|
616
+ if ( i[0] == "証拠金維持率】" )
617
+ r[i[0]] = i[1]
618
+ else
619
+ r[i[0]] = i[1].gsub(/,/, "").to_i
620
+ end
621
+ r
622
+ }
623
+ return Margin.new(
624
+ values["時価評価総額】"],
625
+ values["建玉評価損益】"],
626
+ values["口座残高】"],
627
+ values["証拠金維持率】"],
628
+ values["余力】"],
629
+ values["拘束証拠金】"],
630
+ values["必要証拠金】"],
631
+ values["注文中必要証拠金】"],
632
+ values["振替可能額】"]
633
+ )
634
+ end
635
+
636
+ # ログアウトします。
637
+ def logout
638
+ @client.click( @links.find {|i|
639
+ i.text == "\303\233\302\270\303\236\302\261\302\263\303\204" \
640
+ || i.text == "ログアウト"
615
641
  })
616
- end
617
-
618
- private
619
- # "USD/JPY"を:USDJPYのようなシンボルに変換します。
620
- def to_pair( str )
621
- str.gsub( /\//, "" ).to_sym
622
- end
623
-
624
- # 注文種別を注文種別コードに変換します。
625
- def to_order_type_code( order_type )
626
- return case order_type
627
- when "成行注文"
628
- ClickClientScrap::FX::ORDER_TYPE_MARKET_ORDER
629
- when "通常注文"
630
- ClickClientScrap::FX::ORDER_TYPE_NORMAL
631
- when "OCO注文"
632
- ClickClientScrap::FX::ORDER_TYPE_OCO
633
- when "IFD注文"
634
- ClickClientScrap::FX::ORDER_TYPE_IFD
635
- when "IFD-OCO注文"
636
- ClickClientScrap::FX::ORDER_TYPE_IFD_OCO
637
- else
638
- raise "illegal order_type. order_type=#{order_type}"
639
- end
640
- end
641
-
642
- def link_click( no )
643
- link = @links.find {|i|
644
- i.attributes["accesskey"] == no
645
- }
646
- raise "link isnot found. accesskey=#{no}" unless link
647
- @client.click( link )
648
- end
649
- end
650
-
651
- # オプション
652
- attr :options, true
653
-
654
- #=== スワップ
655
- Swap = Struct.new(:pair, :sell_swap, :buy_swap)
656
- #=== レート
657
- Rate = Struct.new(:pair, :bid_rate, :ask_rate, :sell_swap, :buy_swap )
658
- #===注文
659
- Order = Struct.new(:order_no, :trade_type, :order_type, :execution_expression, :sell_or_buy, :pair, :count, :rate, :order_state )
660
- #===注文結果
661
- OrderResult = Struct.new(:order_no )
662
- #===建玉
663
- OpenInterest = Struct.new(:open_interest_id, :pair, :sell_or_buy, :count, :rate, :profit_or_loss )
664
- #===余力
665
- Margin = Struct.new(
666
- :market_value, #時価評価の総額
667
- :appraisal_profit_or_loss_of_open_interest, #建玉の評価損益
668
- :balance_in_account, # 口座残高
669
- :guarantee_money_maintenance_ratio, #証拠金の維持率
670
- :margin, #余力
671
- :freezed_guarantee_money, #拘束されている証拠金
672
- :required_guarantee_money, #必要な証拠金
673
- :ordered_guarantee_money, #注文中の証拠金
674
- :transferable_money_amount #振替可能額
675
- )
676
- end
677
- end
678
-
679
- class << WWW::Mechanize::Util
680
- def from_native_charset(s, code)
681
- if WWW::Mechanize.html_parser == Nokogiri::HTML
682
- return unless s
683
- Iconv.iconv(code, "UTF-8", s).join("") rescue s # エラーになった場合、変換前の文字列を返す
684
- else
685
- return s
686
- end
687
- end
688
- end
689
-
690
-
642
+ end
643
+
644
+ private
645
+ # "USD/JPY"を:USDJPYのようなシンボルに変換します。
646
+ def to_pair( str )
647
+ str.gsub( /\//, "" ).to_sym
648
+ end
649
+
650
+ # 注文種別を注文種別コードに変換します。
651
+ def to_execution_expression( execution_expression )
652
+ if execution_expression == "指"
653
+ return ClickClientScrap::FX::EXECUTION_EXPRESSION_LIMIT_ORDER
654
+ elsif execution_expression == "逆"
655
+ return ClickClientScrap::FX::EXECUTION_EXPRESSION_REVERSE_LIMIT_ORDER
656
+ else
657
+ return ClickClientScrap::FX::EXECUTION_EXPRESSION_MARKET_ORDER
658
+ end
659
+ end
660
+
661
+ # 注文種別を注文種別コードに変換します。
662
+ def to_order_type_code( order_type )
663
+ return case order_type
664
+ when "成行注文"
665
+ ClickClientScrap::FX::ORDER_TYPE_MARKET_ORDER
666
+ when "通常注文"
667
+ ClickClientScrap::FX::ORDER_TYPE_NORMAL
668
+ when "OCO注文"
669
+ ClickClientScrap::FX::ORDER_TYPE_OCO
670
+ when "IFD注文"
671
+ ClickClientScrap::FX::ORDER_TYPE_IFD
672
+ when "IFD-OCO注文"
673
+ ClickClientScrap::FX::ORDER_TYPE_IFD_OCO
674
+ else
675
+ raise "illegal order_type. order_type=#{order_type}"
676
+ end
677
+ end
678
+
679
+ def link_click( no )
680
+ link = @links.find {|i|
681
+ i.attributes["accesskey"] == no
682
+ }
683
+ raise "link isnot found. accesskey=#{no}" unless link
684
+ @client.click( link )
685
+ end
686
+ end
687
+
688
+ # オプション
689
+ attr :options, true
690
+
691
+ #=== スワップ
692
+ Swap = Struct.new(:pair, :sell_swap, :buy_swap)
693
+ #=== レート
694
+ Rate = Struct.new(:pair, :bid_rate, :ask_rate, :sell_swap, :buy_swap )
695
+ #===注文
696
+ Order = Struct.new(:order_no, :trade_type, :order_type, :execution_expression, :sell_or_buy, :pair,
697
+ :count, :rate, :order_state, :stop_order_rate, :stop_order_execution_expression )
698
+ #===注文結果
699
+ OrderResult = Struct.new(:order_no )
700
+ #===建玉
701
+ OpenInterest = Struct.new(:open_interest_id, :pair, :sell_or_buy, :count, :rate, :profit_or_loss )
702
+ #===余力
703
+ Margin = Struct.new(
704
+ :market_value, #時価評価の総額
705
+ :appraisal_profit_or_loss_of_open_interest, #建玉の評価損益
706
+ :balance_in_account, # 口座残高
707
+ :guarantee_money_maintenance_ratio, #証拠金の維持率
708
+ :margin, #余力
709
+ :freezed_guarantee_money, #拘束されている証拠金
710
+ :required_guarantee_money, #必要な証拠金
711
+ :ordered_guarantee_money, #注文中の証拠金
712
+ :transferable_money_amount #振替可能額
713
+ )
714
+ end
715
+ end
716
+
717
+ class << WWW::Mechanize::Util
718
+ def from_native_charset(s, code)
719
+ if WWW::Mechanize.html_parser == Nokogiri::HTML
720
+ return unless s
721
+ Iconv.iconv(code, "UTF-8", s).join("") rescue s # エラーになった場合、変換前の文字列を返す
722
+ else
723
+ return s
724
+ end
725
+ end
726
+ end
727
+
728
+