見出し画像

いなごフライヤーFutures Market Action Checkerのアラート履歴&売買ボリューム可視化ブックマークレットv3 (更新対応)

あいづです。

ホームページが更新されたので対応しました。

導入方法などは過去の記事を見てください。


ソースコード

javascript: class InagoFmacExtension {
 constructor(document) {
   this.document = document;
   this.soundAttributes = {};
   this.categories = ["bond", "fx", "stockindex", "stock", "gold", "oil", "agriculture"];
   this.dtf = new Intl.DateTimeFormat("jp", {
     "hourCycle": "h24",
     "year": "numeric",
     "month": "2-digit",
     "day": "2-digit",
     "hour": "2-digit",
     "minute": "2-digit",
     "second": "2-digit"
   });
   this.moneyVolumesByTS = {};
   this.moneyVolumesSum = {};
   this.mvRange = {
     max: 0,
     min: 0
   };
   for (const [i, cat] of this.categories.entries()) {
     this.moneyVolumesByTS[cat] = {};
     this.moneyVolumesSum[cat] = 0;
   }
 } /* 音声とそれにかかわる属性をまとめる。属性は カテゴリ(bond,fxなど)、上昇下降('up' or 'down') を指し、  音声のId(既存プログラム上では SoundNum )をキーにカテゴリと上昇下降の値を保持する。 */
 setupSoundAttributes() {
   const marketBoardDataList = [];
   const soundIdList = Object.getOwnPropertyNames(inago_sound_setting.sound_data);
   this.categories.forEach(cat => {
     marketBoardDataList.push({
       name: cat,
       data: eval(`marketBoardData_${cat}`)
     });
   });
   marketBoardDataList.forEach(mbd => {
     for (const [, value] of Object.entries(mbd.data)) {
       for (const soundId of soundIdList) {
         if (value.alertUpSoundNum === soundId) {
           this.soundAttributes[soundId] = {
             category: mbd.name,
             priceAction: "up"
           };
         } else if (value.alertDownSoundNum === soundId) {
           this.soundAttributes[soundId] = {
             category: mbd.name,
             priceAction: "down"
           };
         }
       }
     }
   });
 }
 createTableId(category) {
   return `alert-history_table_${category}`
 }
 getRightEdgeX() {
   var x = 0;
   this.document.querySelectorAll(".contents > div:not([style*='word-wrap']):not([id='inago_bar_chart_setting'])").forEach(e => {
     let r = e.getBoundingClientRect();
     x = Math.max(r.x + e.offsetWidth, x);
   });
   return x;
 } /* 表示枠を作る */
 addAlertHistoryView() {
   /* 右端に表示するためのX座標の取得。注意書きの領域は無視したいので word-wrap の有無でフィルタリングして X座標を取得している。 */
   var x = this.getRightEdgeX(); /* スタイルの挿入 */
   this.document.querySelector(".contents").insertAdjacentHTML('beforeBegin', `<style type="text/css">        div.alert-history{width:230px;height:144px;position:absolute;left:${x}px;background-color:#e2e2e2; font-size:10px;}        div.alert-history_label{height:14px; background-color: #111111;font-size:10px}        .alert-history_table,.alert-history_table td, .alert-history_table th {border-collapse:collapse;border:#9a9a9a 1px solid;}        .alert-history_table{width:100%;font-size:10px;font-weight: lighter;background-color:#454545; color:#4a4a4a}        .alert-history_table thead th{align:center;background-color:#111111;color:#fafafa}        .alert-history_table tbody tr:nth-child(2n+1) td:nth-child(1){background-color:#f5f5f5}        .alert-history_table tbody tr:nth-child(2n) td:nth-child(1){background-color:#e2e2e2}        .alert-history_table tbody tr:nth-child(2n) td:nth-child(1){background-color:#e2e2e2}        .alert-history_table td.up {background-color: #7bffa8;text-align:center}        .alert-history_table td.down {background-color: #ffa3d3;text-align:center}      </style>      `);
   for (const [i, cat] of this.categories.entries()) {
     this.document.querySelector(".contents").insertAdjacentHTML("beforeEnd", `<div id="${this.createTableId(cat)}" class="alert-history" style="top:${i*144}px;">          <div class="alert-history_label">${cat}</div>          <div class="alert-history_label" style="position:absolute;top:0px;right:0px;cursor: pointer;" onclick="            let last='';            let r=[];            document.querySelectorAll('#${this.createTableId(cat)} tbody tr').forEach(tr => {              let tds = tr.querySelectorAll('td');              let dt = tds[0].innerText;              r.push([dt, tds[1].innerText].join(','));              last= dt;            });            if(r.length !== 0){              let l = document.createElement('a');              l.setAttribute('href','data:Application/octet-stream,' + encodeURIComponent(r.join('\\n')));              l.setAttribute('download', '${cat}.'+last+'.csv');              l.click();            }">Save as CSV          </div>          <div style="height:130px;overflow-y:scroll;">            <table id="${this.createTableId(cat)}" class="alert-history_table" >              <thead><tr background-color:#555><th>DateTime</th><th>Up/Down</th></tr></thead>              <tbody></tbody>            </table>          </div>        </div>`);
   }
 } /* 音声は 既存のオブジェクト 'inago_sound' の関数 'play' で再生される。既存のオブジェクトの関数をオーバーライドする。 */
 inject2InagoSoundPlay() {
   var basePlay = inago_sound.play;
   var self = this;
   inago_sound.play = function (soundId) {
     basePlay.apply(inago_sound, [soundId]);
     let ud = self.soundAttributes[soundId].priceAction;
     let dt = new Date(inago_sound.last_play_time[soundId]);
     let tableId = self.document.querySelector(`#${self.createTableId(self.soundAttributes[soundId].category)} > tbody`).insertAdjacentHTML("afterbegin", `<tr>        <td>${self.dtf.format(dt)}</td>        <td class="${ud}">${ud}</td>      </tr>`);
   };
 }
 getMoneyVolume() {
   this.mvRange.max = 0;
   this.mvRange.min = 0;
   for (const cat of this.categories) {
     const d = eval(`inago_chart_${cat}.bar_data`);
     this.moneyVolumesSum[cat] = 0;
     for (const [ts, vs] of Object.entries(d)) {
       this.moneyVolumesByTS[cat][ts] = 0;
       for (const [k, v] of Object.entries(vs)) {
         this.moneyVolumesByTS[cat][ts] += v[1] - v[0];
       }
       this.moneyVolumesSum[cat] += this.moneyVolumesByTS[cat][ts];
     }
     this.mvRange.max = Math.max(this.moneyVolumesSum[cat], this.mvRange.max);
     this.mvRange.min = Math.min(this.moneyVolumesSum[cat], this.mvRange.min);
   }
 }
 sweepMoneyVolumeData() {
   /* 辞書の肥大化防止 */
   const nowts = Date.now() - (1000 * 200);
   for (const cat of this.categories) {
     for (const [ts, v] of Object.entries(this.moneyVolumesByTS[cat])) {
       let t = Number(ts);
       if (t && t < nowts) {
         delete this.moneyVolumesByTS[cat][ts];
       }
     }
   }
 }
 addMoneyVolumeView() {
   var x = this.getRightEdgeX(); /* スタイルの挿入 */
   this.document.querySelector(".contents").insertAdjacentHTML('beforeBegin', `      <style type="text/css">      .mv-area{width: 230px;height: 144px;position:absolute;left:${x}px;background-color:#e2e2e2; font-size:10px;}      .mv-bar {display: inline-block;margin: 4px;height: 136px;width: 102px;background:lightgray;}      .mv-bar>div{display: flex;align-items: center;}      .mv-bar.buy>div {position:absolute;top:0px;height: 136px;width: 102px;}      .mv-bar.sell>div {position:absolute;top:0px;height: 136px;width: 102px;}      .mv-bar span {position:absolute;top:0px;font-weight: bold;color: #010101;text-align: center;display: block;width: 102px;z-index:2;}      </style>      `);
   for (const [i, cat] of this.categories.entries()) {
     this.document.querySelector(".contents").insertAdjacentHTML("beforeEnd", `        <div class="mv-area ${cat}" style="top:${i*144}px;">          <div class="mv-bar sell">            <span></span>            <div></div>          </div>          <div class="mv-bar buy">            <span></span>            <div></div>          </div>      </div>        `);
   }
 }
 refreshMoneyVolumesView() {
   for (const cat of this.categories) {
     let val = this.moneyVolumesSum[cat];
     let numLabel = this.formatNumber(val);
     let isBuy = val > 0;
     let r = isBuy ? Math.abs(val / this.mvRange.max) : Math.abs(val / this.mvRange.min);
     let p = r * 100;
     let stylep = p.toFixed(2);
     let bl = this.document.querySelector(`.mv-area.${cat} .buy>span`);
     bl.innerText = isBuy ? numLabel : '';
     let b = this.document.querySelector(`.mv-area.${cat} .buy>div`);
     b.style = isBuy ? `background: linear-gradient(to right, #7bffa8, #7bffa8 ${stylep}%, #d3d3d3 ${stylep}%)` : '';
     let sl = this.document.querySelector(`.mv-area.${cat} .sell span`);
     sl.innerText = isBuy ? '' : numLabel;
     let s = this.document.querySelector(`.mv-area.${cat} .sell>div`);
     s.style = isBuy ? '' : `background: linear-gradient(to left, #ffa3d3, #ffa3d3 ${stylep}%, #d3d3d3 ${stylep}%)`;
   }
 }
 formatNumber(n) {
   return Math.abs(n) >= 1.0e+9 ? (Math.abs(n) / 1.0e+9).toFixed(2) + "B" : Math.abs(n) >= 1.0e+6 ? (Math.abs(n) / 1.0e+6).toFixed(2) + "M" : Math.abs(n) >= 1.0e+3 ? (Math.abs(n) / 1.0e+3).toFixed(2) + "K" : (Math.abs(n)).toFixed(2);
 }
}
var ife = new InagoFmacExtension(document);
ife.setupSoundAttributes();
ife.addAlertHistoryView();
ife.addMoneyVolumeView();
ife.inject2InagoSoundPlay();
setInterval(() => {
 ife.getMoneyVolume();
 ife.refreshMoneyVolumesView();
}, 500);
setInterval(() => {
 ife.sweepMoneyVolumeData();
}, 18000);


おわりに

本記事は有料設定にしていますが、すべての内容が無料で閲覧できます。
投げ銭用として設定していますので、評価いただければ幸いです。

ここから先は

0字

¥ 100

この記事が気に入ったらチップで応援してみませんか?