Skip to content

Instantly share code, notes, and snippets.

@m4i
Created April 28, 2009 16:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save m4i/103252 to your computer and use it in GitHub Desktop.
Save m4i/103252 to your computer and use it in GitHub Desktop.
// ==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