見出し画像

先物いなごフライヤーのアラート履歴を表示するブックマークレットv2 (投資活動日記 2020/08/08)

あいづです。
コロナショックの暴落から株など取引を始めた初心者です。

今回は以前作成したいなごフライヤーさんのFutures Market Action Checker(以下、FMAC)のブックマークレットの機能追加をしたので記事にします。

前回の記事

追加した機能:各商品の「買い」「売り」金額合計表示機

まずはスクショを見てください。

画像1

一番左のピンクと緑のゲージが各商品の「買い」「売り」金額合計です。

これは左の商品の棒グラフのデータを基準とし、以下のロジックで表示しています。

・買いから売りを引いた結果がプラスなら買い、マイナスなら売り

・ゲージの長さは各商品の中で最大(最小)値との比率です。

経緯

エアトレーダーkmgさんの動画でマネーフローの話聞いていて、「時期によってお金の入っている商品が違う」という点を考えた時に、先物いなごフライヤーの中でも商品ごとのお金の流入が可視化できるといいかなと思ったので作ってみました。

個人的にはすべてをデータで見たいんですけど、網羅的に把握してやるのはなかなかに難しいので、まずはここから!


導入の仕方

本記事のソースコードを用いて前回の記事を参照してください。
注意事項なども前回の記事と同様です。

ソースコード

javascript:class InagoFmacExtension {
 constructor(document) {
   this.document = document;
   this.soundAttributes = {};
   this.categories = ["bond", "fx", "stockindex", "usstock", "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}`
 }
 /* 表示枠を作る */
 addAlertHistoryView() {
   /* 右端に表示するためのX座標の取得。注意書きの領域は無視したいので word-wrap の有無でフィルタリングして X座標を取得している。 */
   var x = 0;
   this.document.querySelectorAll(".contents > div:not([style*='word-wrap'])").forEach(e => {
     let r = e.getBoundingClientRect();
     x = Math.max(r.x + e.offsetWidth, x);
   });
   /* スタイルの挿入 */
   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 = 0;
   this.document.querySelectorAll(".contents > div:not([style*='word-wrap'])").forEach(e => {
     let r = e.getBoundingClientRect();
     x = Math.max(r.x + e.offsetWidth, x);
   });
   /* スタイルの挿入 */
   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);


ご感想お待ちしてます!

金融リテラシーもない完全な素人なので分からない事だらけです。こういったブックマークレットが良いのかどうかも分からないのでご感想お待ちしています!活用方法とかいなごフライヤー自体をどのように活用しているかなども教えて頂ければうれしいです。


おわり

この記事が気に入ったらサポートをしてみませんか?