Created
April 28, 2009 16:33
-
-
Save m4i/103252 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name SBI SECURITIES Future Trade Support | |
// @namespace http://m4i.jp/ | |
// @include https://depindex.sbisec.co.jp/cfront/servlet/cfront.OrderController* | |
// @require http://code.jquery.com/jquery-1.2.6.min.js | |
// ==/UserScript== | |
var Util = { | |
memoize: function(property, object/* = window */) { | |
if (property.constructor === Array) { | |
for (var i = 0, l = property.length; i < l; i++) { | |
arguments.callee(property[i], object); | |
} | |
} else { | |
object = object || window; | |
var original = object[property]; | |
object[property] = function() { | |
if (typeof arguments.callee.memo === 'undefined') { | |
arguments.callee.memo = original.call(object); | |
} | |
return arguments.callee.memo; | |
}; | |
} | |
}, | |
raiseError: function() { | |
alert('UserScript: SBI SECURITIES Future Trade Support\n\nERROR!'); | |
throw true; | |
}, | |
getUpperPrice: function(price) { | |
switch (true) { | |
case Page.isLarge(): return price + 10; | |
case Page.isMini(): return price + 5; | |
case Page.isOption(): | |
if (price < 20) return price + 1; | |
if (price < 1000) return price + 5; | |
return price + 10; | |
} | |
Util.raiseError(); | |
}, | |
getLowerPrice: function(price) { | |
switch (true) { | |
case Page.isLarge(): return price - 10; | |
case Page.isMini(): return price - 5; | |
case Page.isOption(): | |
if (price > 1000) return price - 10; | |
if (price > 20) return price - 5; | |
return price - 1; | |
} | |
Util.raiseError(); | |
}, | |
}; | |
var Page = { | |
isFuture: function() { | |
return this.isFutureTop() | |
|| this.isLarge() | |
|| this.isMini(); | |
}, | |
isFutureTop: function() { | |
var ml_type_inputs = this._getMlTypeInputs(); | |
return ml_type_inputs.length === 2 | |
&& ml_type_inputs[0].value === '1' | |
&& ml_type_inputs[1].value === '2'; | |
}, | |
isLarge: function() { | |
var ml_type_inputs = this._getMlTypeInputs(); | |
return ml_type_inputs.length === 1 | |
&& ml_type_inputs[0].value === '1'; | |
}, | |
isMini: function() { | |
var ml_type_inputs = this._getMlTypeInputs(); | |
return ml_type_inputs.length === 1 | |
&& ml_type_inputs[0].value === '2'; | |
}, | |
isOption: function() { | |
return this.isOptionTop() | |
|| this.isCall() | |
|| this.isPut(); | |
}, | |
isOptionTop: function() { | |
var put_call_inputs = this._getPutCalInputs(); | |
return put_call_inputs.length === 2 | |
&& put_call_inputs[0].value === '2' | |
&& put_call_inputs[1].value === '1'; | |
}, | |
isCall: function() { | |
var put_call_inputs = this._getPutCalInputs(); | |
return put_call_inputs.length === 1 | |
&& put_call_inputs[0].value === '2'; | |
}, | |
isPut: function() { | |
var put_call_inputs = this._getPutCalInputs(); | |
return put_call_inputs.length === 1 | |
&& put_call_inputs[0].value === '1'; | |
}, | |
isCorrecting: function() { | |
return document.getElementsByName('CORRECT_PRICE').length > 0; | |
}, | |
hasKehaiIta: function() { | |
return this.isLarge() | |
|| this.isMini() | |
|| this.isCall() | |
|| this.isPut(); | |
}, | |
_getMlTypeInputs: function() { | |
return document.getElementsByName('ML_TYPE'); | |
}, | |
_getPutCalInputs: function() { | |
return document.getElementsByName('PUT_CALL'); | |
}, | |
}; | |
for (var property in Page) Util.memoize(property, Page); | |
var Radioize = { | |
ORDER_PATTERN: { | |
TSUJO: '0', | |
PAIR: '1', | |
STEP: '2', | |
STEP_PAIR: '3', | |
}, | |
ORDER_CONDITION_GROUP: { | |
SASHINE: ['1', '2', '3', '4', '5'], | |
NARIYUKI: ['6', '7', '8'], | |
}, | |
orderPattern: function() { | |
this._radioize('ORDER_PATTERN', 4, unsafeWindow.PatternOnChange); | |
}, | |
orderCondition: function() { | |
var order_pattern_select = document.getElementsByName('ORDER_PATTERN')[0]; | |
if (!order_pattern_select) return; | |
var NARIYUKI = this.ORDER_CONDITION_GROUP.NARIYUKI; | |
var priceInput = document.getElementsByName(Page.isCorrecting() | |
? 'CORRECT_PRICE' : 'PRICE')[0]; | |
var previousPrice = ''; | |
var disablePrice = function(select) { | |
if ($.inArray(select.value, NARIYUKI) == -1) { | |
if (!priceInput.value) { | |
priceInput.value = previousPrice; | |
} | |
} else { | |
if (priceInput.value) { | |
previousPrice = priceInput.value; | |
} | |
priceInput.value = ''; | |
} | |
}; | |
var orderConditionNamePrefix = Page.isCorrecting() | |
? 'CORRECT_ORDER_CONDITION' : 'ORDER_CONDITION'; | |
var order_pattern = $(order_pattern_select).val(); | |
this._radioize(orderConditionNamePrefix, | |
order_pattern == this.ORDER_PATTERN.TSUJO ? 4 : 1, | |
order_pattern == this.ORDER_PATTERN.PAIR | |
? unsafeWindow.PatternOnChange : disablePrice | |
); | |
this._radioize(orderConditionNamePrefix + '_IFDONE', 1); | |
this._radioize(orderConditionNamePrefix + '_IFDOCO1', 1, | |
unsafeWindow.PatternOnChange); | |
}, | |
_radioize: function(name, columns, onClickCallback) { | |
var select = document.getElementsByName(name)[0]; | |
if (!select) return; | |
name = name.toLowerCase() + '_'; | |
onClickCallback = onClickCallback || function() {}; | |
$(select).css({ | |
position: 'absolute', | |
visibility: 'hidden', | |
}); | |
var container = select.parentNode; | |
// ずれないように余分な空白を削除 | |
this._removeTextChildNodes(container); | |
$(select).find('option').each(function(i) { | |
if (columns && i != 0 && i % columns == 0) { | |
container.appendChild(document.createElement('br')); | |
} | |
var id = name + this.value; | |
var radio = document.createElement('input'); | |
radio.type = 'radio'; | |
radio.id = id; | |
radio.name = name; | |
radio.value = this.value; | |
radio.checked = this.selected; | |
$(radio).click(function() { | |
if (this.value == $(select).val()) return; | |
$(select).val(this.value); | |
onClickCallback(select); | |
}); | |
var label = document.createElement('label'); | |
label.setAttribute('for', id); | |
$(label).text($(this).text()); | |
container.appendChild(radio); | |
container.appendChild(label); | |
}); | |
}, | |
_removeTextChildNodes: function(element) { | |
for (var i = element.childNodes.length - 1; i >= 0; i--) { | |
var node = element.childNodes[i]; | |
if (node.nodeType == 3 && /^\s*$/.test(node.data)) { | |
element.removeChild(node); | |
} | |
} | |
}, | |
}; | |
function setPriceByKehaine() { | |
if (!Page.hasKehaiIta()) return; | |
var NUMBER_OF_APPEND_ROWS = 9; | |
var NUMBER_OF_MAX_MIDDLE_ROWS = 4; | |
var tbody = $('table[width=235] tbody').eq(0); | |
if (tbody.find('tr:eq(0) div:eq(1)').text() != '気配値') Util.raiseError(); | |
tbody.find('div').css({ | |
lineHeight: '1', | |
fontSize: 'xx-small', | |
}); | |
var trs = tbody.find('tr:gt(1)'); | |
var prices = trs.find('div:eq(1)').map( | |
function() toNumberFromFormattedNumber($(this).text())); | |
var rowIndex = { ask: { max: 0 }, bit: { min: trs.length - 1 } }; | |
rowIndex.ask.min = Math.floor(rowIndex.bit.min / 2); | |
rowIndex.bit.max = Math.ceil(rowIndex.bit.min / 2); | |
var lack = { ask: false, bit: false }; | |
// 気配値が全く存在しないければ中断 | |
if (!prices[rowIndex.ask.min] && !prices[rowIndex.bit.max]) return; | |
// ask を埋める | |
for (var i = rowIndex.ask.min; i >= rowIndex.ask.max; i--) { | |
if (prices[i]) continue; | |
prices[i] = Util.getUpperPrice(prices[i + 1]); | |
var divs = trs.eq(i).find('div'); | |
divs.eq(1).text(formatNumber(prices[i])); | |
divs.eq(0).text('0'); | |
lack.ask = true; | |
} | |
// bit を埋める | |
for (var i = rowIndex.bit.max; i <= rowIndex.bit.min; i++) { | |
if (prices[i]) continue; | |
prices[i] = Util.getLowerPrice(prices[i - 1]); | |
if (prices[i] <= 0) break; | |
var divs = trs.eq(i).find('div'); | |
divs.eq(1).text(formatNumber(prices[i])); | |
divs.eq(2).text('0'); | |
lack.bit = true; | |
} | |
// 追加行の skelton を作成 | |
var skelton = { | |
ask: $(trs[rowIndex.ask.max].cloneNode(true)), | |
middle: $(tbody.find('tr')[1].cloneNode(true)), | |
bit: $(trs[rowIndex.bit.min].cloneNode(true)), | |
}; | |
skelton.ask.find('div:last').text(''); | |
skelton.middle.find('div').text('0'); | |
skelton.bit.find('div:first').text(''); | |
// ask と bit の間を追加 | |
var ask = prices[rowIndex.ask.min], bit = prices[rowIndex.bit.max]; | |
var insertBase = trs.eq(rowIndex.ask.min); | |
var numberOfAppendRows = { | |
ask: NUMBER_OF_APPEND_ROWS, | |
bit: NUMBER_OF_APPEND_ROWS, | |
}; | |
for (var i = 0; i < NUMBER_OF_MAX_MIDDLE_ROWS; i++) { | |
ask = Util.getLowerPrice(ask); | |
if (ask === bit) break; | |
skelton.middle.find('div:eq(1)').text(formatNumber(ask)); | |
insertBase.after(skelton.middle[0].cloneNode(true)); | |
insertBase = insertBase.next(); | |
numberOfAppendRows.ask--; | |
bit = Util.getUpperPrice(bit); | |
if (bit === ask) break; | |
skelton.middle.find('div:eq(1)').text(formatNumber(bit)); | |
insertBase.after(skelton.middle[0].cloneNode(true)); | |
numberOfAppendRows.bit--; | |
} | |
// ask と bit の外側を追加 | |
var appendRows = function(ask_or_bit, price, getNextPrice, kehaineSu, selector) { | |
for (var i = 0; i < numberOfAppendRows[ask_or_bit]; i++) { | |
price = getNextPrice(price); | |
if (price <= 0) break; | |
var tr = $(skelton[ask_or_bit][0].cloneNode(true)); | |
tr.find('div:eq(1)').text(formatNumber(price)); | |
tr.find('div' + kehaineSu).text(lack[ask_or_bit] ? '0' : '----'); | |
tbody.find(selector).after(tr); | |
} | |
}; | |
appendRows('ask', prices[rowIndex.ask.max], Util.getUpperPrice, ':first', 'tr:eq(1)'); | |
appendRows('bit', prices[rowIndex.bit.min], Util.getLowerPrice, ':last', 'tr:last'); | |
var priceNamePrefix = Page.isCorrecting() ? 'CORRECT_PRICE' : 'PRICE'; | |
var priceInput; | |
if (priceInput = document.getElementsByName(priceNamePrefix)[0]) { | |
var priceInputs = []; | |
priceInputs[0] = $(priceInput); | |
if (priceInput = document.getElementsByName(priceNamePrefix + '_OCO')[0]) { | |
priceInputs[1] = priceInputs[0]; | |
priceInputs[2] = $(priceInput); | |
} else if (priceInput = document.getElementsByName(priceNamePrefix + '_IFDONE')[0]) { | |
priceInputs[1] = priceInputs[0]; | |
priceInputs[2] = $(priceInput); | |
} else if (priceInput = document.getElementsByName(priceNamePrefix + '_IFDOCO1')[0]) { | |
priceInputs[1] = $(priceInput); | |
priceInputs[2] = $(document.getElementsByName(priceNamePrefix + '_IFDOCO2')[0]); | |
} else { | |
priceInputs[1] = priceInputs[0]; | |
priceInputs[2] = priceInputs[0]; | |
} | |
var setPrice = function(price, index) { | |
priceInputs[index].val(price); | |
}; | |
var onMouseOver = function(index) { | |
$(this).css('background-color', '#fc9'); | |
priceInputs[index].css('background-color', '#fc9'); | |
}; | |
var onMouseOut = function(index) { | |
$(this).css('background-color', ''); | |
priceInputs[index].css('background-color', ''); | |
}; | |
tbody.find('tr:gt(1)').each(function() { | |
var price = toNumberFromFormattedNumber($(this).find('div:eq(1)').text()); | |
$(this).find('td').each(function(i) { | |
$(this) | |
.click(function() setPrice(price, i)) | |
.mouseover(function() onMouseOver.call(this, i)) | |
.mouseout(function() onMouseOut.call(this, i)) | |
.css('cursor', 'pointer'); | |
}); | |
}); | |
} | |
function toNumberFromFormattedNumber(string) { | |
return Number(string.replace(/\D+/g, '')); | |
} | |
function formatNumber(number) { | |
return String(number).replace(/\d(?=(?:\d{3})+$)/g, '$&,'); | |
} | |
} | |
function setQuantity() { | |
var quantity_input = document.getElementsByName('QUANTITY')[0]; | |
if (!quantity_input) return; | |
quantity_input.value = GM_getValue('quantity') || 1; | |
$(quantity_input).change(function() GM_setValue('quantity', this.value)); | |
} | |
function setPassword() { | |
var trade_pwd_input = document.getElementsByName('trade_pwd')[0]; | |
if (!trade_pwd_input) return; | |
var accountNumber = getAccountNumber(); | |
if (!accountNumber) return; | |
var password; | |
var cryptedPassword = GM_getValue('password'); | |
if (cryptedPassword) { | |
password = decryptPassword(cryptedPassword, accountNumber); | |
} else { | |
var message = [ | |
'UserScript - SBI SECURITIES Future Trade Support', | |
'', | |
'取引パスワードを入力してください', | |
'', | |
'入力した取引パスワードは口座番号を鍵としてごく簡単な暗号化がなされ prefs.js に保存されます。', | |
'よって、口座番号と prefs.js を入手し、この UserScript を読むことができれば取引パスワードを知ることが可能です。', | |
'ご利用には十分ご注意ください。', | |
'なお保存されたパスワードの削除は about:config から行ってください。', | |
].join('\n'); | |
if (!(password = prompt(message))) return; | |
GM_setValue('password', cryptPassword(password, accountNumber)); | |
} | |
trade_pwd_input.value = password; | |
function getAccountNumber() { | |
if (!unsafeWindow.top.indicator) return; | |
return $(unsafeWindow.top.indicator.document) | |
.find('font:eq(0)[color=#666666]').text(); | |
} | |
function cryptPassword(password, accountNumber) { | |
var key = makeKey(accountNumber); | |
return uneval(password.split('').map(function(c, i) | |
c.charCodeAt(0) ^ key.charCodeAt(i % key.length))); | |
} | |
function decryptPassword(cryptedPassword, accountNumber) { | |
var key = makeKey(accountNumber); | |
return String.fromCharCode.apply(null, eval(cryptedPassword) | |
.map(function(c, i) c ^ key.charCodeAt(i % key.length))); | |
} | |
function makeKey(accountNumber) { | |
return accountNumber.replace(/\D+/g, ''); | |
} | |
} | |
function checkSkipEstimate() { | |
var skip_estimate_checkbox = document.getElementsByName('skip_estimate')[0]; | |
if (!skip_estimate_checkbox) return; | |
skip_estimate_checkbox.checked = true; | |
} | |
// 注文パターンをラジオボタンに | |
Radioize.orderPattern(); | |
// 執行条件をラジオボタンに + 成行で価格リセット | |
Radioize.orderCondition(); | |
// 価格を気配値をクリックしてセット | |
setPriceByKehaine(); | |
// 数量に前回入力した値をセット | |
setQuantity(); | |
// 取引パスワードをセット | |
setPassword(); | |
//「注文確認画面を省略」を常にチェック | |
checkSkipEstimate(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment