「投資のモヤモヤ解消!」株、FX、仮想通貨、何でもありのバックテスト

「投資のモヤモヤ解消!」株、FX、仮想通貨、何でもありのバックテスト

あなた投資は、手探りではありませんか?

あなたの手法には、確固たる根拠がありますか?

あなたの検証は、チャートをさかのぼるだけではありませんか?

10銘柄以上、何十年にもおよぶ、統計的といえる検証ができていますか?

リスク管理、資金管理、分散投資が運用成績にどのような影響をあたえるのかイメージできますか?

もし、上記のひとつでも自信がもてないとしたら、この記事でご紹介するバックテストが役に立つかもしれません。

同じ疑問を、過去、ボク自身も抱えていました。

そして、バックテストをたくさん行うことでひとつひとつ解消してきたのです。

この記事では、執筆時点でのボクのバックテストを余すことなくお伝えしたいと思います!

考え中

「投資のモヤモヤ解消!」株、FX、仮想通貨、何でもありのバックテスト

※ この記事で公開しているコードは、実際にボク自身が使用しているものですが、その動作を保証するものではありません。自己責任でご使用くださるよう、お願いします。

※ 2018年2月時点で実際に使用しているものを公開していますが、時間が経過するにしたがって内容が劣化していく可能性が高いです。あらかじめご了承ください。

  1. バックテストとは
    1. 株、FX、商品(コモディティ)、仮想通貨、
      資金管理にリスク管理、分散投資まで。
      なんでもありのバックテスト!
    2. バックテストでここまでわかる!
    3. バックテストをするとどんな良いことがあるのか
    4. バックテストは良いが、盲信してはいけない
    5. お金をかけずにバックテストできる?
  2. おれはこうやる!How to バックテスト
    1. バックテストの流れ
    2. 使うツールは?
  3. Investment Tech Hack 式 バックテスト 2018年版
    1. ツールの準備
    2. 価格データを取得する
    3. 相関係数を算出する
    4. テクニカル分析を計算する
    5. 市場間の休日のずれを埋める
    6. いよいよバックテスト!!
      (シグナルや資金管理、リスク管理の設定)
    7. データを分析する
    8. 視覚化して分析する
  4. まとめ
  5. 「バックテストのファイル一式」プレゼント!

1. バックテストとは

バックテスト
バックテストとは、過去のデータを入力し、そのデータに基づいて仮説およびシナリオが正しいかどうかを検証してみる手法のこと。金融機関においては、市場リスク計測の有効性を確認するため、バックテストが定期的に行われている。
引用元みずほ総合研究所(株)

バックテストで手法の有効性が確認できれば、「大数の法則」にもとづいて、未来においてもその結果を再現できる可能性がでてきます。(「大数の法則」についてはこちらの記事をご覧ください)

これから、この記事の中で徹底的に解説しますが、バックテストは投資の手法を決める上でもっとも重要で、ひじょ~~~に参考になります。バックテストをすることで、自信をもってトレードに挑むことができるのです。

目次へ

1-1. 株、FX、商品(コモディティ)、仮想通貨、資金管理にリスク管理、分散投資まで。なんでもありのバックテスト!

ボクが行っている検証には以下のような特徴があります。

価格データさえあれば銘柄は自由!
株式、FX、商品(コモディティ)、仮想通貨、CFD、スワップポイント、スプレッド(価格データ由来か固定)、手数料(定額、定率)、海外市場、なんでもあり!
複数銘柄の分散投資が可能!
銘柄数に上限なし!(計算に時間がかかるだけ)
価格データさえあれば何年分でも検証できる
同じ手法を複数銘柄で分散投資したテストをすることができる!
相関係数や価格変動の勢いをもとにシグナルのスクリーニングを行う!
資金管理やリスク管理もフレキシブルに変更可能!
これらが成績にあたえる影響もこまかく分析することができる!
手数料や税金を含めた資産の推移もテストできる!
(損失の繰越は未対応。開発すれば可能)
数値化さえできれば、どんなテクニカル分析でも可能!
ライントレードみたいなのは苦手
結果のグラフ化や分析も自由自在!

世に出回るほとんどの検証ソフトは、複数銘柄を同時に走らせるような分散投資のテストをすることができません(知らないだけかも)。少なくとも、上の項目すべてを網羅するような検証ソフトは存在しないはずです。

バックテストをするのなら、できる限り実際のトレードに近い形で行ったのほうが良いのは当然です。なので、ボクのかゆいところに手が届くような検証のプログラムを自作したわけです!

この記事では、自作したボクのバックテストのすべてを解説します!

目次へ

1-2. バックテストでここまでわかる!

バックテスト結果の一例をご紹介します。
この記事でご紹介するバックテストでは、こんなことがわかってしまいます!

あるバックテストの売買履歴をチャートで確認

売買をチャートで確認する

保有ポジションの値洗い(未決済の含み損益)の推移とともに確認できます。

銘柄間の相関係数の推移

銘柄間の相関係数の推移

相関係数は「似た動きをするかどうか」ってやつですね!「1」がまったく同じ動きで「-1」が真逆の動きを表しています。(ちょっとカッコつけて3Dのグラフにしていますが、本当はヒートマップで十分・・笑)

あるバックテストの資金関連のグラフ

資産の推移 年ごとの損益の推移 ドローダウンの推移 ポジション保有リスクの推移

あるバックテストにおける対リスク比の損益の分布

対リスク比の損益の分布

バックテストで分析できる項目(一例)

資産関連
決済済み現金資産
損益(円、%)、最大ドローダウン(円、%)、CAGR(平均年利)、MAR比
未決済を含む純資産
損益(円、%)、最大ドローダウン(円、%)、CAGR(平均年利)、MAR比
日毎の分析
取引日数、最大損失/日(円)、最大利益/日(円)、勝率/日(%)、リスクリワード比率/日(倍)、期待値/日(リスク比)
売買成績
年ごと
粗利益/損失(円)、純利益/損失(円)、合計手数料(円)、勝率(%、金額、リスク比)、リスクリワード比率(倍、金額、リスク比)、期待値(リスク比)、破産の確率(%)
銘柄ごと
粗利益/損失(円)、純利益/損失(円)、合計手数料(円)、勝率(%、金額、リスク比)、リスクリワード比率(倍、金額、リスク比)、期待値(リスク比)、破産の確率(%)
タートルズのユニット
ピラミッティングの段階別トレード回数
その他
トレード期間の長さによる分布、リスク比の損益による分布

かなり細かく分析、比較できます!
自分の中での検証項目が増えれば増えるほど、その検証は堅固なものになります。

目次へ

1-3. バックテストをするとどんな良いことがあるのか

さてさて!
色々分析できるのは良いですが、バックテストするとどんな良いことがあるのでしょうか。
ちょっと考えてみたいと思います!

バックテストすることで得られる7つのメリット

  1. トレードを疑似体験することができる
  2. 通常のリアルチャートでは得られないほど膨大な経験値を得ることができる
  3. テクニカル指標や資金管理、リスク管理の理解が深まる
  4. 現実の取引では試せないような無茶なトレードをテストすることができる
  5. 仮想通貨のような新たな市場に参加する場合に、さまざまな傾向をつかんだ上で挑戦できる
  6. 長期的に勝てる見込みのない手法で現実のトレードをすることがほぼなくなる
  7. 手法の盲点を減らせる可能性がある

あえて7つという制限を設けて、ざっと書き出してみました。あっと言う間にリストアップできちゃいますね。もっともっとある気がします!

目次へ

1-4. バックテストは良いが、盲信してはいけない

もちろん、デメリットもあります。

とくに注意しなくてはならないのは、「バックテストの結果を盲信してしまうこと」です。これは、一時期、ボク自身もおちいってしまったことです。

バックテストのデメリット

  1. エラーがあるのに、顕在化せず、もっともらしい結果を算出するケースがある
    (これが一番危険)
  2. リアルマネーをつかったトレードに比べると、経験の質が落ちる
  3. なんだかんだ言って、人間の脳みそが一番優秀
    (勉強した上での「人間の感覚や勘」に勝るものではない)
  4. ライントレードのような、複合的に判断するトレードルールの検証が難しい
  5. 短期間の検証、検証回数が少ない、最適化しすぎる等、おちいりやすいトラップがある
  6. 統計的に裏付けがあるからといっても絶対ではない
  7. つぶさに検証結果を確認するのが、ちょっと、めんどくさい。。

最低限、ここにあげたデメリットに注意しながら検証していかないと、最悪、 せっかくバックテストしたの大損してしまうなんてことも十分ありえます。要注意です!

目次へ

1-5. お金をかけずにバックテストできる?

つらつらと「バックテストとはこういうものだよ」とご紹介してきましたが、
きっと、こう考える人が多いのではないでしょうか。

どうせ、有料のツールを使うんでしょ

思いますよね。

わかります。

ところがどっこい!
これからご紹介する検証は、一切お金をかけずに行うことができます!

もちろん、有料にしたほうが便利になることはたくさんありますが、無料でも、こと検証結果において「劣る」ということはありません。

ツールについては「2-2. 使うツールは?」でリストアップしていますので、ご覧ください。

目次へ

2. おれはこうやる!How to バックテスト

バックテストの全体像をご紹介していきます。

2-1. バックテストの流れ

まずは、全体の流れです。

  1. 価格データの取得
  2. データの整形
    • 基本の形を整える
    • テクニカル分析や相関係数を算出する
    • 市場間の日付の差を埋める
  3. シグナルの設定
  4. 資金管理やリスク管理の設定
  5. バックテストを実行!
  6. 結果を精査
    • 資金の推移
    • 売買成績
    • 銘柄ごとのエントリーとピラミッティング、イグジットのタイミング
    • それぞれを、表計算ソフトで統計データを算出したり、グラフによる視覚化で確認したりする
  7. バックテストをひたすら繰り返す

そんなに複雑なことはしていないですよね。プログラムさえできていれば、意外とやることはシンプルです。

目次へ

2-2. 使うツールは?

次に、検証に使用するツールです。

  1. google apps script
    Googleが提供するJavaScriptベースの開発環境。無料で手軽にプログラミングができる
  2. google fusion tables
    Googleドライブの簡易データベース。価格データや計算後のテクニカル分析などを入れる
  3. google スプレッドシート
    Googleドライブの表計算Webアプリ。売買履歴や資金の推移等、レポート出力用
  4. Python
    データ分析方面で、もっとも勢いがあるプログラミング言語
  5. cloud9
    クラウド上で、Pythonをうごかすことができる

※ cloud9は、amazonに買収され、ちょっと仕様が変わってしまいました。この記事では主にグラフの作成に使用するだけなので、サイトの紹介を控えます(というか、紹介しているサイトがない)。

これ以外のツールは、一切使いません!

この中で、cloud9だけは有料で使用していますが、「Pythonを使いたい」というだけなら、無料で十分です。検証結果の確認をスムーズに行うためのものなので、なんだったら使わなくても良いくらいです。

目次へ

3. Investment Tech Hack 式 バックテスト 2018年版

good luck

さて、いよいよ、Investment Tech Hack 式 バックテストの全貌を公開します。すべて、2018年2月現在、私が行っているそのままのものです。

正直、この章はかなりややこしくてとっつきにくいです。
「プログラミングとか、考えただけでイヤになる」みたいな人は、どうぞ飛ばしてください(目次へ)。

3-1. ツールの準備

まずツールを準備する必要があります。細かな設定方法については、他のページで詳しく丁寧に解説してくれているページがたくさんありますので、それぞれの解説ページへのリンクを置く形にします。

3-1-1. 必要なツールを準備

  1. Googleまわり
    1. googleのアカウントを作成
    2. googleドライブに「fusion tables」を追加
    3. googleドライブに「Google apps script」を追加
  2. cloud9の登録~Pythonのインストール
  3. PythonにJyupiterとPlotlyをインストール

※ cloud9は、amazonに買収され、ちょっと仕様が変わってしまいました。この記事では主にグラフの作成に使用するだけなので、サイトの紹介を控えます(というか、紹介しているサイトがない)。

Pythonを使用したグラフの作成については、コードを公開しておきますので、ご興味があればウェブで調べながらチャレンジしてみてください。
cloud9以外にもクラウドIDEはいくつかありますし、WindowsにPythonをインストールして使用することもできます(もちろんMACも)。

目次へ

3-1-2. 必要なファイルの準備

検証を構成するファイルは以下の通りです。
以降、「google apps script = GAS、googleスプレッドシート = SS、Fusion Tables = FT 」と表記します。

  1. (GAS)[BackTest] Main
  2. (FT)[BackTest] Prices
  3. (FT)[BackTest] Correl 2000-2017 (year)
  4. (FT)[BackTest] Prices ,Techs 2000-2017
  5. (SS)[作成用] 1.Prices
  6. (SS)[作成用] 1.Prices for Calc
  7. (SS)[作成用] 2.Correl
  8. (SS)[作成用] 3.Prices ,Techs ,Sigs 1(~5)
  9. (SS)[作成用] 3.Prices ,Techs ,Sigs ,items
  10. (SS)[作成用] 4.fund
  11. (SS)[作成用] 4.record
  12. (SS)[作成用] 4.unit 1(~4)

※ ファイル名が変わっても動きます。わかりやすい名前で作成してください。

とりあえず、まずはこれらのファイルを作成します。それぞれのファイルにID(プロダクトキー)が振られているので、それを、GASの基本設定のコーディングに使用します。

backtest files バックテストに必要なファイル

目次へ

3-1-3. [BackTest] Main の基本設定

コーディングの前に、必要な機能を追加したり、ライブラリを読み込んでおきます。

  1. GAS「[BackTest] Main」に「fusion tables」の機能を追加
  2. 以下のライブラリを読み込む
    • GAS Library - MyUtilities
      ID:1Zt_rrHczM9uiCv70zghjgQ7T7-Ynypwc7ByOGM_GqDhRkdwOUIfFVmJe
      識別子:GU
    • GAS Library - Calclation
      ID:1rMBz0ZYK00AcTNfXw1qQfzIym5CPvCwvm0oHry3P3bBqHwbHUQQqymMw
      識別子:CALC
    • GAS Library - Fusion Tables Utillities
      ID:1o0gsDlcflREy5v1cNvszSO8iISz5G-YQ4XRv_KbrIEeU6LDd9dkQawWF
      識別子:FTU
    • GAS Library - Moment
      ID:1BgIY53F72m6dmdC7UGuCdVD02DHXknG62WPNEkdoiS0nJl1yqDBjdTAA
      識別子:MO

目次へ

3-1-4. [BackTest] Main の構成

GAS「[BackTest] Main」は、以下のスクリプトファイルで構成されています(「ファイル / 新規作成 / スクリプトファイル」で作成できる)。

  • 0. Settings.gs
  • 0. Trigger Utillities.gs
  • 1-1. 価格データの取得.gs
  • 1-2. Correl.gs
  • 1-3. Techs (MA,MACD,BB,Stage)20000セル対策最新.gs
  • 1-4. fusion tablesに基軸通貨を加える.gs
  • 1-5. Date Operation.gs
  • 2. Back Test.gs
  • 3. 作成用SSとプロパティの初期化.gs
  • 3. 作成用SSをCSVに変換.gs
  • for test

目次へ

3-1-5. [BackTest] Main の基本設定(コーディング編)

「3-1-4」で作成したスクリプトファイルに、以下を貼り付けて保存します。

「0. Settings.gs」のプロダクトキーは、「3-1-2」で作成したファイルのものを適宜入力していきます。

0. Settings.gs

/**
 * 全体の設定
 */
(function(global){
  var GV = {
    
    /**
     * 価格データのFT
     */
    FT:{
      // Webから取ってきた段階の生データ
      PRICE  :{ID:"---- Fusion Tables の ID(プロダクトキー)----" //1999-2017
               ,ITM : ["Date","SymNum","Symbol","Open","High","Low","Close","Volume"/*,"OpenInterest"*/]}
      // テクニカル分析算出後のデータ
      ,Techs :{ID:"---- Fusion Tables の ID(プロダクトキー)----"} // 16symbols 2000-2017
      // 相関係数のデータ
      ,Correl:{ID:"---- Fusion Tables の ID(プロダクトキー)----"} // year 2000-2017
    }
    
    /**
     * 作業用 SS
     */
    ,SS:{
      Prices  :{    ID:"---- スプレッドシート の ID(プロダクトキー)----" ,SHEET:"シート1"}
      ,forCalc:{    ID:"---- スプレッドシート の ID(プロダクトキー)----" ,SHEET:"シート1"}
      ,Techs  :{ 0:{ID:"---- スプレッドシート の ID(プロダクトキー)----" ,SHEET:"シート1"}
                ,1:{ID:"---- スプレッドシート の ID(プロダクトキー)----" ,SHEET:"シート1"}
                ,2:{ID:"---- スプレッドシート の ID(プロダクトキー)----" ,SHEET:"シート1"}
                ,3:{ID:"---- スプレッドシート の ID(プロダクトキー)----" ,SHEET:"シート1"}
                ,4:{ID:"---- スプレッドシート の ID(プロダクトキー)----" ,SHEET:"シート1"}}
      ,Correl :{    ID:"---- スプレッドシート の ID(プロダクトキー)----" ,SHEET:"シート1"}
      
      // 以下は「3-2」の関数でまとめて初期化することができる。
      // Unitsは1シート5年分が限界(上限2000000セル/1シート)
      /////////////////////////////////////////////////////////////////////////////////
      ,Units  :{ 0:{ID:"---- スプレッドシート の ID(プロダクトキー)----" ,SHEET:"シート1"} 
                ,1:{ID:"---- スプレッドシート の ID(プロダクトキー)----" ,SHEET:"シート1"}
                ,2:{ID:"---- スプレッドシート の ID(プロダクトキー)----" ,SHEET:"シート1"}
                ,3:{ID:"---- スプレッドシート の ID(プロダクトキー)----" ,SHEET:"シート1"}}
      ,Records :{   ID:"---- スプレッドシート の ID(プロダクトキー)----" ,SHEET:"シート1"}
      ,Fund    :{   ID:"---- スプレッドシート の ID(プロダクトキー)----" ,SHEET:"シート1"}
      /////////////////////////////////////////////////////////////////////////////////
    }
    
    /**
     * 銘柄の設定
     * ―― シンボルコード、小数点、手数料、基軸通貨
     * ―― C=code,R=round(小数の桁数),L=lot
     * ―― costは現地通貨
     */
    ,SYM:{
      //--株価指数--
      Nikkei225  :{C:11 ,R:2 ,L:1    ,cost:8        ,currency:""            ,stooq:"%5Enkx"}
      ,DJI       :{C:12 ,R:2 ,L:1    ,cost:2.5      ,currency:"USDJPY"      ,stooq:"%5Edji"}
      ,DAX       :{C:13 ,R:3 ,L:1    ,cost:1.5      ,currency:"EURJPY"      ,stooq:"%5Edax"} 
      ,FTSE      :{C:14 ,R:3 ,L:1    ,cost:4        ,currency:"EURJPY"      ,stooq:"x.f"}
      ,HSI       :{C:15 ,R:2 ,L:1    ,cost:13       ,currency:"HKDJPY"      ,stooq:"%5Ehsi"} 
      //--為替--
      ,USDJPY    :{C:21 ,R:4 ,L:5000 ,cost:30       ,currency:""            ,stooq:"usdjpy"}
      ,EURUSD    :{C:22 ,R:6 ,L:5000 ,cost:0.5      ,currency:"USDJPY"      ,stooq:"eurusd"}
      ,GBPUSD    :{C:23 ,R:6 ,L:5000 ,cost:0.5      ,currency:"USDJPY"      ,stooq:"gbpusd"}
      ,AUDUSD    :{C:24 ,R:6 ,L:5000 ,cost:0.5      ,currency:"USDJPY"      ,stooq:"audusd"}
      ,CHFUSD    :{C:25 ,R:6 ,L:5000 ,cost:0.5      ,currency:"USDJPY"      ,stooq:"chfusd"}
      ,CADUSD    :{C:26 ,R:6 ,L:5000 ,cost:0.5      ,currency:"USDJPY"      ,stooq:"cadusd"}
      //−−海外商品−− 
      ,Gold      :{C:31 ,R:3 ,L:1    ,cost:0.5      ,currency:"USDJPY"      ,stooq:"gc.f"}
      ,Platinum  :{C:32 ,R:3 ,L:1    ,cost:2        ,currency:"USDJPY"      ,stooq:"pl.f"}
      ,Palladium :{C:33 ,R:3 ,L:1    ,cost:2        ,currency:"USDJPY"      ,stooq:"pa.f"}
      ,CrudeOil  :{C:34 ,R:3 ,L:25   ,cost:0.5*25   ,currency:"USDJPY"      ,stooq:"cl.f"}
      ,Corn      :{C:35 ,R:3 ,L:200  ,cost:1.25*200 ,currency:"USDJPY_cent" ,stooq:"zc.f"}
    }
    
    /**
     * 計算に必要なデータ
     * ―― 基軸通貨
     */
    ,forCalc:["HKDJPY" ,"EURJPY"]
    
    /**
     * 再起動処理
     */
    ,EXECUTE_COUNT : parseInt(PropertiesService.getScriptProperties().getProperty("EXECUTE_COUNT") || 0)
    ,ROOP_COUNT    : (function(){var a=PropertiesService.getScriptProperties().getProperty("ROOP_COUNT"); return a>=0 ? parseInt(a || 0) : a;})()
    ,ERROR_COUNT   : 0
    
    /**
     * メール関連
     */
    ,MAIL_TO  : "---- 作業通知用メールアドレス ----" //複数ある場合は配列に
    ,MAIL_CC  : ("") // {@string} 複数の場合は「,」で区切る
    ,MAIL_BCC : ("")
    ,MAIL : function(title ,text){
      if(this.MAIL_TO){
        if(!this.MAIL_CC && !this.MAIL_BCC){
          MailApp.sendEmail(this.MAIL_TO ,"GAS|"+title ,text);
        }else{
          var option = {};
          if(this.MAIL_CC)option.cc = this.MAIL_CC;
          if(this.MAIL_BCC)option.bcc = this.MAIL_BCC;
          MailApp.sendEmail(this.MAIL_TO ,"GAS|"+title ,text ,option);
        }
      }
    }
  };
  global.GV = GV;
})(this);

目次へ

GASには、起動時間の上限が6分と決められています。

しかし、検証をしていくと、6分ではまったく足りないというケースがよくあります。そんなときにGASを再起動するためにあるのが「Trigger Utillities」です(詳しくはこちらのサイトをごらんください)。

0. Trigger Utillities.gs

/**
 * Trigger Utillites
 */
(function(global){
  var SP = PropertiesService.getScriptProperties()
     ,SA = ScriptApp
     ,TriggerUtil = {
    
       DEFAULT_TRIGGER_KEY       : "TRIGGER_ID"
       ,DEFAULT_INTERVAL_MINUTES : 1
       ,DEFAULT_TIMEOUT_MENUTES  : 5
       
       /**
        * タイムアウトに近いか?
        * @param {Moment} startMoment 処理開始時の時間(Momentオブジェクト)
        */
       ,isNearlyTimeOut : function(startMoment ,option) {
         var 
          timeScale = (option.SCALE || "minutes")
         ,timeout = (option.NUM || this.DEFAULT_TIMEOUT_MENUTES)
         ;
         return MO.moment().diff(startMoment , timeScale) >= timeout;
       }
       
       /**
        * 渡されたTriggerKeyで保存されているTriggerを削除
        * @param {string} triggerKey (非必須)ScriptPropertiesに保存されているTriggerIdのKey名  渡されない場合はDEFAULT_TRIGGER_KEYの値になる。
        */
       ,deleteTrigger : function(triggerKey) {
         triggerKey = triggerKey || this.DEFAULT_TRIGGER_KEY;
         var triggerId = SP.getProperty(triggerKey);
         if(!triggerId) {
           return;
         }
         SA.getProjectTriggers().filter(function(trigger){
           //var trigger = SA.getScriptTriggers()[0];
           return trigger.getUniqueId() == triggerId;
         })
         .forEach(function(trigger) {
           SA.deleteTrigger(trigger);
         });
         SP.deleteProperty(triggerKey);
         Logger.log("Delete property.["+triggerKey+"]");
       }
       
       /**
        * 渡されたfunctionNameを再起動
        * @param {object} option (オプション) triggerKeyとintervalMinutesを設定する。
        */
       ,resumeTrigger : function(functionName , option) {
         if(!functionName) {
           throw new Error("Given functionName");
         }
         var options = (option || {})
            ,triggerKey = (options.triggerKey || this.DEFAULT_TRIGGER_KEY)
            ,intervalMinutes = (options.intervalMinutes || this.DEFAULT_INTERVAL_MINUTES)
            ,triggerId = SA.newTrigger(functionName).timeBased().at(
              MO.moment().add("minutes" ,intervalMinutes ).toDate()
            ).create().getUniqueId();
         SP.setProperty(triggerKey, triggerId);
         Logger.log("Make trriger and Set property.\n["+triggerKey+":"+triggerId+"]");
       }

     };
  global.SP = SP;
  global.TU = TriggerUtil;
})(this);

目次へ

3-2. 価格データを取得する

Stooq」というサイトから、価格データを取得するコードです。「0. Settings.gs」で指定した銘柄の価格データをとってきます。期間は「1-1. 価格データの取得.gs」の中で指定することができます。

手順

  1. 銘柄と期間を指定
  2. 「Get_Prices()」「Get_PricesForCalc()」の関数を走らせる(結果はSS「[作成用] 1.Prices」「[作成用] 1.Prices for Calc」に)
  3. それぞれ、SSに出力されたものを、FT「[BackTest] Prices」に登録する(SSからCSVで書き出し、FTに読み込む)

1-1. 価格データの取得.gs

function Get_Prices(){
  var
   start = "19990101" // yyyyMMdd
  ,end   = "20171231"
  ,sh    = SpreadsheetApp.openById(GV.SS.Prices.ID).getSheetByName(GV.SS.Prices.SHEET)
  ;
  
  sh.clear();
  sh.getRange(1 ,1 ,1 ,7).setValues([["Date" ,"SymNum" ,"Symbol" ,"Open" ,"High" ,"Low" ,"Close"]]);
  
  for(var sym in GV.SYM){
    if(GV.SYM[sym].stooq!=null){
      GV.SYM[sym].URL = "https://stooq.com/q/d/l/?s="+GV.SYM[sym].stooq+"&d1="+start+"&d2="+end+"&i=d";
      GV.SYM[sym].csv = GU._CSVToArray(
        GU._getCsv(
          GV.SYM[sym].URL
        ).contentString 
        ,","
      );
      
      var lenCol = GV.SYM[sym].csv[0].length;
      GV.SYM[sym].csv.shift();
      
      for(var i=0 ,lenRow=GV.SYM[sym].csv.length; i<lenRow; i++){
        GV.SYM[sym].csv[i].splice(1 ,0 ,GV.SYM[sym].C ,sym);
        
        var lenCol_i = GV.SYM[sym].csv[i].length;
        if(lenCol_i <lenCol+2){
          for(var j=0 ,j_len=lenCol+2-lenCol_i; j<j_len; j++){
            GV.SYM[sym].csv[i].push("");
          }
        }
      }
      
      var lastRow = sh.getLastRow();
      sh.getRange(lastRow+1 ,1 ,lenRow ,lenCol+2).setValues(GV.SYM[sym].csv);
    }
  }
}


function Get_PricesForCalc(){
  var
   start = "19990101" // yyyyMMdd
  ,end   = "20171231"
  ,sh    = SpreadsheetApp.openById(GV.SS.forCalc.ID).getSheetByName(GV.SS.forCalc.SHEET)
  ;
  
  sh.clear();
  sh.getRange(1 ,1 ,1 ,7).setValues([["Date" ,"SymNum" ,"Symbol" ,"Open" ,"High" ,"Low" ,"Close"]]);
  
  for(var i=0 ,len_i=GV.forCalc.length; i<len_i; i++){
    var URL = "https://stooq.com/q/d/l/?s="+GV.forCalc[i]+"&d1="+start+"&d2="+end+"&i=d";
    var csv = GU._CSVToArray(
      GU._getCsv(URL).contentString 
      ,","
    );
    
    var lenCol = csv[0].length;
    csv.shift();
    
    for(var j=0 ,lenRow=csv.length; j<lenRow; j++){
      csv[j].splice(1 ,0 ,"" ,GV.forCalc[i]);
      
      var lenCol_j = csv[j].length;
      if(lenCol_j <lenCol+2){
        for(var k=0 ,len_k=lenCol+2-lenCol_j; k<len_k; k++){
          csv[j].push("");
        }
      }
    }
    
    var lastRow = sh.getLastRow();
    sh.getRange(lastRow+1 ,1 ,lenRow ,lenCol+2).setValues(csv);
  }
}

目次へ

3-3. 相関係数を算出する

作成したFT「[BackTest] Prices」の価格データをもとに、指定した期間の週ごとの相関係数を算出します。

手順

  1. 銘柄と期間を指定
  2. 「Correl()」の関数を走らせる(結果はSS「[作成用] 2.Correl」に)
  3. それぞれ、SSに出力されたものを、FT「[BackTest] Correl」に登録する(SSでCSVに書き出し、FTで読み込む)

このコードはもっと効率の良い書き方がある気がする。。

1-2. Correl.gs

/**
 * 相関関係を算出してSSへ
 */
function Correl(){
  var functionStart = MO.moment();
  Logger.log("Start script.["+arguments.callee.name+"]\n[EXECUTE_COUNT: "+GV.EXECUTE_COUNT+"]\n[ROOP_COUNT: "+GV.ROOP_COUNT+"]\n\n");
  
  var triggerKey = ("TRIGGER_ID_"+arguments.callee.name);
  if(GV.ROOP_COUNT){
    TU.deleteTrigger(triggerKey); // 通常の処理のトリガーを削除 
    Logger.log("Deleted used trriger.\n");
  }
  if(GV.EXECUTE_COUNT){
    TU.deleteTrigger(); // エラー処理用のトリガーを削除
    Logger.log("Deleted trriger of error handling.\n");
  }
  
  A:while(true){
    try {
      
      /**
       * setting
       */
      var 
       ST = {
         SH: SpreadsheetApp.openById(GV.SS.Correl.ID).getSheetByName(GV.SS.Correl.SHEET)
         
         /**
          * 相関の計算期間を指定(絶対数)
          * Date: "YYYY.MM.DD"
          */
         ,START  : "1999.01.01"
         ,END    : "2017.12.31"
         ,CRRL_N : 30 //365 183 90 30
         
         //再起動の間隔(分)
         ,RUN_SYCLE: 10
      };
      
      var 
       start  = MO.moment(ST.START ,"YYYY.MM.DD").day(1).subtract(1 ,"week")
      ,end    = MO.moment(ST.END   ,"YYYY.MM.DD")
      ,days   = end.diff(start ,"days")
      ,weeks  = Math.floor(days /7)
      ,wStart = (Math.ceil(ST.CRRL_N /7) +1)
      ,crrlN  = Math.floor(ST.CRRL_N /7 *5 -15) //「-15」は祝日の調整分
      ,symLen = Object.keys(GV.SYM).length
      ,ar     = new Object()
      ,crrl   = new Array()
      ,w ,wi 
      ,wd ,wd_ 
      ,i ,i_ 
      ,j ,k 
      ,date ,dateLen 
      ,a ,a_ 
      ,b ,b_ 
      ,preAr ,arA ,arB 
      ,log
      ;
      
      /**
       * データの取得と成形
       */
      preAr = FTU._getRows({
        ID        : GV.FT.PRICE.ID  
        ,SELECT   : "Date,Close,Symbol"  
        ,WHERE    : "Date>\'"+ST.START+"\'"  
        ,ORDER_BY : "Date ASC"
      }).RESULT.rows;
      for(i in GV.SYM) ar[i] = preAr.filter(function(pre){return pre.indexOf(i)>=0;});
      
      /**
       * 相関の計算
       */
      wd = start.add(wStart+GV.ROOP_COUNT ,"weeks");
      B:for(w=GV.ROOP_COUNT; w<weeks-wStart; w++){ // 週ごとのループ
        
        if(w>0) wd = start.add(1 ,"weeks");
        wd_ = wd.clone().toDate();
        date = [
          wd.clone().format("YYYY/MM/DD")
          ,wd.clone().format("YYYY/M/D")
          ,wd.clone().format("YYYY-MM-DD")
          ,wd.clone().format("YYYY-M-D")
        ];
        for(k=0; k<7; k++){
          date.push(
            wd.clone().subtract(k+1 ,"days").format("YYYY/MM/DD")
            ,wd.clone().subtract(k+1 ,"days").format("YYYY/M/D")
            ,wd.clone().subtract(k+1 ,"days").format("YYYY-MM-DD")
            ,wd.clone().subtract(k+1 ,"days").format("YYYY-M-D")
          );
        }
        dateLen = date.length;
        
        // 銘柄Aのループ
        i_ = 0;
        for(i in GV.SYM){
          wi       = w *symLen +i_;
          crrl[wi] = [wd_ ,GV.SYM[i].C ,i]; // ヘッダー [日付 ,銘柄コード ,銘柄名]
          log      = wd_+"\n[wi:"+wi+"]\nA:"+GV.SYM[i].C+"|"+wd.format("YYYY/MM/DD")+"\n";

          // 銘柄Aの該当の日付が配列の何番目か検索
          C:for(a_=(ar[i].length-1<ar[i].length/10+w*5 ? ar[i].length-1 : Math.floor(ar[i].length/10+w*5)); a_>0; a_--){for(k=0; k<dateLen; k++) if(ar[i][a_][0]===date[k]) break C;}
          
          // 配列Aを作成
          arA = new Array(); 
          for(a=a_; a>a_-crrlN; a--) arA.unshift(ar[i][a][1]);
          
          // 銘柄Bのループ
          for(j in GV.SYM){ 
            
            // 銘柄Bの該当の日付が配列の何番目か検索
            D:for(b_=(ar[j].length-1<ar[j].length/10+w*5 ? ar[j].length-1 : Math.floor(ar[j].length/10+w*5)); b_>0; b_--){for(k=0; k<dateLen; k++) if(ar[j][b_][0]===date[k]) break D;}
            
            // 配列Bを作成
            arB = new Array();
            for(b=b_; b>b_-crrlN; b--) arB.unshift(ar[j][b][1]);
            
            // 配列Aと配列Bで相関を計算して挿入
            crrl[wi].push(CALC.roundN(CALC.Correl(arA ,arB) ,4)); 
            log += "A:"+GV.SYM[i].C+"-B:"+GV.SYM[j].C+"|"+ar[j][b_][0]+"\n";
          }

          Logger.log(log+crrl[wi]+"\n");
          if(TU.isNearlyTimeOut(functionStart ,{SCALE:"seconds" ,NUM:320})) break B;
          
          i_++;
        }
      }

      if(GV.ROOP_COUNT==0){
        var items = [["Date","SymNum","Symbol"]];
        for(i in GV.SYM){items[0].push(i);}
        ST.SH.getRange(1 ,1 ,1 ,crrl[wi-1].length).setValues(items);
      }
      ST.SH.getRange(2+GV.ROOP_COUNT*symLen ,1 ,crrl.length-GV.ROOP_COUNT*symLen ,crrl[wi-1].length).setValues(crrl.slice(GV.ROOP_COUNT*symLen));
      break A;
      
    /**
     * エラー処理
     */
    }catch(e){
      GV.ERROR_COUNT++;
      Logger.log(e+"\n[ERROR_COUNT: "+GV.ERROR_COUNT+"]\n");
      
      if(GV.ERROR_COUNT>2){
        GV.EXECUTE_COUNT++;
        
        if(GV.EXECUTE_COUNT<4){
          SP.setProperties({EXECUTE_COUNT:GV.EXECUTE_COUNT});
          Logger.log("Set property.\n  [EXECUTE_COUNT: "+GV.EXECUTE_COUNT+"]\n");
          
          TU.resumeTrigger(arguments.callee.name ,{intervalMinutes:2});
          Logger.warning("なんかよく失敗しているのでちょっと経ってから再起動します。");
          
          GV.MAIL("Ran script: "+arguments.callee.name+" 処理のエラー回数が閾値を超えたので再起動します" ,"処理のエラー回数が閾値を超えたので設定時間が経過した後に再起動します。\n\n----------\n"+Logger.getLog());
          return;

        }else{
          Logger.warning("再起動してもダメなようです。確認してみてください。");
          GV.MAIL("Ran script: "+arguments.callee.name+" 処理の再起動回数が閾値を超えたので終了します" ,"処理の再起動回数が閾値を超えたので終了します。\n\n----------\n"+Logger.getLog());
          return;
        }
      }
    } // try catch
  } // while
  
  /**
   * 次回への引き継ぎ
   */
  if(w <weeks -wStart){
    SP.setProperty("ROOP_COUNT" ,w);
    Logger.log("Set property. [ROOP_COUNT: "+w+" /"+(weeks-wStart)+"]\n");
    
    var triggerId = ScriptApp.newTrigger(arguments.callee.name).timeBased().at(MO.moment().add("minutes" ,ST.RUN_SYCLE).toDate()).create().getUniqueId();
    SP.setProperty(triggerKey ,triggerId);
    Logger.log("Make trriger and Set property. ["+triggerKey+": "+triggerId+"]");
    
    if(GV.EXECUTE_COUNT>1){ 
      SP.deleteProperty("EXECUTE_COUNT");
      Logger.log("Delete property. [EXECUTE_COUNT]\n");
      
      Logger.log("End script. ["+arguments.callee.name+"] エラーによる再起動処理を終了し、"+ST.RUN_SYCLE+"分後に続きの処理を行います。");
      GV.MAIL("Ran script: "+arguments.callee.name+" ["+w+" /"+(weeks-wStart)+"] [ "+GV.EXECUTE_COUNT+" ] エラーによる再起動処理を終了し、"+ST.RUN_SYCLE+"分後に続きの処理を行います。" ,Logger.getLog());

    }else{
      Logger.log("End script: "+arguments.callee.name+" "+ST.RUN_SYCLE+"分後に続きの処理を行います。");
      GV.MAIL("Ran script: "+arguments.callee.name+" ["+w+" /"+(weeks-wStart)+"]"+ST.RUN_SYCLE+"分後に続きの処理を行います。" ,Logger.getLog());
    }
    
  /**
   * 全ての処理が完了した時
   */
  }else{
    if(GV.EXECUTE_COUNT>0){ 
      SP.deleteProperty("EXECUTE_COUNT");
      SP.deleteProperty("ROOP_COUNT");
      Logger.log("Delete property. [EXECUTE_COUNT ,ROOP_COUNT]\n");
      
      Logger.log("End script. ["+arguments.callee.name+"] エラーによる再起動処理を終了します。");
      GV.MAIL("Ran script: "+arguments.callee.name+" ["+w+" /"+(weeks-wStart)+"] [ "+GV.EXECUTE_COUNT+" ] エラーによる再起動処理を終了します。" ,Logger.getLog());
      
    }else{
      SP.deleteProperty("ROOP_COUNT");
      Logger.log("Delete property. [ROOP_COUNT]\n");

      Logger.log("End script.["+arguments.callee.name+"]");
      GV.MAIL("Ran script: "+arguments.callee.name+" ["+w+" /"+(weeks-wStart)+"]" ,Logger.getLog());
    }
  }
}//func

目次へ

3-4. テクニカル分析を計算する

作成したFT「[BackTest] Prices」の価格データをもとに、テクニカル分析を算出します。

手順

  1. 銘柄と期間を指定
  2. 「Techs()」の関数を走らせる(結果はSS「[作成用] 3.Prices ,Techs ,Sigs 1(~5)」に)
  3. それぞれ、SSに出力されたものを、FT「[BackTest] [BackTest] Prices ,Techs」に登録する(SSでCSVに書き出し、FTで読み込む)
  4. FT「[BackTest] [BackTest] Prices ,Techs」に基軸通貨(「[作成用] 1.Prices forCalc」)のデータを登録する

1-3. Techs (MA,MACD,BB,Stage)20000セル対策最新.gs

/**
 * テクニカル分析を算出してSSへ
 * フィルターを EMA300 と 終値
 */
function Techs(){
  var functionStart = MO.moment();
  Logger.log("Start script.["+arguments.callee.name+"]\n[EXECUTE_COUNT: "+GV.EXECUTE_COUNT+"]\n[ROOP_COUNT: "+GV.ROOP_COUNT+"]\n\n");
  
  var triggerKey = ("TRIGGER_ID_"+arguments.callee.name);
  if(GV.ROOP_COUNT){
    TU.deleteTrigger(triggerKey); // 通常の処理のトリガーを削除 
    Logger.log("Deleted used trriger.\n");
  }
  if(GV.EXECUTE_COUNT){
    TU.deleteTrigger(); // エラー処理用のトリガーを削除
    Logger.log("Deleted trriger of error handling.\n");
  }
  var 
   rooped = (function(a){return a==0 ? [] : a.split(",")})(GV.ROOP_COUNT)
  ,sheetNum = 0
  ;
  
  A:while(true){
    try{
      
      /**
       * setting
       */
      var
       ST = {SHEET:
             (function(){
               var obj = new Object() ,num;
               for(num in GV.SS.Techs){
                 obj[num] = SpreadsheetApp.openById(GV.SS.Techs[num].ID).getSheetByName(GV.SS.Techs[num].SHEET);
               }
               return obj;
             })()
             
             // 計算期間を指定(Date:"YYYY.MM.DD")
             ,START: "1999.01.01"
             ,END  : "2015.12.31"
             
             // 再起動の間隔(分)
             ,RUN_SYCLE: 2
             
             // 書き出す内容は全てここで指定
             ,ITM:{
               Date              : {calc_start:0   ,func :function(){return _.array[itm_k];}}
               ,SymNum           : {calc_start:0   ,func :function(){return _.array[itm_k];}}
               ,Symbol           : {calc_start:0   ,func :function(){return _.array[itm_k];}}
               ,Open             : {calc_start:0   ,func :function(){return _.array[itm_k];}}
               ,High             : {calc_start:0   ,func :function(){return _.array[itm_k];}}
               ,Low              : {calc_start:0   ,func :function(){return _.array[itm_k];}}
               ,Close            : {calc_start:0   ,func :function(){return _.array[itm_k];}}
               
               ,TR               : {calc_start:1   ,func :function(){return CALC.roundN(CALC.TR(_.array[_.idx("High")] ,_.array[_.idx("Low")] ,_.array_[_.idx("Close")]) ,GV.SYM[sym].R);}}
               ,ATR20            : {calc_start:19  ,func_:function(){var i,n=20,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("TR")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(20 ,_.array_[_.idx("ATR20")] ,_.array[_.idx("TR")]) ,GV.SYM[sym].R);}}
               ,SMATR20          : {calc_start:19  ,func :function(){var i,n=20,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("TR")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);}}
               
               ,High10           : {calc_start:10  ,func :function(){var i,n=10,arHigh=[]; for(i=1;i<n+1;i++){arHigh.push(ar[sym][day_j-i][_.idx("High")])} return Math.max.apply(null ,arHigh);}}
               ,High20           : {calc_start:20  ,func :function(){var i,n=20,arHigh=[]; for(i=1;i<n+1;i++){arHigh.push(ar[sym][day_j-i][_.idx("High")])} return Math.max.apply(null ,arHigh);}}
               ,Low10            : {calc_start:10  ,func :function(){var i,n=10,arLow =[]; for(i=1;i<n+1;i++){arLow .push(ar[sym][day_j-i][_.idx("Low" )])} return Math.min.apply(null ,arLow.filter(function(a){return a != false;}));}}
               ,Low20            : {calc_start:20  ,func :function(){var i,n=20,arLow =[]; for(i=1;i<n+1;i++){arLow .push(ar[sym][day_j-i][_.idx("Low" )])} return Math.min.apply(null ,arLow.filter(function(a){return a != false;}));}}
               
               ,SMA20            : {calc_start:19  ,func :function(){var i,n=20 ,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);}}
               ,SMA350           : {calc_start:349 ,func :function(){var i,n=350,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);}}

               ,EMA5             : {calc_start:4   ,func_:function(){var i,n=5  ,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(5   ,_.array_[_.idx("EMA5"  )] ,_.array[_.idx("Close")]) ,GV.SYM[sym].R);}}
               ,EMA6             : {calc_start:5   ,func_:function(){var i,n=6  ,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(6   ,_.array_[_.idx("EMA6"  )] ,_.array[_.idx("Close")]) ,GV.SYM[sym].R);}}
               ,EMA12            : {calc_start:11  ,func_:function(){var i,n=12 ,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(12  ,_.array_[_.idx("EMA12" )] ,_.array[_.idx("Close")]) ,GV.SYM[sym].R);}}
               ,EMA19            : {calc_start:18  ,func_:function(){var i,n=19 ,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(19  ,_.array_[_.idx("EMA19" )] ,_.array[_.idx("Close")]) ,GV.SYM[sym].R);}}
               ,EMA20            : {calc_start:19  ,func_:function(){var i,n=20 ,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(20  ,_.array_[_.idx("EMA20" )] ,_.array[_.idx("Close")]) ,GV.SYM[sym].R);}}
               ,EMA25            : {calc_start:24  ,func_:function(){var i,n=25 ,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(25  ,_.array_[_.idx("EMA25" )] ,_.array[_.idx("Close")]) ,GV.SYM[sym].R);}}
               ,EMA26            : {calc_start:25  ,func_:function(){var i,n=26 ,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(26  ,_.array_[_.idx("EMA26" )] ,_.array[_.idx("Close")]) ,GV.SYM[sym].R);}}
               ,EMA40            : {calc_start:39  ,func_:function(){var i,n=40 ,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(40  ,_.array_[_.idx("EMA40" )] ,_.array[_.idx("Close")]) ,GV.SYM[sym].R);}}
               ,EMA50            : {calc_start:49  ,func_:function(){var i,n=50 ,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(50  ,_.array_[_.idx("EMA50" )] ,_.array[_.idx("Close")]) ,GV.SYM[sym].R);}}
               ,EMA100           : {calc_start:99  ,func_:function(){var i,n=100,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(100 ,_.array_[_.idx("EMA100")] ,_.array[_.idx("Close")]) ,GV.SYM[sym].R);}}
               ,EMA150           : {calc_start:149 ,func_:function(){var i,n=150,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(150 ,_.array_[_.idx("EMA150")] ,_.array[_.idx("Close")]) ,GV.SYM[sym].R);}}
               ,EMA250           : {calc_start:249 ,func_:function(){var i,n=250,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(250 ,_.array_[_.idx("EMA250")] ,_.array[_.idx("Close")]) ,GV.SYM[sym].R);}}
               ,EMA300           : {calc_start:299 ,func_:function(){var i,n=300,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(300 ,_.array_[_.idx("EMA300")] ,_.array[_.idx("Close")]) ,GV.SYM[sym].R);}}
               ,EMA350           : {calc_start:349 ,func_:function(){var i,n=350,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(350 ,_.array_[_.idx("EMA350")] ,_.array[_.idx("Close")]) ,GV.SYM[sym].R);}}
               
               ,MACD_6_19        : {calc_start:18  ,func :function(){return CALC.roundN(_.array[_.idx("EMA6")] -_.array[_.idx("EMA19")] ,GV.SYM[sym].R);}}
               ,MACD_6_19_EMA9   : {calc_start:26  ,func_:function(){var i,n=9,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("MACD_6_19")]-0)}   return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(9 ,_.array_[itm_k] ,_.array[_.idx("MACD_6_19")]) ,GV.SYM[sym].R);}}
               ,MACD2_6_19       : {calc_start:26  ,func :function(){return CALC.roundN(_.array[_.idx("MACD_6_19")] -_.array[_.idx("MACD_6_19_EMA9")] ,GV.SYM[sym].R);}}

               ,MACD_5_20        : {calc_start:19  ,func :function(){return CALC.roundN(_.array[_.idx("EMA5")] -_.array[_.idx("EMA20")] ,GV.SYM[sym].R);}}
               ,MACD_5_20_EMA9   : {calc_start:27  ,func_:function(){var i,n=9,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("MACD_5_20")]-0)}   return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(9 ,_.array_[itm_k] ,_.array[_.idx("MACD_5_20")]) ,GV.SYM[sym].R);}}
               ,MACD2_5_20       : {calc_start:27  ,func :function(){return CALC.roundN(_.array[_.idx("MACD_5_20")] -_.array[_.idx("MACD_5_20_EMA9")] ,GV.SYM[sym].R);}}
               
               ,MACD_5_40        : {calc_start:39  ,func :function(){return CALC.roundN(_.array[_.idx("EMA5")] -_.array[_.idx("EMA40")] ,GV.SYM[sym].R);}}
               ,MACD_5_40_EMA9   : {calc_start:47  ,func_:function(){var i,n=9,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("MACD_5_40")]-0)}   return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(9 ,_.array_[itm_k] ,_.array[_.idx("MACD_5_40")]) ,GV.SYM[sym].R);}}
               ,MACD2_5_40       : {calc_start:47  ,func :function(){return CALC.roundN(_.array[_.idx("MACD_5_40")] -_.array[_.idx("MACD_5_40_EMA9")] ,GV.SYM[sym].R);}}
               
               ,MACD_20_40       : {calc_start:39  ,func :function(){return CALC.roundN(_.array[_.idx("EMA20")] -_.array[_.idx("EMA40")] ,GV.SYM[sym].R);}}
               ,MACD_20_40_EMA9  : {calc_start:47  ,func_:function(){var i,n=9,sum=0; for(i=0;i<n;i++){sum+=(ar[sym][day_j-i][_.idx("MACD_20_40")]-0)}   return CALC.roundN(sum/n ,GV.SYM[sym].R);} ,func:function(){return CALC.roundN(CALC.EMA(9 ,_.array_[itm_k] ,_.array[_.idx("MACD_20_40")]) ,GV.SYM[sym].R);}}
               ,MACD2_20_40      : {calc_start:47  ,func :function(){return CALC.roundN(_.array[_.idx("MACD_20_40")] -_.array[_.idx("MACD_20_40_EMA9")] ,GV.SYM[sym].R);}}
               
               ,Filter_C_300     : {calc_start:299 ,func :function(){return CALC.roundN(_.array[_.idx("Close")]  -_.array[_.idx("EMA300")] ,GV.SYM[sym].R);}}
               ,Filter_50_300    : {calc_start:299 ,func :function(){return CALC.roundN(_.array[_.idx("EMA50")]  -_.array[_.idx("EMA300")] ,GV.SYM[sym].R);}}
               ,Filter_25_350    : {calc_start:349 ,func :function(){return CALC.roundN(_.array[_.idx("EMA25")]  -_.array[_.idx("EMA350")] ,GV.SYM[sym].R);}}
               ,Filter_100_350   : {calc_start:349 ,func :function(){return CALC.roundN(_.array[_.idx("EMA100")] -_.array[_.idx("EMA350")] ,GV.SYM[sym].R);}}

               ,Stage : {
                 calc_start:39  
                 ,func : function(){
                   var 
                   short   = _.array[_.idx("EMA5")]
                   ,middle = _.array[_.idx("EMA20")]
                   ,long   = _.array[_.idx("EMA40")]
                   ,stage
                   = (short  >=middle && middle >=long  ) ? 1
                   : (middle >=short  && short  >=long  ) ? 2
                   : (middle >=long   && long   >=short ) ? 3
                   : (long   >=middle && middle >=short ) ? 4
                   : (long   >=short  && short  >=middle) ? 5
                   : (short  >=long   && long   >=middle) ? 6
                   : "error";
                   if(stage==="error") throw new Error("ステージの計測でエラーが発生しました。");
                   return stage;
                 }
               }

               ,sigma20          : {calc_start:19  ,func :function(){var i,n=20,data=[]; for(i=0;i<n;i++){data.push(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(CALC.StandardDeviation(data) ,GV.SYM[sym].R);}}
               ,sigma350         : {calc_start:349 ,func :function(){var i,n=350,data=[]; for(i=0;i<n;i++){data.push(ar[sym][day_j-i][_.idx("Close")] -0)} return CALC.roundN(CALC.StandardDeviation(data) ,GV.SYM[sym].R);}}

               ,sigmaTR20        : {calc_start:19  ,func :function(){var i,n=20,data=[]; for(i=0;i<n;i++){data.push(ar[sym][day_j-i][_.idx("TR")] -0)} return CALC.roundN(CALC.StandardDeviation(data) ,GV.SYM[sym].R);}}
               
               ,pre_plus_DM      : {calc_start:1   ,func :function(){return CALC.roundN(_.array[_.idx("High")] -_.array_[_.idx("High")] ,GV.SYM[sym].R);}}
               ,pre_minus_DM     : {calc_start:1   ,func :function(){return CALC.roundN(_.array_[_.idx("Low")] -_.array[_.idx("Low")]   ,GV.SYM[sym].R);}}
               ,plus_DM          : {calc_start:1   ,func :function(){var pre_plus_DM=_.array[_.idx("pre_plus_DM")] ,pre_minus_DM=_.array[_.idx("pre_minus_DM")]; return pre_plus_DM <0 ?  0 : pre_plus_DM==pre_minus_DM ? 0 : pre_plus_DM <pre_minus_DM ? 0 : pre_plus_DM;}}
               ,minus_DM         : {calc_start:1   ,func :function(){var pre_plus_DM=_.array[_.idx("pre_plus_DM")] ,pre_minus_DM=_.array[_.idx("pre_minus_DM")]; return pre_minus_DM <0 ? 0 : pre_minus_DM==pre_plus_DM ? 0 : pre_minus_DM <pre_plus_DM ? 0 : pre_minus_DM;}}
               ,pre_sum_TR       : {calc_start:14  ,func :function(){var i,n=14,sum_TR=0;       for(i=0;i<n;i++){sum_TR      +=(ar[sym][day_j-i][_.idx("TR")]-0);}       return sum_TR;}} 
               ,pre_sum_plus_DM  : {calc_start:14  ,func :function(){var i,n=14,sum_plus_DM=0;  for(i=0;i<n;i++){sum_plus_DM +=(ar[sym][day_j-i][_.idx("plus_DM")]-0);}  return sum_plus_DM;}}
               ,pre_sum_minus_DM : {calc_start:14  ,func :function(){var i,n=14,sum_minus_DM=0; for(i=0;i<n;i++){sum_minus_DM+=(ar[sym][day_j-i][_.idx("minus_DM")]-0);} return sum_minus_DM;}}
               ,plus_DI_14       : {calc_start:14  ,func :function(){return CALC.roundN(_.array[_.idx("pre_sum_plus_DM")]  /_.array[_.idx("pre_sum_TR")] *100 ,2);}}
               ,minus_DI_14      : {calc_start:14  ,func :function(){return CALC.roundN(_.array[_.idx("pre_sum_minus_DM")] /_.array[_.idx("pre_sum_TR")] *100 ,2);}}
               ,DX               : {calc_start:14  ,func :function(){var plus_DI=_.array[_.idx("plus_DI_14")] ,minus_DI=_.array[_.idx("minus_DI_14")];return CALC.roundN(Math.abs(plus_DI -minus_DI) /(plus_DI +minus_DI) *100 ,2)}}
               ,ADX_14           : {calc_start:27  ,func :function(){var i,n=14,sum_DX=0; for(i=0;i<n;i++){sum_DX+=(ar[sym][day_j-i][_.idx("DX")]-0);} return CALC.roundN(sum_DX /n ,2);}}
             }
             ,FT_LIST:["Date","SymNum","Symbol","Open","High","Low","Close"]
      }
      ,_ = {array:[] ,array_:[]
            ,set_vars_day : function(){
              this.array = ar[sym][day_j];
              if(day_j >0) this.array_ = ar[sym][day_j-1];
            }
            ,idx : function(itm){return itm_list.indexOf(itm);}
           }
      ,start    = MO.moment(ST.START ,"YYYY.MM.DD")
      ,end      = MO.moment(ST.END   ,"YYYY.MM.DD")
      ,symLen   = Object.keys(GV.SYM).length
      ,itm_list = Object.keys(ST.ITM)
      ,preAr    = new Array()
      ,ar       = new Object()
      ,sym
      ,day_j
      ,itm_k
      ,itm
      ,log
      ;
      
      /**
       * データの取得と成形
       */
      preAr = FTU._getRows({
        ID        : GV.FT.PRICE.ID  
        ,SELECT   : ST.FT_LIST.toString()
        ,WHERE    : "Date>\'"+ST.START+"\'"  
        ,ORDER_BY : "Date ASC"
      }).RESULT.rows;
      for(sym in GV.SYM) ar[sym] = preAr.filter(function(pre){return pre.indexOf(sym)>=0;});
      
      /**
       * main function
       */
      B:for(sym in GV.SYM){
        if(!~rooped.toString().indexOf(sym)){
          for(day_j=0; day_j<ar[sym].length; day_j++){
            _.set_vars_day();
            
            for(itm_k=ST.FT_LIST.length; itm_k<itm_list.length; itm_k++){
              itm = itm_list[itm_k];
              
              if(day_j <ST.ITM[itm].calc_start){
                _.array[itm_k] = "";
                
              }else if(ST.ITM[itm].calc_start==day_j && ST.ITM[itm].func_){
                _.array[itm_k] = ST.ITM[itm].func_();
                
              }else{
                _.array[itm_k] = ST.ITM[itm].func();
              }
            }
            if(TU.isNearlyTimeOut(functionStart ,{SCALE:"seconds" ,NUM:320})) break B;
          }
          C:while(true){
            try{
              var lastRow = ST.SHEET[sheetNum].getLastRow();
              if(lastRow ==0){
                ST.SHEET[sheetNum].getRange(1 ,1 ,1 ,itm_list.length).setValues([itm_list]);
                lastRow++;
              }
              ST.SHEET[sheetNum].getRange(lastRow+1 ,1 ,ar[sym].length ,itm_list.length).setValues(ar[sym]);
              Logger.log("Set data of "+sym+" to SS["+sheetNum+"].");
              break C;
              
            }catch(e){
              if(~e.message.indexOf("2000000")){
                sheetNum++;
                Logger.log("スプレッドシートの上限に達したのでSS["+sheetNum+"]に切り替えます。\n");
              }else{
                throw new Error(e.message);
                break C;
              }
            }
          }
          
          rooped.push(sym);
        }
      }
      break A;
      
    /**
     * エラー処理
     */
    }catch(e){
      GV.ERROR_COUNT++;
      Logger.log(e+"\n[ERROR_COUNT: "+GV.ERROR_COUNT+"]\n");
      
      if(GV.ERROR_COUNT >2){
        GV.EXECUTE_COUNT++;
        
        if(GV.EXECUTE_COUNT <4){
          SP.setProperties({EXECUTE_COUNT:GV.EXECUTE_COUNT});
          Logger.log("Set property.\n  [EXECUTE_COUNT: "+GV.EXECUTE_COUNT+"]\n");
          
          TU.resumeTrigger(arguments.callee.name ,{intervalMinutes:2});
          Logger.warning("なんかよく失敗しているのでちょっと経ってから再起動します。");
          
          GV.MAIL("Ran script: "+arguments.callee.name+" 処理のエラー回数が閾値を超えたので再起動します" ,"処理のエラー回数が閾値を超えたので設定時間が経過した後に再起動します。\n\n----------\n"+Logger.getLog());
          return;
          
        }else{
          Logger.warning("再起動してもダメなようです。確認してみてください。");
          GV.MAIL("Ran script: "+arguments.callee.name+" 処理の再起動回数が閾値を超えたので終了します" ,"処理の再起動回数が閾値を超えたので終了します。\n\n----------\n"+Logger.getLog());
          return;
        }
      }
    } // try catch
  } // while
  
  /**
   * 次回への引き継ぎ
   */
  if(rooped.length <symLen){
    SP.setProperty("ROOP_COUNT" ,rooped.toString());
    Logger.log("Set property. [ROOP_COUNT: "+rooped.length+" /"+symLen+"]\n");
    
    var triggerId = ScriptApp.newTrigger(arguments.callee.name).timeBased().at(MO.moment().add("minutes" ,ST.RUN_SYCLE).toDate()).create().getUniqueId();
    SP.setProperty(triggerKey ,triggerId);
    Logger.log("Make trriger and Set property. ["+triggerKey+": "+triggerId+"]");
    
    if(GV.EXECUTE_COUNT >1){ 
      SP.deleteProperty("EXECUTE_COUNT");
      Logger.log("Delete property. [EXECUTE_COUNT]\n");
      
      Logger.log("End script. ["+arguments.callee.name+"] エラーによる再起動処理を終了し、"+ST.RUN_SYCLE+"分後に続きの処理を行います。");
      GV.MAIL("Ran script: "+arguments.callee.name+" ["+rooped.length+" /"+symLen+"] [ "+GV.EXECUTE_COUNT+" ] エラーによる再起動処理を終了し、"+ST.RUN_SYCLE+"分後に続きの処理を行います。" ,Logger.getLog());
      
    }else{
      Logger.log("End script: "+arguments.callee.name+" "+ST.RUN_SYCLE+"分後に続きの処理を行います。");
      GV.MAIL("Ran script: "+arguments.callee.name+" ["+rooped.length+" /"+symLen+"]"+ST.RUN_SYCLE+"分後に続きの処理を行います。" ,Logger.getLog());
    }
    
  /**
   * 全ての処理が完了した時
   */
  }else{
    if(GV.EXECUTE_COUNT>0){ 
      SP.deleteProperty("EXECUTE_COUNT");
      SP.deleteProperty("ROOP_COUNT");
      Logger.log("Delete property. [EXECUTE_COUNT ,ROOP_COUNT]\n");
      
      Logger.log("End script. ["+arguments.callee.name+"] エラーによる再起動処理を終了します。");
      GV.MAIL("Ran script: "+arguments.callee.name+" ["+rooped.length+" /"+symLen+"] [ "+GV.EXECUTE_COUNT+" ] エラーによる再起動処理を終了します。" ,Logger.getLog());
      
    }else{
      SP.deleteProperty("ROOP_COUNT");
      Logger.log("Delete property. [ROOP_COUNT]\n");
      
      Logger.log("End script.["+arguments.callee.name+"]");
      GV.MAIL("Ran script: "+arguments.callee.name+" ["+rooped.length+" /"+symLen+"]" ,Logger.getLog());
    }
  }
} // func

目次へ

3-5. 市場間の休日のずれを埋める

作成したFT「[BackTest] [BackTest] Prices ,Techs」の中で、休場のズレを埋めていきます。

手順

  1. 「DateOperation()」の関数を走らせる
  2. すべて完了した時点で、確認のため再度「DateOperation()」を走らせる
  3. 問題なければ完了(問題があった場合はログのメールを参考に手作業でFTを修正していく)。

これも、もっと効率の良い書き方がある気がする。。というか、Pythonならちゃちゃっと修正できる。

1-5. Date Operation.gs

/**
 * 日付を整える
 *
 * -- 銘柄による取引日の違いを埋める
 */
function DateOperation(){
  var functionStart = MO.moment();
  Logger.log("Start script.["+arguments.callee.name+"]\n[EXECUTE_COUNT: "+GV.EXECUTE_COUNT+"]\n[ROOP_COUNT: "+GV.ROOP_COUNT+"]\n\n");

  var triggerKey = ("TRIGGER_ID_"+arguments.callee.name);
  if(GV.ROOP_COUNT){
    TU.deleteTrigger(triggerKey); // 通常の処理のトリガーを削除 
    Logger.log("Deleted used trriger.\n");
  }
  if(GV.EXECUTE_COUNT){
    TU.deleteTrigger(); // エラー処理用のトリガーを削除
    Logger.log("Deleted trriger of error handling.\n");
  }
  
  A:while(true){
    try {
      
      /**
       * セッテイング
       */
      ////////////////////////////////////////////////////////////
      ////////////////////////////////////////////////////////////
      var
      ST = {
        RUN_SYCLE : 2            // 再起動の間隔(分)
        ,START    : "2000.1.1"   // 開始の日付
        ,END      : "2016.12.31" // 終了の日付
        ,SYCLE    : 1            // データ取得の間隔(年)
        
        ,FT_maxResults : 100
      }
      ////////////////////////////////////////////////////////////
      ////////////////////////////////////////////////////////////
      ,WORK = {
        Techs:{FT_ID:GV.FT.Techs.ID  ,ar:new Array()}
      }
      ,FT = {
        START :(
          GV.ROOP_COUNT==0 ? MO.moment(ST.START ,"YYYY.MM.DD").format("YYYY.MM.DD") 
          : MO.moment(ST.START ,"YYYY.MM.DD").add(GV.ROOP_COUNT ,"years").format("YYYY.MM.DD")
        )
        ,START_ :(
          GV.ROOP_COUNT==0 ? MO.moment(ST.START ,"YYYY.MM.DD").subtract(1 ,"week").format("YYYY.MM.DD") 
          : MO.moment(ST.START ,"YYYY.MM.DD").add(GV.ROOP_COUNT ,"years").subtract(1 ,"week").format("YYYY.MM.DD")
        )
        ,END :(
          GV.ROOP_COUNT==0 ? MO.moment(ST.START ,"YYYY.MM.DD").add(ST.SYCLE ,"year").subtract(1 ,"day").format("YYYY.MM.DD")
          : MO.moment(ST.START ,"YYYY.MM.DD").add(ST.SYCLE+GV.ROOP_COUNT ,"years").subtract(1 ,"day").format("YYYY.MM.DD")
        )
        ,Years :MO.moment(ST.END ,"YYYY.MM.DD").diff(MO.moment(ST.START ,"YYYY.MM.DD") ,"years") +1
      }
      ,syms = Object.keys(GV.SYM)
      ,all_syms_len = (syms.length + GV.forCalc.length)
      ,w
      ;
      for(w in WORK) if(WORK[w].FT_ID) WORK[w]["list"] = FTU._getItems({ID:WORK[w].FT_ID ,maxResults:ST.FT_maxResults});
      
      /**
      * 年毎の処理 (SHへは年毎の書き出し)
      */
      var year ,w ,sym_ ,sym ,day ,itm;
      B:for(year=GV.ROOP_COUNT; year<FT.Years; year++){
        if(year>0){
          FT.START  = MO.moment(ST.START ,"YYYY.MM.DD").add(year ,"years").format("YYYY.MM.DD");
          FT.START_ = MO.moment(ST.START ,"YYYY.MM.DD").add(year ,"years").subtract(1 ,"week").format("YYYY.MM.DD");
          FT.END    = MO.moment(ST.START ,"YYYY.MM.DD").add(ST.SYCLE +year ,"years").subtract(1 ,"day").format("YYYY.MM.DD");
        }
        Logger.log("////////////////////////////////\n\n\nFusion tables から次の期間のデータを取得します。\n"+FT.START+"\n"+FT.END+"\n");
        
        // データの取得と成形
        for(w in WORK){
          // FTのIDの有無
          if(WORK[w].FT_ID){
            // データの取得
            WORK[w].pre_ar = FTU._getRows({
              ID       :WORK[w].FT_ID
              ,SELECT  :WORK[w]["list"].toString()
              ,WHERE   :"Date>=\'"+FT.START_+"\' AND Date<=\'"+FT.END+"\' "
              ,ORDER_BY:"Date ASC"
            }).RESULT.rows;
            
            var 
             date_  = MO.moment(FT.START  ,"YYYY.MM.DD")
            ,date__ = MO.moment(FT.START_ ,"YYYY.MM.DD")
            ,date
            ,ar_date_len = (MO.moment(FT.END ,"YYYY.MM.DD").diff(date__ ,"days") +1)
            ,ar_date
            // 何かのエラーを防ぐためのバッファ、計算期間に余裕を持たせている
            ,buffer = (ar_date_len -(MO.moment(FT.END ,"YYYY.MM.DD").diff(date_ ,"days") +1))
            ;
            for(ar_date=0; ar_date<ar_date_len; ar_date++){
              date = [date__.format("YYYY/M/D") ,date__.format("YYYY/MM/DD") ,date__.format("YYYY-M-D") ,date__.format("YYYY-MM-DD")];
              WORK[w].ar[ar_date] = WORK[w].pre_ar.filter(function(pre){return ~pre.indexOf(date[0]) || ~pre.indexOf(date[1]) || ~pre.indexOf(date[2]) || ~pre.indexOf(date[3]);});
              
              if(ar_date >buffer-1){
                if(WORK[w].ar[ar_date].length==0){
                  ar_date --;
                  ar_date_len --;
                  
                }else if(WORK[w].ar[ar_date].length >all_syms_len){
                  Logger.log("Error:\n"+date[0]+" のデータが重複しています。\n\n");
                  
                }else if(WORK[w].ar[ar_date].length <all_syms_len){
                  C:for(sym_=0; sym_<all_syms_len; sym_++){
                    sym = sym_<syms.length ? syms[sym_] : GV.forCalc[sym_ -syms.length];
                    
                    // ar_dateの日、銘柄(sym)のデータがないとき
                    if(!~WORK[w].ar[ar_date].toString().indexOf(sym)){
                      // iLen日分さかのぼってデータを探す
                      for(var i=1 ,iLen=10; i<iLen; i++){
                        var push_ar = WORK[w].ar[ar_date -i].filter(function(pre){return ~pre.indexOf(sym);});
                        // filterで該当がないと[]が返るので、toStringで""に変換して、true・falseの判別。
                        if(push_ar.toString()){
                          var push_len = WORK[w].ar[ar_date].push(push_ar[0].slice(0));
                          break;
                        }
                      }
                      // 見つけたデータを日付と始値、高値、安値を修正
                      var before_close = WORK[w].ar[ar_date][push_len-1][WORK[w].list.indexOf("Close")];
                      WORK[w].ar[ar_date][push_len-1][WORK[w].list.indexOf("Date")] = date[0];
                      WORK[w].ar[ar_date][push_len-1][WORK[w].list.indexOf("Open")] = before_close;
                      WORK[w].ar[ar_date][push_len-1][WORK[w].list.indexOf("High")] = before_close;
                      WORK[w].ar[ar_date][push_len-1][WORK[w].list.indexOf("Low")]  = before_close;
                      
                      FTU._insertRow({
                        ID      :WORK[w].FT_ID
                        ,COLUMNS:"\'"+WORK[w].list.toString().replace(/,/g, "\',\'")+"\'"
                        ,VALUES :"\'"+WORK[w].ar[ar_date][WORK[w].ar[ar_date].length -1].toString().replace(/NaN/g, "").replace(/,/g, "\',\'")+"\'"
                      })
                      Logger.log(date[0]+" | "+sym);
                      
                      if(push_len==all_syms_len) break C;
                    }
                  }
                }
              }
              date__.add(1 ,"days");
              if(TU.isNearlyTimeOut(functionStart ,{SCALE:"seconds" ,NUM:300})){
                Logger.log("一定時間を経過したので作業を終了します。\n");
                break B;
              }
            }
          }
        }
        GV.ROOP_COUNT++;
      }
      break A;
      
      /**
      * エラー処理
      */
    }catch(e){
      GV.ERROR_COUNT++;
      Logger.log(e.name+":"+e.message+"\n[ERROR_COUNT: "+GV.ERROR_COUNT+"]\n");
      
      if(GV.ERROR_COUNT >5){
        GV.EXECUTE_COUNT++;

        SP.setProperties({EXECUTE_COUNT:GV.EXECUTE_COUNT ,ROOP_COUNT:GV.ROOP_COUNT});
        Logger.log("Set property.\n  [EXECUTE_COUNT: "+GV.EXECUTE_COUNT+"]\n[ROOP_COUNT: "+GV.ROOP_COUNT+"]\n");
        
        if(GV.EXECUTE_COUNT<10){
          TU.resumeTrigger(arguments.callee.name ,{intervalMinutes:2});
          Logger.warning("なんかよく失敗しているのでちょっと経ってから再起動します。");
          
          GV.MAIL("Ran script: "+arguments.callee.name+" 処理のエラー回数が閾値を超えたので再起動します" ,"処理のエラー回数が閾値を超えたので設定時間が経過した後に再起動します。\n\n----------\n"+Logger.getLog());
          return;
          
        }else{
          Logger.warning("再起動してもダメなようです。確認してみてください。");
          GV.MAIL("Ran script: "+arguments.callee.name+" 処理の再起動回数が閾値を超えたので終了します" ,"処理の再起動回数が閾値を超えたので終了します。\n\n----------\n"+Logger.getLog());
          return;
        }
        
      }else if(~e.message.indexOf("Rate Limit Exceeded")){
        var ErrorStart = MO.moment();
        Logger.log("20秒間、処理を停止します。");
        while(true){if(TU.isNearlyTimeOut(ErrorStart ,{SCALE:"seconds" ,NUM:20})) break;}
        Logger.log("処理を再開します。\n");
      }
    } // try catch
  } // while
  
  /**
   * 次回への引き継ぎ
   */
  if(GV.ROOP_COUNT <FT.Years){
    SP.setProperty("ROOP_COUNT" ,GV.ROOP_COUNT);
    Logger.log("Set property.\n[ROOP_COUNT: "+GV.ROOP_COUNT+" /"+FT.Years+"]\n");
    
    var triggerId = ScriptApp.newTrigger(arguments.callee.name).timeBased().at(MO.moment().add("minutes" ,ST.RUN_SYCLE).toDate()).create().getUniqueId();
    SP.setProperty(triggerKey ,triggerId);
    Logger.log("Make trriger and Set property. ["+triggerKey+": "+triggerId+"]");
    
    if(GV.EXECUTE_COUNT >1){ 
      SP.deleteProperty("EXECUTE_COUNT");
      Logger.log("Delete property. [EXECUTE_COUNT]\n");
      
      Logger.log("End script. ["+arguments.callee.name+"] エラーによる再起動処理を終了し、"+ST.RUN_SYCLE+"分後に続きの処理を行います。");
      GV.MAIL("Ran script: "+arguments.callee.name+" ["+GV.ROOP_COUNT+" /"+FT.Years+"] [ "+GV.EXECUTE_COUNT+" ] エラーによる再起動処理を終了し、"+ST.RUN_SYCLE+"分後に続きの処理を行います。" ,Logger.getLog());
      
    }else{
      Logger.log("End script: "+arguments.callee.name+" "+ST.RUN_SYCLE+"分後に続きの処理を行います。");
      GV.MAIL("Ran script: "+arguments.callee.name+" ["+GV.ROOP_COUNT+" /"+FT.Years+"]"+ST.RUN_SYCLE+"分後に続きの処理を行います。" ,Logger.getLog());
    }
    
  /**
   * 全ての処理が完了した時
   */
  }else{
    if(GV.EXECUTE_COUNT>0){ 
      SP.deleteProperty("EXECUTE_COUNT");
      SP.deleteProperty("ROOP_COUNT");
      Logger.log("Delete property. [EXECUTE_COUNT ,ROOP_COUNT]\n");
      
      Logger.log("End script. ["+arguments.callee.name+"] エラーによる再起動処理を終了します。");
      GV.MAIL("Ran script: "+arguments.callee.name+" ["+GV.ROOP_COUNT+" /"+FT.Years+"] [ "+GV.EXECUTE_COUNT+" ] エラーによる再起動処理を終了します。" ,Logger.getLog());
      
    }else{
      SP.deleteProperty("ROOP_COUNT");
      Logger.log("Delete property. [ROOP_COUNT]\n");
      
      Logger.log("End script.["+arguments.callee.name+"]");
      GV.MAIL("Ran script: "+arguments.callee.name+" ["+GV.ROOP_COUNT+" /"+FT.Years+"]" ,Logger.getLog());
    }
  }
} // func

目次へ

3-6. いよいよバックテスト!!(シグナルや資金管理、リスク管理の設定)

やっとここまできました・・!
あとちょっと検証を実行できます!もうひと踏ん張りです!

まずは、検証用のコードを。

2. Back Test.gs

/**
 * バックテストを実行
 */

function Run_BackTest(){
  /*//////////////////
  // ForTEST /////////
  var testAr = [];
  //////////////////*/
  
  var functionStart = MO.moment();
  Logger.log("Start script.["+arguments.callee.name+"]\n[EXECUTE_COUNT: "+GV.EXECUTE_COUNT+"]\n[ROOP_COUNT: "+GV.ROOP_COUNT+"]\n\n");
  
  var
   triggerKey = ("TRIGGER_ID_"+arguments.callee.name)
  ,resumed = false
  ;
  if(GV.ROOP_COUNT){
    TU.deleteTrigger(triggerKey);
    resumed = true;
    Logger.log("\n/////【resumed mode】///////////////////////////\nDeleted used trriger.\nturn on resumed mode.\n");
  }
  if(GV.EXECUTE_COUNT){
    TU.deleteTrigger();
    Logger.log("Deleted trriger of error handling.\n");
  }
  
  /**
   * セッテイング
   */
  var ST = {
    
    // 再起動の間隔(分)
    RUN_SYCLE : 2
    
    // 計算する期間を指定 "YYYY.MM.DD"
    ,START : "2012.1.1"//"2001.1.1"
    ,END   : "2017.12.31"
    
    // FTでデータを取得する期間(年)
    ,SYCLE : 1 
    ,FT_maxResults : 100
    
    /**
     * 資金管理の設定
     */
    ,FIRST_FUND : 10000000
    ,RUIN_FUND  : 100000
    ,N_pct_FUND : 1/100
    ,N_Source   : "NetAssets"   // *{String} NetAssets or CashAsset リスク管理の基準を変更できる。
    ,N_Interest : "Simple"      // *{String} Compound(複利)or Simple(単利)
    ,Virtual_DD : 2            // *{Number} 1Nの算出時に減算するDDの価値 2とすると現在の元金からDD*2を減算する。年間の損失を最大50%におさえることができる。
    ,N_Calc     : "Correct"     // {String} Correct(しっかり)or Simple(簡単に)Nの計算をポジションとNのサイズにもとづいてしっかり計算するか、単純に1u=1Nとするか。すべてのリスク管理に影響がでる。
    ,Leverage : ({is:true,N:1}) // 1~50程度 1で現物
//    ,Leverage : ({is:true,N:50})
    
    /**
     * 税金
     */
//    ,TAX : ({is:true ,pct:0.2}) // 税金を考慮するかどうか(簡易版、マイナス分の繰越が未実装)
//    ,TAX : ({is:false})
    ,TAX : ({
      is:true
      ,pct:function(profit){ // 総合課税
        if ( profit <= 1950000 ) return 0.15;
        if ( profit <= 3300000 ) return 0.2;
        if ( profit <= 6950000 ) return 0.3;
        if ( profit <= 9000000 ) return 0.33;
        if ( profit <= 18000000 ) return 0.43;
        if ( profit <= 40000000 ) return 0.47;
        return 0.51; // 4000万円以上
      }
    })
                    
    /**
     * テクニカル由来の決済(開発時はExitSigとExitPriceの調整が必要)
     */
//    ,ExitSig : ({is:"HL" ,H:"High10" ,L:"Low10"}) // タートルズの利益確定
//    ,ExitSig : ({is:"Target" ,1:"SMA350"}) // ユニットのテクニカル分析「SMA350」など。
//    ,ExitSig : ({is:"CrossEMA" ,1:"EMA150" ,2:"EMA250"}) //  EMAのクロスで決済
    ,ExitSig : ({is:false})
    
    /**
     * 損切りの決済(開発時はExitSigとExitPriceの調整が必要)
     */
//    ,SO : ({is:"Turtle" ,1:"ATR20" ,N:2 ,CALC:"Change"}) // Fix(固定)or Change(変動)LOとSOの計算値を毎日計算し直すか、初回の計算値で固定とするか。
    ,SO : ({is:false})
    
    /**
     * 資金(Nの価値・為替換算対策)ベースの損切りの有効・無効(計算値を算出orゼロ)
     * ({is:true ,N:9.5 ,ChgPctN_Value:true ,ChgPctATR:true})
     */
//    ,Exit_N : ({is:true ,N:4 ,ChgPctN_Value:true ,ChgPctATR:true})
    ,Exit_N : ({is:false})
    
    /**
     * 時限式Exitの有効・無効
     * 時限式Exitの条件、価格での比較(Entryと直近の終値)の有効・無効
     */
//    ,Timed_Exit : ({is:true ,N:80 ,ComparePrice:false})
    ,Timed_Exit : ({is:false})
    
    /**
     * EntrySigの確認で途転(移動平均線のクロス等)
     */
    ,ReversingEntry : ({is:true})
//    ,ReversingEntry : ({is:false})

    /**
     * Entryの設定
     */
//    ,Entry : ({is:"AllSig",TIMING:"b4Closing",BUY_SELL:"Both"})
    ,Entry : ({is:"AllSig",TIMING:"b4Closing",BUY_SELL:"Buy"})
//    ,Entry : ({is:"NewSig",TIMING:"NextClose",BUY_SELL:"Both"})
    
    /**
     * EntrySignalの設定
     */
//    ,EntrySig : ({is:"Target",1:"EntrySig"}) 
//    ,EntrySig : ({is:"Target",1:"EMA150"})
    ,EntrySig : ({is:"doubleEMA",1:"EMA50",2:"EMA100",SIG:"Cross",SLOPE:false}) //,SIG:Over / Cross
//    ,EntrySig : ({is:"doubleEMA",1:"EMA100",2:"EMA350",SIG:"Cross",SLOPE:true}) //,SIG:Over / Cross
//    ,EntrySig : ({is:"TripleEMA",1:"EMA150",2:"EMA250",3:"EMA350"}) // 3より1と2が上にあるとき、1と2のGCでエントリー
//    ,EntrySig : ({is:"FilteredHL" ,Filter:"Filter_50_300" ,H:"High20" ,L:"Low20" ,SIG:"Cross"}) // 
//    ,EntrySig : ({is:"FilteredMACD" ,Filter:"Filter_50_300" ,1:"MACD2_6_19" ,SIG:"Cross"}) // 
    
    /**
     * ピラミッティングの設定
     * Over or Cross LOのラインを超えていれば良いとするか、LOを上抜けたタイミングだけでエントリーするか
     * Fix(固定)or Change(変動)LOとSOの計算値を毎日計算し直すか、初回の計算値で固定とするか。
     */
    ,LO : ({is:"Turtle" ,1:"EntryPrice" ,2:"ATR20" ,N:1 ,SIG:"Over" ,CALC:"Change"}) // N:1/2
    
    /**
     * 分散投資の上限の設定
     */
    ,LIMIT_TOTAL         : 24
    ,LIMIT_LONG          : 12
    ,LIMIT_SHORT         : 12
    ,LIMIT_YELLOW        : 6.4 //10.4
    ,LIMIT_RED           : 4.4 //6.4
    ,LIMIT_SINGLE        : 4.4
    ,LIMIT_N_Status      : "4u"        // {String} 4u or false 上限をリスク由来とするか、4とするか。リスク由来の場合は、4u以降も、EntrySig→LOで追加していく。
//    ,Correl_Risk         : true        // {boolean} 相関関係のリスクを考慮するかどうか
    ,Correl_Risk         : false
    ,Filter_CorrelRed    : false       // {boolean} false 0.7以上の相関リスクの銘柄のエントリーを制限する
    ,NumDays_Same_Factor : 25          // {Number} 強相関のエントリーを制限するときに、同じ要因のシグナルと見なす日数
    
    /**
     * 同時に出たシグナルから強いトレンドを抽出
     * Order_by (FilStrength,DI_14,DX,ADX_14) Order_by_Strength が有効なときに、何をもとに比較するかを指定する(ユニットの項目)
     * 昇順・降順を指定する(昇順:小▷大、降順:大▷小)
     */
//    ,Check_Strength :({is:true ,Order_by:"ADX_14" ,ASC_DESC:"DESC"})
    ,Check_Strength :({is:false})
    
    /**
     * PLフィルターの設定
     * その銘柄の直前のトレードが利益だった場合はトレードを控える
     * N比で何倍以上の利益をPLフィルターの対象とするか
     */
//    ,PL_FILTER : ({is:true ,N:5 ,ChgPctN_Value:true ,ChgPctATR:true})
    ,PL_FILTER : ({is:false})
  };
  
  /**
   * 計算する項目や計算方法等を指定する
   */
  var WORK = {
    
    /**
     * あらかじめ計算したものを格納する
     */
    Techs  :{FT_ID:GV.FT.Techs.ID  ,ar:[] ,ar_forCalc:[]}
    ,Correl:{
      FT_ID:(function(){return ST.Correl_Risk===true ? GV.FT.Correl.ID : ""})() // FusionTablesのIDがなければ読み込まれない
      ,ar:[]
    }
    
    /**
     * ユニット計算表
     * -- 毎朝、前日の値動きを取得してその日の計算をする計算表。当日のトレード用の計算値を算出している。
     * -- データ構造 (ar > year > 銘柄 > 日付 > 項目(ITM)) 
     */
    ,Units :{ar:[]
             ,SH:(function(){
               var obj = {} ,num;
               for(num in GV.SS.Units){
                 obj[num] = SpreadsheetApp.openById(GV.SS.Units[num].ID).getSheetByName(GV.SS.Units[num].SHEET);
               }
               return obj;
             })()
             
             /**
              * 前日の値動きにもとづくユニットやシグナルの計算
              * ユニットに格納される価格や計算値はほとんどが前日のもの
              */
             ,ITM:{
               Date      : function(){_.u[_.ul(itm)] = _.t[_.tl(itm)];}
               ,SymNum   : function(){_.u[_.ul(itm)] = GV.SYM[sym].C;}
               ,Symbol   : function(){_.u[_.ul(itm)] = sym;}
               ,Lot      : function(){_.u[_.ul(itm)] = GV.SYM[sym].L;}
               // 前日の価格(計算のもとになっている価格。実際の計算はTechsから)
               ,Open     : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,High     : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,Low      : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,Close    : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               // 当日の価格(チャート上にエントリーやイグジットを表示するときは、この価格を使わないと先読みしてエントリーしたように見えてしまう。しかしこの価格とテクニカルを重ねるとテクニカルが前日のものであるからまたずれてしまう。。)
               ,Open_forChart  : function(){_.u[_.ul(itm)] = (_.t[_.tl("Open")]-0);}
               ,High_forChart  : function(){_.u[_.ul(itm)] = (_.t[_.tl("High")]-0);}
               ,Low_forChart   : function(){_.u[_.ul(itm)] = (_.t[_.tl("Low")]-0);}
               ,Close_forChart : function(){_.u[_.ul(itm)] = (_.t[_.tl("Close")]-0);}
               ,Currency : function(){ // 基軸通貨。円の場合は「1」を返す。
                 _.u[_.ul(itm)] = (function(){
                   function switch_(arg){
                     function basis(){
                       if(_.first) return T.ar_forCalc[year][arg][day][_.tl("Close")]-0;
                       if(year>0 && day===0) return T.ar_forCalc[year-1][arg][T.ar_forCalc[year-1][arg].length-1][_.tl("Close")]-0;
                       return T.ar_forCalc[year][arg][day-1][_.tl("Close")]-0;
                     }
                     var cases = {
                       "USDJPY_cent": function(){
                         var currency_price = _.first ? T.ar[year]["USDJPY"][day][_.tl("Close")]-0
                         : year>0 && day===0 ? T.ar[year-1]["USDJPY"][T.ar[year-1]["USDJPY"].length-1][_.tl("Close")]-0
                         : T.ar[year]["USDJPY"][day-1][_.tl("Close")]-0
                         ;
                         return CALC.roundN(currency_price /100 ,4);
                       }
                       ,"USDJPY": function(){
                         if(_.first) return T.ar[year][arg][day][_.tl("Close")]-0;
                         if(year>0 && day===0) return T.ar[year-1][c][T.ar[year-1][arg].length-1][_.tl("Close")]-0;
                         return T.ar[year][arg][day-1][_.tl("Close")]-0;
                       }
                       ,"EURJPY": function(){basis();}
                       ,"HKDJPY": function(){basis();}
                       ,_default: function(){return 1;}
                     };
                     return (Object.hasOwnProperty.call(cases,arg) && cases[arg] || cases._default)();
                   };
                   return switch_(GV.SYM[sym].currency);
                 })();
               }
               ,ATR20       : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,High10      : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,Low10       : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,H_2ATR      : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,L_2ATR      : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,High20      : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,Low20       : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,SMA20       : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,EMA5        : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,EMA6        : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,EMA19       : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,EMA20       : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,EMA25       : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,EMA40       : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,EMA50       : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,EMA100      : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,EMA150      : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,EMA200      : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,EMA250      : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,EMA300      : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,EMA350      : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,Filter      : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,FilStrength : function(){_.u[_.ul(itm)] = _.first ? "" : CALC.roundN((_.u[_.ul("Filter")]-0) /(_.u[_.ul("ATR20")]-0) ,GV.SYM[sym].R);}
//               ,Filter_C_300  : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,Filter_50_300 : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,Filter_25_350 : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,BB_plus_1   : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,BB_plus_2   : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,BB_plus_2_5 : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,BB_plus_3   : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,BB_minus_1  : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,BB_minus_2  : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,BB_minus_2_5: function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,BB_minus_3  : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,ATR_band_h  : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,ATR_band_l  : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,MACD        : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,MACD5_20    : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,MACD5_20_2  : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,MACD_6_19   : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,MACD2_6_19  : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,MACD5_40    : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,MACD5_40_2  : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,MACD_20_40   : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,MACD20_40_2 : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,MACD50_300  : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
//               ,MACD50_300_2: function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,plus_DI_14  : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,minus_DI_14 : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,DX          : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,ADX_14      : function(){_.u[_.ul(itm)] = _.first ? "" : (_.t_[_.tl(itm)]-0);}
               ,EntrySig : function(){
                 _.u[_.ul("EntrySig")] = (function(){ // Entry Signal スキームの切り替え
                   if(_.first) return "";
                   var entry_sig_is = ST.EntrySig.is;
                   
                   function switch_(arg){
                     var cases = {
                       "Target": function(){
                         return _.t_[_.tl(ST.EntrySig[1])]-0
                       }
                       ,"doubleEMA": function(){
                         var
                          s  = _.u[_.ul(ST.EntrySig[1])]  ,m  = _.u[_.ul(ST.EntrySig[2])]
                         ,s_ = _.u_[_.ul(ST.EntrySig[1])] ,m_ = _.u_[_.ul(ST.EntrySig[2])]
                         ,slopeL = (ST.EntrySig.SLOPE === false ? true : ST.EntrySig.SLOPE === true && s_ <  && m_ < m ? true : false)
                         ,slopeS = (ST.EntrySig.SLOPE === false ? true : ST.EntrySig.SLOPE === true && s_ > s && m_ > m ? true : false)
                         ,buy_sell = ST.EntrySig.BUY_SELL
                         ;
                         if ( s == null || m == null ) throw new Error("EntrySig 必要な計算値が読み込まれていません");
                         if ( ST.EntrySig.SIG === "Over" ) {
                           if ( m < s && slopeL ) return 1;
                           if ( s < m && slopeS ) return -1;
                         }
                         if ( ST.EntrySig.SIG==="Cross" ) {
                           if ( s_ <= m_ && m < s && slopeL ) return 1;
                           if ( m_ <= s_ && s < m && slopeS ) return -1;
                         }
                         return 0;
                       }
                       ,"TripleEMA": function(){
                         var
                          s=_.u[_.ul(ST.EntrySig[1])]   ,m=_.u[_.ul(ST.EntrySig[2])]   ,l=_.u[_.ul(ST.EntrySig[3])]
                         ,s_=_.u_[_.ul(ST.EntrySig[1])] ,m_=_.u_[_.ul(ST.EntrySig[2])] ,l_=_.u_[_.ul(ST.EntrySig[3])]
                         ;
                         if(s==null || m==null || l==null) throw new Error("EntrySig 必要な計算値が読み込まれていません");
                         if(((l_>=s_ && s_>=m_)||(s_>=l_ && l_>=m_)) && s>=m && m>=l) return 1;
                         if(((m_>=s_ && s_>=l_)||(m_>=l_ && l_>=s_)) && l>=m && m>=s) return -1;
                         return 0;
                       }
                       ,"FilteredHL": function(){
                         var
                          fil=_.u[_.ul(ST.EntrySig.Filter)] 
                         ,h  =_.u[_.ul("High")]         ,l  =_.u[_.ul("Low")]
                         ,hN_=_.u_[_.ul(ST.EntrySig.H)] ,lN_=_.u_[_.ul(ST.EntrySig.L)]
                         ;
                         if(hN_==null || lN_==null) throw new Error("EntrySig 必要な計算値が読み込まれていません");
                         if(fil>0 && h>hN_) return 1;
                         if(fil<0 && l<lN_) return -1;
                         return 0;
                       }
                       ,"FilteredMACD": function(){
                         var fil=_.u[_.ul(ST.EntrySig.Filter)] ,macd2=_.u[_.ul(ST.EntrySig[1])];
                         if(fil==null || macd2==null) throw new Error("EntrySig 必要な計算値が読み込まれていません");
                         if(ST.EntrySig.SIG==="Over"){
                           if(fil>0 && macd2>0) return 1;
                           if(fil<0 && macd2<0) return -1;
                         }
                         if(ST.EntrySig.SIG==="Cross"){
                           var macd2_=_.u_[_.ul(ST.EntrySig[1])];
                           if(!GU._is("Number" ,macd2_))     return 0;
                           if(fil>0 && macd2_<=0 && macd2>0) return 1;
                           if(fil<0 && macd2_>=0 && macd2<0) return -1;
                         }
                         return 0;
                       }
                       ,_default: function(){
                         throw new Error("EntrySig が正しく設定されていません。");
                       }
                     };
                     return (Object.hasOwnProperty.call(cases,arg) && cases[arg] || cases._default)();
                   };
                   return switch_(entry_sig_is);
                 })();
               }
               /**
                * イグジットシグナル
                * -- 前日のユニット計算表で算出された計算値をもとに、イグジットシグナルを判断する
                * -- また、前日のExitの計算値は、前日のExitPriceで計算するべきだが、ここで算出している(スキーム追加時の編集箇所が1箇所で済む)
                * -- 前日のユニット計算表に入っているテクニカルの値は、前々日の値動きをもとに計算されたもの。前日のExitPriceを計算するには、前日のユニット計算表のテクニカルの値を使えば良い
                */
               ,ExitSig :function(){
                 _.u[_.ul("ExitSig")] = (function(){
                   if(_.first || ST.ExitSig.is===false || _.u_[_.ul("EntryPrice")]==0) return "";
                   var
                    exit_sig_is = ST.ExitSig.is
                   ,ls_ = _.u_[_.ul("LongShort")]
                   ,h   = _.u[_.ul("High")]
                   ,l   = _.u[_.ul("Low")]
                   ;
                   function switch_(arg){
                     var cases = {
                       "CrossEMA": function(){
                         _.u_[_.ul("ExitPrice")] = _.t[_.tl("Close")]-0; //売買履歴の確認用
                         var s=_.u[_.ul(ST.ExitSig[1])] ,s_=_.u_[_.ul(ST.ExitSig[1])] ,m=_.u[_.ul(ST.ExitSig[2])] ,m_=_.u_[_.ul(ST.ExitSig[2])];
                         if(s==null || m==null) throw new Error("ExitSig 必要な計算値が読み込まれていません");
                         if(ls_===1  && s_>m_ && m>s) return 1;
                         if(ls_===-1 && m_>s_ && s>m) return 1;
                         return 0;
                       }
                       ,"Target": function(){
                         if(_.u_[_.ul(ST.ExitSig[1])]==null) throw new Error("ExitSig 必要な計算値が読み込まれていません");
                         _.u_[_.ul("ExitPrice")] = _.u_[_.ul(ST.ExitSig[1])]-0; //売買履歴の確認用
                         if(ls_===1  && _.u_[_.ul("ExitPrice")] >l) return 1;
                         if(ls_===-1 && _.u_[_.ul("ExitPrice")] <h) return 1;
                         return 0;
                       }
                       ,"HL": function(){
                         if(_.u_[_.ul(ST.ExitSig.L)]==null || _.u_[_.ul(ST.ExitSig.H)]==null) throw new Error("ExitSig 必要な計算値が読み込まれていません");
                         _.u_[_.ul("ExitPrice")] = ls_===1 ? _.u_[_.ul(ST.ExitSig.L)]-0 : ls_===-1 ? _.u_[_.ul(ST.ExitSig.H)]-0 : "";
                         if(ls_===1  && _.u_[_.ul("ExitPrice")] >l) return 1;
                         if(ls_===-1 && _.u_[_.ul("ExitPrice")] <h) return 1;
                         return 0;
                       }
                       ,_default: function(){
                         throw new Error("ExitSig が正しく設定されていません。");
                       }
                     };
                     return (Object.hasOwnProperty.call(cases,arg) && cases[arg] || cases._default)();
                   };
                   return switch_(exit_sig_is);
                 })();
               }
               ,SO_Sig : function(){
                 _.u[_.ul("SO_Sig")] = (function(){
                   if(_.first || ST.SO.is===false || _.u_[_.ul("EntryPrice")]==0) return "";
                   var ls_=_.u_[_.ul("LongShort")] ,so_=_.u_[_.ul("SO")] ,h=_.u[_.ul("High")] ,l=_.u[_.ul("Low")];
                   if(ls_===1  && so_>l) return 1;
                   if(ls_===-1 && so_<h) return 1;
                   return 0;
                 })();
               }
               ,ExitSig_N : function(){
                 _.u[_.ul("ExitSig_N")] = (function(){
                   if(ST.Exit_N.is===false) return "";
                   if(_.first || _.u_[_.ul("EntryPrice")]=="") return 0;
                   if(ST.Exit_N.ChgPctN_Value===true && _.u_[_.ul("ChgPctN_Value")] <ST.Exit_N.N *-1) return 1;
                   if(ST.Exit_N.ChgPctATR    ===true && _.u_[_.ul("ChgPctATR")]     <ST.Exit_N.N *-1) return 1;
                   return 0;
                 })();
               }
               ,TimedSig :function(){
                 _.u[_.ul("TimedSig")] = (function(){
                   if(_.first || ST.Timed_Exit.is===false || _.u_[_.ul("EntryPrice")]=="") return "";
                   var ls_=_.u_[_.ul("LongShort")] ,ep_=_.u_[_.ul("EntryPrice")];
                   if(_.u_[_.ul("NumDays")] >=ST.Timed_Exit.N){
                     if(ST.Timed_Exit.ComparePrice===false) return 1;
                     if(ST.Timed_Exit.ComparePrice===true){
                       if(ls_===1  && ep_>_.t_[_.tl("Close")]) return 1;
                       if(ls_===-1 && ep_<_.t_[_.tl("Close")]) return 1;
                     }
                   }
                   return 0;
                 })();
               }
               ,ReversingEntrySig :function(){
                 _.u[_.ul("ReversingEntrySig")] = (function(){
                   if(_.first || ST.ReversingEntry.is===false || _.u_[_.ul("EntryPrice")]=="") return "";
                   var ls_=_.u_[_.ul("LongShort")] ,es=_.u[_.ul("EntrySig")] ,es_=_.u_[_.ul("EntrySig")];
                   if(ls_===1  && es_===0 && es===-1) return 1;
                   if(ls_===-1 && es_===0 && es===1)  return 1;
                   return 0;
                 })();
               }
               ,PrevProfitSig :function(){
                 _.u[_.ul("PrevProfitSig")] = (function(){
                   if(ST.PL_FILTER.is===false) return "";
                   if(_.first) return 0;
                   return _.u_[_.ul("PrevProfitSig")];
                 })();
               }
               ,LO2_Sig :function(){
                 _.u[_.ul("LO2_Sig")] = (function(){
                   if(_.first || ST.LO.is===false || _.u_[_.ul("LO2")]=="") return "";
                   var lo2_Sig_ = _.u_[_.ul("LO2_Sig")];
                   function calcLO2_Sig(){
                     if(_.u_[_.ul("LongShort")]===1  && _.u_[_.ul("LO2")] <(_.u[_.ul("High")]-0)) return 1;
                     if(_.u_[_.ul("LongShort")]===-1 && _.u_[_.ul("LO2")] >(_.u[_.ul("Low")]-0))  return 1;
                     return 0;
                   }
                   if(ST.LO.SIG==="Over") return calcLO2_Sig(); 
                   if(ST.LO.SIG==="Cross"){
                     if(lo2_Sig_-0===0) return calcLO2_Sig();
                     if(lo2_Sig_===1 || lo2_Sig_===-1) return -1;
                     throw new Error("LO_Sig 想定外のケースです。計算に問題がないか確認をしてください。");
                   }
                 })();
               }
               ,LO3_Sig :function(){
                 _.u[_.ul("LO3_Sig")] = (function(){
                   if(_.first || ST.LO.is===false || _.u_[_.ul("LO3")]=="") return "";
                   var lo3_Sig_ = _.u_[_.ul("LO3_Sig")];
                   function calcLO3_Sig(){
                     if(_.u_[_.ul("LongShort")]===1  && _.u_[_.ul("LO3")] <(_.u[_.ul("High")]-0)) return 1;
                     if(_.u_[_.ul("LongShort")]===-1 && _.u_[_.ul("LO3")] >(_.u[_.ul("Low")]-0)) return 1;
                     return 0;
                   };
                   if(ST.LO.SIG==="Over") return calcLO3_Sig(); 
                   if(ST.LO.SIG==="Cross"){
                     if(lo3_Sig_-0===0) return calcLO3_Sig();
                     if(lo3_Sig_===1 || lo3_Sig_===-1) return -1;
                     throw new Error("LO_Sig 想定外のケースです。計算に問題がないか確認をしてください。");
                   }
                 })();
               }
               ,LO4_Sig :function(){
                 _.u[_.ul("LO4_Sig")] = (function(){
                   if(_.first || ST.LO.is===false || _.u_[_.ul("LO4")]=="") return "";
                   var lo4_Sig_ = _.u_[_.ul("LO4_Sig")];
                   function calcLO4_Sig(){
                     if(_.u_[_.ul("LongShort")]===1  && _.u_[_.ul("LO4")] <(_.u[_.ul("High")]-0)) return 1;
                     if(_.u_[_.ul("LongShort")]===-1 && _.u_[_.ul("LO4")] >(_.u[_.ul("Low")]-0))  return 1;
                     return 0;
                   };
                   if(ST.LO.SIG==="Over") return calcLO4_Sig(); 
                   if(ST.LO.SIG==="Cross"){
                     if(lo4_Sig_-0===0) return calcLO4_Sig();
                     if(lo4_Sig_===1 || lo4_Sig_===-1) return -1;
                     throw new Error("LO_Sig 想定外のケースです。計算に問題がないか確認をしてください。");
                   }
                 })();
               }
               // Unit の保有状況
               ,EntryDate     : function(){_.u[_.ul(itm)] = _.first ? "" : _.u_[_.ul("EntryDate")] !=""    ? _.u_[_.ul("EntryDate")]      : "";}
               ,EntryPrice    : function(){_.u[_.ul(itm)] = _.first ? "" : _.u_[_.ul("EntryPrice")]!=""    ? _.u_[_.ul("EntryPrice")]     : "";}
               ,EntryAvgPrice : function(){_.u[_.ul(itm)] = _.first ? "" : _.u_[_.ul("EntryAvgPrice")]!="" ? _.u_[_.ul("EntryAvgPrice")]  : "";}
               ,LongShort     : function(){_.u[_.ul(itm)] = _.first ? "" : _.u_[_.ul("LongShort")] !=""    ? _.u_[_.ul("LongShort")]      : "";}
               ,N_Status      : function(){_.u[_.ul(itm)] = _.first ? "" : _.u_[_.ul("N_Status")]  !=""    ? _.u_[_.ul("N_Status")]       : "";} // 1u 2u 3u 4u
               ,Pos           : function(){_.u[_.ul(itm)] = _.first ? 0  : _.u_[_.ul("Pos")]       >0      ? _.u_[_.ul("Pos")]            : 0;}  // 枚数
               ,NumDays       : function(){_.u[_.ul(itm)] = _.first ? 0  : _.u_[_.ul("EntryPrice")]>0      ? _.u_[_.ul("NumDays")]-0+1    : 0;}
               ,N_Size :function(){
                 _.u[_.ul("N_Size")] = (function(){
                   if(_.first) return 0;
                   var n_value = _.f[_.fl("N_Value")]==null ? _.f_[_.fl("N_Value")] : _.f[_.fl("N_Value")];
                   return n_value <=0 ? 0 : CALC.roundN(n_value /(GV.SYM[sym].L *_.u[_.ul("ATR20")] *_.u[_.ul("Currency")]) ,0);
                 })();
               }
               ,N :function(){
                 _.u[_.ul("N")] = (function(){
                   if(_.first || _.u[_.ul("Pos")]===0 || _.u[_.ul("N_Size")] <=0) return 0;
                   if(ST.N_Calc==="Correct") return CALC.roundN(_.u[_.ul("Pos")] /_.u[_.ul("N_Size")] ,1);
                   if(ST.N_Calc==="Simple")  return _.u_[_.ul("N")];
                   throw new Error("Units:N 想定外のケースです。計算に問題がないか確認をしてください。");
                 })();
               }  // Unit数
               ,RedRisk       : function(){_.u[_.ul(itm)] = _.first ? 0 : _.u_[_.ul("RedRisk")]      >0 ? _.u_[_.ul("RedRisk")]      : 0;}
               ,YellowRisk    : function(){_.u[_.ul(itm)] = _.first ? 0 : _.u_[_.ul("Yellowrisk")]   >0 ? _.u_[_.ul("YellowRisk")]   : 0;}
               ,NominalValue  : function(){_.u[_.ul(itm)] = _.first ? 0 : _.u_[_.ul("NominalValue")] >0 ? _.u_[_.ul("NominalValue")] : 0;}
               ,PL            : function(){_.u[_.ul(itm)] = 0;}
               ,Comm          : function(){_.u[_.ul(itm)] = 0;}
               ,MTM           : function(){_.u[_.ul(itm)] = 0;}
               ,ChgPctATR     : function(){_.u[_.ul("ChgPctATR")]     = _.u[_.ul("EntryPrice")]==="" ? "" : CALC.roundN(((_.u[_.ul("Close")] -_.u[_.ul("EntryPrice")]) *_.u[_.ul("LongShort")]) /_.u[_.ul("ATR20")] ,3);}
               ,ChgPctN_Value : function(){_.u[_.ul("ChgPctN_Value")] = _.u[_.ul("EntryPrice")]==="" ? "" : 0;}
               
               /**
                * 次営業日に向けたSOとLOの計算値
                * -- その日のATRで計算し直す
                * -- 当初のLOをそのまま引き継ぐのもありかも
                */
               ,LO2 :function(new_b4){
                 _.u[_.ul("LO2")] = (function(){
                   var target_price = _.u[_.ul(ST.LO[1])];
                   if(target_price==null) throw new Error("LO 必要な計算値が読み込まれていません。");
                   if(_.first || ST.LO.is===false || target_price=="" || _.u[_.ul("N_Status")].substr(0,1)-0 >=2) return "";
                   
                   function calcLO2(){
                     if(ST.LO.is==="Turtle"){
                       var range = _.u[_.ul(ST.LO[2])];
                       if(range==null) throw new Error("LO 必要な計算値が読み込まれていません。");
                       return CALC.roundN(target_price +(range *ST.LO.N *_.u[_.ul("LongShort")]) ,GV.SYM[sym].R);
                     }
                   }
                   if(new_b4==="New")             return calcLO2();
                   if(new_b4==="B4")              return _.u[_.ul("LO2")];
                   if(_.u[_.ul("LO2")]==null)     return "later";
                   if(_.u[_.ul("LO2")]==="later") return ST.LO.CALC==="Fix" && _.u_[_.ul("LO2")] >0 ? _.u_[_.ul("LO2")] : calcLO2();
                   throw new Error("calcLO 想定外のケースです。計算に問題がないか確認をしてください。");
                 })();
               }
               ,LO3 :function(new_b4){
                 _.u[_.ul("LO3")] = (function(){
                   var target_price = _.u[_.ul(ST.LO[1])];
                   if(target_price==null) throw new Error("LO 必要な計算値が読み込まれていません。");
                   if(_.first || ST.LO.is===false || target_price=="" || _.u[_.ul("N_Status")].substr(0,1)-0 >=3) return "";
                   
                   function calcLO3(){
                     if(ST.LO.is==="Turtle"){
                       var range = _.u[_.ul(ST.LO[2])];
                       if(range==null) throw new Error("LO 必要な計算値が読み込まれていません。");
                       return CALC.roundN(target_price +(range *ST.LO.N *2 *_.u[_.ul("LongShort")]) ,GV.SYM[sym].R);
                     }
                   }
                   if(new_b4==="New")             return calcLO3();
                   if(new_b4==="B4")              return _.u[_.ul("LO3")];
                   if(_.u[_.ul("LO3")]==null)     return "later";
                   if(_.u[_.ul("LO3")]==="later") return ST.LO.CALC==="Fix" && _.u_[_.ul("LO3")] >0 ? _.u_[_.ul("LO3")] : calcLO3();
                   throw new Error("calcLO 想定外のケースです。計算に問題がないか確認をしてください。");
                 })();
               }
               ,LO4 :function(new_b4){
                 _.u[_.ul("LO4")] = (function(){
                   var target_price = _.u[_.ul(ST.LO[1])];
                   if(target_price==null) throw new Error("LO 必要な計算値が読み込まれていません。");
                   if(_.first || ST.LO.is===false || target_price=="" || _.u[_.ul("N_Status")].substr(0,1)-0 >=4) return "";
                   
                   function calcLO4(){
                     if(ST.LO.is==="Turtle"){
                       var range = _.u[_.ul(ST.LO[2])];
                       if(range==null) throw new Error("LO 必要な計算値が読み込まれていません。");
                       return CALC.roundN(target_price +(range *ST.LO.N *3 *_.u[_.ul("LongShort")]) ,GV.SYM[sym].R);
                     }
                   }
                   if(new_b4==="New")             return calcLO4();
                   if(new_b4==="B4")              return _.u[_.ul("LO4")];
                   if(_.u[_.ul("LO4")]==null)     return "later";
                   if(_.u[_.ul("LO4")]==="later") return ST.LO.CALC==="Fix" && _.u_[_.ul("LO4")] >0 ? _.u_[_.ul("LO4")] : calcLO4();
                   throw new Error("calcLO 想定外のケースです。計算に問題がないか確認をしてください。");
                 })();
               }
               ,SO :function(new_b4){
                 _.u[_.ul("SO")] = (function(){
                   if(_.first || ST.SO.is===false || _.u[_.ul("EntryPrice")]=="") return "";
                   var calcSo = function(){
                     if(ST.SO.is==="Turtle"){
                       var range = _.u[_.ul(ST.SO[1])];
                       if(range==null) throw new Error("SO 必要な計算値が読み込まれていません。");
                       return CALC.roundN(_.tp[_.tp.length-1][_.rl("Price")] -(range *ST.SO.N *_.u[_.ul("LongShort")]) ,GV.SYM[sym].R);
                     }
                   };
                   if(new_b4==="New")            return calcSo();
                   if(new_b4==="B4")             return _.u[_.ul("SO")];
                   if(_.u[_.ul("SO")]==null)     return "later";
                   if(_.u[_.ul("SO")]==="later") return ST.SO.CALC==="Fix" && _.u_[_.ul("SO")] >0 ? _.u_[_.ul("SO")] : calcSo();
                   throw new Error("calcSO 想定外のケースです。計算に問題がないか確認をしてください。");
                 })();
               }
               // 逆指値注文
               ,ExitPrice :function(){
                 _.u[_.ul("ExitPrice")] = (function(){
                   // 本来はここで当日のExitPriceを設定するが、翌日のExitSigでまとめて算出することに
                   return "";
                 })();
               }
               ,Log :function(){
                 _.u[_.ul("Log")] = (function(){
                   if(_.u[_.ul("Log")]==null) return "log";
                   if(_.u[_.ul("Log")]==="log"){
                     TP.log[sym] = [];
                     return JSON.stringify(_.log.concat());
                   }
                 })();
               }
               ,TradingPos :function(){
                 _.u[_.ul("TradingPos")] = (function(){
                   if(_.u[_.ul("TradingPos")]==null)   return "log";
                   if(_.u[_.ul("TradingPos")]==="log") return JSON.stringify(_.tp.concat());
                 })();
               }
             }
             /**
              * イグジット処理
              */
             ,Exit :function(){if(!_.first){
               var ExitSig = (function(){
                 if(_.u[_.ul("ExitSig")]===1)   return "Exit_Tech";
                 if(_.u[_.ul("SO_Sig")]===1)    return "Exit_SO";
                 if(_.u[_.ul("ExitSig_N")]===1) return "Exit_N";
                 if(_.u[_.ul("TimedSig")]===1)  return "Exit_Timed";
                 if(_.u[_.ul("ReversingEntrySig")]===1) return "Exit_Reversing";
                 return false;
               })();
               if(ExitSig){
                 for(_.tp_exit=0 ,_.tpLen=_.tp.length; _.tp_exit<_.tpLen; _.tp_exit++){
                   _.r_last = _.r1.push([]) -1;
                   
                   for(itm in R.ITM) R.ITM[itm](ExitSig);
                   
                   TP.Sum_PL = TP.Sum_PL   -0+_.r1[_.r_last][_.rl("PL")];
                   TP.Sum_Comm = TP.Sum_Comm -0+_.r1[_.r_last][_.rl("Comm")];
                   _.u[_.ul("PL")] = _.u[_.ul("PL")]   -0+_.r1[_.r_last][_.rl("PL")];
                   _.u[_.ul("Comm")] = _.u[_.ul("Comm")] -0+_.r1[_.r_last][_.rl("Comm")];

                   if(_.tp_exit ==_.tpLen -1 && ST.PL_FILTER.is===true){
                     if(ST.PL_FILTER.ChgPctN_Value===true && _.r1[_.r_last][_.rl("ChgPctN_Value")] >ST.PL_FILTER.N) _.u[_.ul("PrevProfitSig")] = 1;
                     if(ST.PL_FILTER.ChgPctATR    ===true && _.r1[_.r_last][_.rl("ChgPctATR")]     >ST.PL_FILTER.N) _.u[_.ul("PrevProfitSig")] = 1;
                   }
                   _.log.push("[EXIT:"+_.r1[_.r_last].concat()+"]");
                 }

                 _.u[_.ul("EntryDate")]     = "";
                 _.u[_.ul("EntryPrice")]    = "";
                 _.u[_.ul("EntryAvgPrice")] = "";
                 _.u[_.ul("LongShort")]     = "";
                 _.u[_.ul("N_Status")]      = "";
                 _.u[_.ul("Pos")]           = 0;
                 _.u[_.ul("N")]             = 0;
                 _.u[_.ul("NominalValue")]  = 0;
                 _.u[_.ul("NumDays")]       = 0;
                 _.u[_.ul("ChgPctATR")]     = "";
                 _.u[_.ul("ChgPctN_Value")] = "";

                 if(_.u[_.ul("LongShort")]===1)  F.ITM["LongN"]();
                 if(_.u[_.ul("LongShort")]===-1) F.ITM["ShortN"]();
                 F.ITM["TotalN"]();
                 F.ITM["SumNominalValue"]();

                 if(ST.Correl_Risk===true) U.CalcCorrelRisk();

                 TP.ar[sym] = []; // TradingPosの初期化
                 _.tp = TP.ar[sym];
               }
             }}
             /**
              * エントリー処理
              * -- B4:前日の値動きにもとづくエントリー処理     -- N は前日のものを使用する
              * -- New:当日のシグナルにもとづくエントリー処理  -- N はその日のものを使用する
              */
             ,Entry : function( new_b4 ){ if( !_.first ) {
               var sig = _.u[_.ul("EntrySig")];
               if ( _.u[_.ul("N_Size")] > 0 ) {
                 var 
                 not_over = function() {
                   if ( _.u[_.ul("N")]-0+1                    > ST.LIMIT_SINGLE ) return false;
                   if ( _.f[_.fl("TotalN")]-0+1               > ST.LIMIT_TOTAL )  return false;
                   if ( sig ===  1 && _.f[_.fl("LongN")]-0+1  > ST.LIMIT_LONG )   return false;
                   if ( sig === -1 && _.f[_.fl("ShortN")]-0+1 > ST.LIMIT_SHORT )  return false;
                   if ( ST.Correl_Risk === true && _.u[_.ul("RedRisk")]-0+1    > ST.LIMIT_RED )    return false;
                   if ( ST.Correl_Risk === true && _.u[_.ul("YellowRisk")]-0+1 > ST.LIMIT_YELLOW ) return false;
                   if ( _.u[_.ul("N_Status")] === ST.LIMIT_N_Status ) return false;
                   if ( ST.Filter_CorrelRed === true && new_b4 === "New" ) { 
                     for ( var sym_ in GV.SYM ) {
                       if ( sym_ !== sym && U.ar[year][sym_][day][_.ul("EntryPrice")] > 0 ) {
                         var
                          symLS       = _.u[_.ul("LongShort")]
                         ,sym_LS      = U.ar[year][sym_][day][_.ul("LongShort")]
                         ,sym_NumDays = U.ar[year][sym_][day][_.ul("NumDays")]
                         ,stNumDays   = ST.NumDays_Same_Factor
                         ,crrl        = C.ar[year][sym][week][_.cl(sym_)]
                         ;
                         if ( crrl >= ST.LIMIT_RED     && symLS *sym_LS == 1  && sym_NumDays <= stNumDays ) return false;
                         if ( crrl <= ST.LIMIT_RED *-1 && symLS *sym_LS == -1 && sym_NumDays <= stNumDays ) return false;
                       }
                     }
                   }
                   if ( ST.PL_FILTER.is === true && _.u[_.ul("PrevProfitSig")] === 1 ) {
                     _.u[_.ul("PrevProfitSig")] = 0;
                     return false;
                   }
                   if ( ST.Leverage.is === true ) {
                     var
                      leverage = ST.Leverage.N
                     ,net_asset = (_.f[_.fl("NetAssets")]==null ? _.f_[_.fl("NetAssets")] : _.f[_.fl("NetAssets")])
                     ;
                     var pre_nominal_value = (function() {
                       return CALC.roundN( (GV.SYM[sym].L-0) *(_.u[_.ul("N_Size")]-0) *(_.u[_.ul("Close")]-0) *(_.u[_.ul("Currency")]-0) ,0 );
                     });
                     if ( _.f[_.fl("SumNominalValue")] -0+pre_nominal_value > net_asset *leverage ) {
                       return false;
                     }
                   }
                   return true;
                 }
                 ,calc_n = function() {
                   if ( _.u[_.ul("N_Size")] === 0 ) _.u[_.ul("N")] = 0;
                   if ( ST.N_Calc === "Simple" )    _.u[_.ul("N")] = _.u[_.ul("N")] -0+1;
                   if ( ST.N_Calc === "Correct" )   U.ITM["N"]();
                 }
                 ,other_tasks = function() {
                   calc_n();
                   if ( sig === 1 )  F.ITM["LongN"]();
                   if ( sig === -1 ) F.ITM["ShortN"]();
                   F.ITM["TotalN"]();
                   F.ITM["SumNominalValue"]();
                   
                   if ( ST.Correl_Risk === true ) U.CalcCorrelRisk();
                   
                   _.tp.push( _.r0[_.r_last].concat() );
                   U.ITM.SO( new_b4 ) ,U.ITM.LO2( new_b4 ) ,U.ITM.LO3( new_b4 ) ,U.ITM.LO4( new_b4 );
                   //シグナルが出た日の終値でエントリーする場合は不要。シグナルが出るのがほぼ確定したことを確認して前日の終値間際でエントリーする場合はいる。
                   
                   _.log.push( "[ENTRY:"+_.r0[_.r_last].concat()+"]" );
                 }
                 ;
                 if ( new_b4 === "B4" && _.u[_.ul("EntryPrice")] > 0 ) {
                   sig = _.u[_.ul("LongShort")];
                   if ( _.u[_.ul("LO2_Sig")] === 1 ) { if ( not_over() ) { _.r_last = _.r0.push([]) -1; for ( itm in R.ITM ) { R.ITM[itm]("Entry_LO2"); } other_tasks(); } }
                   if ( _.u[_.ul("LO3_Sig")] === 1 ) { if ( not_over() ) { _.r_last = _.r0.push([]) -1; for ( itm in R.ITM ) { R.ITM[itm]("Entry_LO3"); } other_tasks(); } }
                   if ( _.u[_.ul("LO4_Sig")] === 1 ) { if ( not_over() ) { _.r_last = _.r0.push([]) -1; for ( itm in R.ITM ) { R.ITM[itm]("Entry_LO4"); } other_tasks(); } }
                   if ( _.u[_.ul("N_Status")] === _.u_[_.ul("N_Status")] ) {
                     if ( ST.N_Calc === "Correct" ) calc_n();
                     if ( ST.Correl_Risk === true ) U.CalcCorrelRisk();
                   }
                 }
                 if ( new_b4 === "New" ) {
                   var check_limit_n_status_and_entry
                   = ST.LIMIT_N_Status === "4u" && _.u[_.ul("EntryPrice")] === "" ? true
                   : ST.LIMIT_N_Status === false ? true
                   : false
                   ;
                   if (check_limit_n_status_and_entry) {
                     var
                      entry_is = ST.Entry.is
                     ,entry_buy_sell = ST.Entry.BUY_SELL
                     ,entry_sig_bool
                     ;
                     function switch_( arg ){
                       var cases = {
                         "AllSig": function() {
                           if ( sig === 1  && (entry_buy_sell === "Buy"  || entry_buy_sell === "Both") ) return true;
                           if ( sig === -1 && (entry_buy_sell === "Sell" || entry_buy_sell === "Both") ) return true;
                           return false;
                         }
                         ,"NewSig": function() {
                           var sig_ = _.u_[_.ul("EntrySig")];
                           if ( sig === 1  && ( sig_ === 0 || sig_ === -1 ) && (entry_buy_sell === "Buy"  || entry_buy_sell === "Both") ) return true;
                           if ( sig === -1 && ( sig_ === 0 || sig_ === 1 )  && (entry_buy_sell === "Sell" || entry_buy_sell === "Both") ) return true;
                           return false;
                         }
                         ,_default: function() {
                           throw new Error( "Entry が正しく設定されていません。" );
                         }
                       };
                       return ( Object.hasOwnProperty.call(cases,arg) && cases[arg] || cases._default )();
                     };
                     entry_sig_bool = switch_( entry_is );
                     if ( entry_sig_bool ) {
                       if ( not_over() ) {
                         _.r_last = _.r0.push([]) -1;
                         for ( itm in R.ITM ) R.ITM[itm]( "Entry_New" );
                         other_tasks();
                       }
                     }
                   }
                 }
               }
             }}
             /**
              * 値洗い
              */
             ,MarkToMarket :function(){if(!_.first && _.tp.length >0){
               for(_.mtm=0; _.mtm<_.tp.length; _.mtm++){ //保有ポジションの数、計算処理を行う。
                 R.ITM.PL("Mark_to_Market");
                 TP.Sum_MTM       = TP.Sum_MTM       -0+_.tp[_.mtm][_.rl("PL")]; //未決済を含む「NetAssets」の計算用。すべての銘柄の値洗いが加減されていく。
                 _.u[_.ul("MTM")] = _.u[_.ul("MTM")] -0+_.tp[_.mtm][_.rl("PL")]; //単一銘柄の値洗いの合計を作成する。

                 R.ITM.ChgPctN_Value("Mark_to_Market");
                 _.u[_.ul("ChgPctN_Value")] = (function() {
                   if(_.tp[_.mtm][_.rl("ChgPctN_Value")] <0)  return Math.min(_.u[_.ul("ChgPctN_Value")] ,_.tp[_.mtm][_.rl("ChgPctN_Value")]);
                   if(_.tp[_.mtm][_.rl("ChgPctN_Value")] >0)  return Math.max(_.u[_.ul("ChgPctN_Value")] ,_.tp[_.mtm][_.rl("ChgPctN_Value")]);
                   if(_.tp[_.mtm][_.rl("ChgPctN_Value")]===0) return _.u[_.ul("ChgPctN_Value")];
                 })();
                 
                 _.log.push("[値洗い"+(_.mtm-0+1)+":"+_.tp[_.mtm][_.rl("PL")]+"]");
               }
               _.log.push("[値洗いTotal:"+_.u[_.ul("MTM")]+"]");
             }}
             /**
              * 相関に基づくリスクの計算
              */
             ,CalcCorrelRisk :function(){
               var sym_i ,sym_j ,LongShort_sym_i ,LongShort_sym_j ,N_sym_i ,N_sym_j ,crrl_ij ,red={} ,yellow={};
               for(sym_i in GV.SYM){red[sym_i]=0; yellow[sym_i]=0;}
               for(sym_i in GV.SYM){
                 for(sym_j in GV.SYM){
                   LongShort_sym_i = U.ar[year][sym_i][day][_.ul("LongShort")];
                   LongShort_sym_j = U.ar[year][sym_j][day][_.ul("LongShort")];
                   N_sym_i = U.ar[year][sym_i][day][_.ul("N")];
                   N_sym_j = U.ar[year][sym_j][day][_.ul("N")];
                   crrl_ij = C.ar[year][sym_i][week][_.cl(sym_j)];

                   if(LongShort_sym_i===1){
                     if(LongShort_sym_j===1 && crrl_ij >=0.4){         // long : long
                       yellow[sym_i] = yellow[sym_i] -0+N_sym_j;
                       if(crrl_ij >=0.7) red[sym_i] = red[sym_i] -0+N_sym_j;
                     }else if(LongShort_sym_j===-1 && crrl_ij <=-0.4){ // long : short
                       yellow[sym_i] = yellow[sym_i] -0+N_sym_j;
                       if(crrl_ij <=-0.7) red[sym_i] = red[sym_i] -0+N_sym_j;
                     }
                   }else if(LongShort_sym_i===-1){
                     if(LongShort_sym_j===-1 && crrl_ij >=0.4){        // short : short
                       yellow[sym_i] = yellow[sym_i] -0+N_sym_j;
                       if(crrl_ij >=0.7) red[sym_i] = red[sym_i] -0+N_sym_j;
                     }else if(LongShort_sym_j===1 && crrl_ij <=-0.4){  // short : long
                       yellow[sym_i] = yellow[sym_i] -0+N_sym_j;
                       if(crrl_ij <=-0.7) red[sym_i] = red[sym_i] -0+N_sym_j;
                     }
                   }
                 }
               }
               for(sym_i in GV.SYM){
                 var u = U.ar[year][sym_i][day];
                 u[_.ul("RedRisk")]    = CALC.roundN(red[sym_i]    ,3);
                 u[_.ul("YellowRisk")] = CALC.roundN(yellow[sym_i] ,3);
                 if(!u[_.ul("EntryPrice")]=="") TP.log[sym_i].push("[R:"+u[_.ul("RedRisk")]+",Y:"+u[_.ul("YellowRisk")]+",L:"+_.f[_.fl("LongN")]+",S:"+_.f[_.fl("ShortN")]+"]");
               }
             }
            } // end Units
    /**
     * 作業用のポジションデータの格納
     * -- データ構造 (ar > {sym} > [itm])
     */
    ,TradingPos :{ar:{} ,log:{} ,Sum_PL:0 ,Sum_Comm:0 ,Sum_MTM:0}
    
    /**
     * Records はトレードの記録・書き出し用
     * -- ITM(項目)は TradingPos と同じ
     * -- データ構造 (ar > [year] > [ent ,ext]  > [トレード毎] > [項目(ITM)])
     */
    ,Records : { SH : SpreadsheetApp.openById(GV.SS.Records.ID).getSheetByName(GV.SS.Records.SHEET)
                ,ar : []
                ,ITM : {
                  No : function ( ent_ext ) {
                    if ( ["Entry_New","Entry_LO2","Entry_LO3","Entry_LO4"].indexOf(ent_ext)!==-1 ) {
                      _.r0[_.r_last][_.rl("No")] = (function() {
                        if ( _.r_last >0 ) return _.r0[_.r_last-1][_.rl("No")] -0+1;
                        if ( _.r_last===0 ) {
                          if ( year===0 ) return 1;
                          for ( var year_=year-1; year_>=0; year_-- ) {
                            if ( R.ar[year_][0].length >0 ) break;
                            if ( year_==0 ) return 1;
                          }
                          return R.ar[year_][0][R.ar[year_][0].length-1][_.rl("No")] -0+1;
                        }
                      })();
                      return;
                    }
                    if ( ["Exit_SO","Exit_Tech","Exit_N","Exit_Timed","Exit_Reversing"].indexOf(ent_ext)!==-1 ) {
                      _.r1[_.r_last][_.rl("No")] = _.tp[_.tp_exit][_.rl("No")];
                      return;
                    }
                  }
                  ,Date : function( ent_ext ) {
                    var N = ent_ext.indexOf("Exit")!==-1 ? 1 : 0; 
                    R.ar[year][N][_.r_last][_.rl(itm)] = _.t[_.tl("Date")];
                    if ( ent_ext==="Entry_New" ) _.u[_.ul("EntryDate")] = _.t[_.tl(itm)];
                  }
                  ,SymNum    : function ( ent_ext ) { var N = ent_ext.indexOf("Exit")!==-1 ? 1 : 0; R.ar[year][N][_.r_last][_.rl(itm)] = GV.SYM[sym].C; }
                  ,Symbol    : function ( ent_ext ) { var N = ent_ext.indexOf("Exit")!==-1 ? 1 : 0; R.ar[year][N][_.r_last][_.rl(itm)] = sym; }
                  ,Lot       : function ( ent_ext ) { var N = ent_ext.indexOf("Exit")!==-1 ? 1 : 0; R.ar[year][N][_.r_last][_.rl(itm)] = GV.SYM[sym].L; }
                  ,EntryExit : function ( ent_ext ) { var N = ent_ext.indexOf("Exit")!==-1 ? 1 : 0; R.ar[year][N][_.r_last][_.rl(itm)] = (N===1 ? "Exit" : "Entry"); }
                  ,LongShort : function ( ent_ext ) {
                    if( ent_ext === "Entry_New" ) { 
                      _.u[_.ul("LongShort")] = _.u[_.ul("EntrySig")];
                      _.r0[_.r_last][_.rl("LongShort")] = _.u[_.ul("EntrySig")];
                      return;
                    }
                    if( ["Entry_LO2","Entry_LO3","Entry_LO4"].indexOf(ent_ext) >= 0 ) {
                      _.r0[_.r_last][_.rl("LongShort")] = _.u[_.ul("LongShort")];
                      return;
                    }
                    if( ["Exit_Tech","Exit_SO","Exit_N","Exit_Timed","Exit_Reversing"].indexOf(ent_ext) >= 0 ) {
                      _.r1[_.r_last][_.rl("LongShort")] = _.tp[_.tp_exit][_.rl("LongShort")] *-1;
                      return;
                    }
                  }
                  ,N_Status : function( ent_ext ) {
                    if( ent_ext === "Entry_New" ) { _.u[_.ul(itm)] = "1u"; _.r0[_.r_last][_.rl(itm)] = "1u"; return; }
                    if( ent_ext === "Entry_LO2" ) { _.u[_.ul(itm)] = "2u"; _.r0[_.r_last][_.rl(itm)] = "2u"; return; }
                    if( ent_ext === "Entry_LO3" ) { _.u[_.ul(itm)] = "3u"; _.r0[_.r_last][_.rl(itm)] = "3u"; return; }
                    if( ent_ext === "Entry_LO4" ) { _.u[_.ul(itm)] = "4u"; _.r0[_.r_last][_.rl(itm)] = "4u"; return; }
                    if( ["Exit_Tech","Exit_SO","Exit_N","Exit_Timed","Exit_Reversing"].indexOf(ent_ext) >= 0 ) {
                      _.r1[_.r_last][_.rl(itm)] = _.tp[_.tp_exit][_.rl(itm)];
                      return;
                    }
                  }
                  ,Cause : function( ent_ext ) {
                    if ( ent_ext === "Entry_New" ) {
                      _.r0[_.r_last][_.rl(itm)] = "EntrySig";
                      return;
                    }
                    if ( ["Entry_LO2","Entry_LO3","Entry_LO4"].indexOf(ent_ext) >= 0 ) {
                      _.r0[_.r_last][_.rl(itm)] = "LO";
                      return;
                    }
                    if ( ["Exit_SO","Exit_Tech","Exit_N","Exit_Timed","Exit_Reversing"].indexOf(ent_ext) >= 0 ) {
                      _.r1[_.r_last][_.rl(itm)] = ent_ext;
                      return;
                    }
                  }
                  //N_Size
                  ,Pos : function( ent_ext ) {
                    if ( ent_ext === "Entry_New" ) {
                      _.r0[_.r_last][_.rl("Pos")] = _.u[_.ul("N_Size")];
                      _.u[_.ul("Pos")] = _.u[_.ul("Pos")] -0+_.u[_.ul("N_Size")];
                      return;
                    }
                    if ( ["Entry_LO2","Entry_LO3","Entry_LO4"].indexOf(ent_ext) >= 0 ) {
                      _.r0[_.r_last][_.rl("Pos")] = _.u_[_.ul("N_Size")];
                      _.u[_.ul("Pos")] = _.u[_.ul("Pos")] -0+_.u_[_.ul("N_Size")];
                      return;
                    }
                    if ( ["Exit_Tech","Exit_SO","Exit_N","Exit_Timed","Exit_Reversing"].indexOf(ent_ext) >= 0 ) {
                      _.r1[_.r_last][_.rl("Pos")] = _.tp[_.tp_exit][_.rl("Pos")];
                      return;
                    }
                  }
                  ,Price : function( ent_ext ) {
                    if ( ent_ext === "Entry_New" ) {
                      var price = (function() {
                        var timing = ST.Entry.TIMING;
                        if ( timing === "b4Closing" ) return _.u[_.ul("Close")]-0;
                        if ( timing === "NextClose" ) return _.t[_.tl("Close")]-0;
                        throw new Error("Entry.TIMING が指定されていません");
                      })();
                      _.r0[_.r_last][_.rl("Price")] = price;
                      _.u[_.ul("EntryPrice")]       = price;
                      _.u[_.ul("EntryAvgPrice")]    = price;
                      return;
                    }
                    if ( ["Entry_LO2","Entry_LO3","Entry_LO4"].indexOf(ent_ext) >= 0 ) {
                      if ( ST.LO.SIG === "Cross" ) {
                        if ( ent_ext === "Entry_LO2" ) _.r0[_.r_last][_.rl("Price")] = _.u_[_.ul("LO2")];
                        if ( ent_ext === "Entry_LO3" ) _.r0[_.r_last][_.rl("Price")] = _.u_[_.ul("LO3")];
                        if ( ent_ext === "Entry_LO4" ) _.r0[_.r_last][_.rl("Price")] = _.u_[_.ul("LO4")];
                      } else if ( ST.LO.SIG === "Over" ) {
                        _.r0[_.r_last][_.rl("Price")] = _.u[_.ul("Close")]-0;
                      } else {
                        throw new Error("[Records Price] LOの設定で例外が発生しました");
                      }
                      _.u[_.ul("EntryAvgPrice")] = (function() {
                        var sum=0 ,pos=0;
                        for(_.tp_lo=0 ,_.tpLen=_.tp.length; _.tp_lo<_.tpLen; _.tp_lo++){
                          sum = sum -0+(_.tp[_.tp_lo][_.rl("Price")] *_.tp[_.tp_lo][_.rl("Pos")]);
                          pos = pos -0+_.tp[_.tp_lo][_.rl("Pos")];
                        }
                        return CALC.roundN( sum /pos ,GV.SYM[sym].R );
                      })();
                      return;
                    }
                    if ( ent_ext === "Exit_Tech" ) {
                      _.r1[_.r_last][_.rl("Price")] = _.u_[_.ul("ExitPrice")]; 
                      return;
                    }
                    if ( ent_ext === "Exit_SO" ) {
                      _.r1[_.r_last][_.rl("Price")] = _.u_[_.ul("SO")];
                      return;
                    }
                    if( ["Exit_N","Exit_Timed","Exit_Reversing"].indexOf(ent_ext) >= 0 ) {
                      _.r1[_.r_last][_.rl("Price")] = _.u[_.ul("Close")]-0 //_.t[_.tl("Close")]-0;
                      return;
                    }
                  }
                  ,Currency : function( ent_ext ) { var N = ~ent_ext.indexOf("Exit") ? 1 : 0; R.ar[year][N][_.r_last][_.rl("Currency")] = _.u[_.ul("Currency")]; }
                  ,N_Value  : function( ent_ext ) {
                    if ( ent_ext === "Entry_New" ) {
                      _.r0[_.r_last][_.rl("N_Value")] = _.f[_.fl("N_Value")];
                      return;
                    }
                    if ( ["Entry_LO2","Entry_LO3","Entry_LO4"].indexOf( ent_ext ) >= 0 ) {
                      _.r0[_.r_last][_.rl("N_Value")] = _.f_[_.fl("N_Value")];
                      return;
                    }
                    if( ["Exit_Tech","Exit_SO","Exit_N","Exit_Timed","Exit_Reversing"].indexOf( ent_ext ) >= 0 ) {
                      _.r1[_.r_last][_.rl("N_Value")] = "";
                      return;
                    }
                  }
                  ,N_ATR : function( ent_ext ) {
                    var N = ~ent_ext.indexOf("Exit") ? 1 : 0;
                    R.ar[year][N][_.r_last][_.rl("N_ATR")] = N===1 ? "" : _.u[_.ul("ATR20")];
                  }
                  ,NominalValue : function( ent_ext ) {
                    if ( ["Entry_New","Entry_LO2","Entry_LO3","Entry_LO4"].indexOf(ent_ext) >= 0 ) {
                      var nominal_value = CALC.roundN(
                        GV.SYM[sym].L
                        * _.r0[_.r_last][_.rl("Pos")]
                        * _.r0[_.r_last][_.rl("Price")]
                        * _.r0[_.r_last][_.rl("Currency")]
                      ,0 );
                      _.r0[_.r_last][_.rl("NominalValue")] = nominal_value;
                      _.u[_.ul("NominalValue")] = _.u[_.ul("NominalValue")] -0+nominal_value;
                      return;
                    }
                    if ( ["Exit_Tech","Exit_SO","Exit_N","Exit_Timed","Exit_Reversing"].indexOf(ent_ext) >= 0 ) {
                      _.r1[_.r_last][_.rl("NominalValue")] = "";
                      return;
                    }
                  }
                  ,PL : function( ent_ext ) { // 値洗いと決済時の損益
                    if ( ["Entry_New","Entry_LO2","Entry_LO3","Entry_LO4"].indexOf( ent_ext ) >= 0 ) {
                      _.r0[_.r_last][_.rl("PL")] = "";
                      return;
                    }
                    if ( ["Exit_SO","Exit_Tech","Exit_N","Exit_Timed","Exit_Reversing"].indexOf( ent_ext ) >= 0 ) {
                      _.r1[_.r_last][_.rl("PL")] = CALC.roundN(
                        (_.r1[_.r_last][_.rl("Price")] *_.r1[_.r_last][_.rl("Currency")]
                        -_.tp[_.tp_exit][_.rl("Price")] *_.tp[_.tp_exit][_.rl("Currency")])
                        * _.r1[_.r_last][_.rl("Pos")]
                        * GV.SYM[sym].L
                        * _.tp[_.tp_exit][_.rl("LongShort")]
                      ,0 );
                      return;
                    }
                    if ( ent_ext === "Mark_to_Market" ) {
                      _.tp[_.mtm][_.rl("PL")] = CALC.roundN(
                        (_.u[_.ul("Close")] *_.u[_.ul("Currency")]
                        - _.tp[_.mtm][_.rl("Price")] *_.tp[_.mtm][_.rl("Currency")])
                        * _.tp[_.mtm][_.rl("Pos")]
                        * GV.SYM[sym].L
                        * _.tp[_.mtm][_.rl("LongShort")]
                      ,0 );
                      return;
                    }
                  }
                  ,Comm : function( ent_ext ) {
                    if ( ["Entry_New","Entry_LO2","Entry_LO3","Entry_LO4"].indexOf(ent_ext) >= 0 ) {
                      _.r0[_.r_last][_.rl("Comm")] = "";
                      return;
                    }
                    if ( ["Exit_SO","Exit_Tech","Exit_N","Exit_Timed","Exit_Reversing"].indexOf(ent_ext) >= 0 ) {
                      var cost;
                      if ( GU._is( "Function" ,GV.SYM[sym].cost ) ) {
                        cost = GV.SYM[sym].cost(_.r1[_.r_last][_.rl("Price")]);
                      } else {
                        cost = GV.SYM[sym].cost;
                      }
                      _.r1[_.r_last][_.rl("Comm")] = CALC.roundN(cost * _.u[_.ul("Currency")] * _.r1[_.r_last][_.rl("Pos")],0);
                      return;
                    }
                  }
                  ,ChgPctATR : function( ent_ext ) {
                    if ( ["Entry_New","Entry_LO2","Entry_LO3","Entry_LO4"].indexOf(ent_ext) >= 0 ) {
                      _.r0[_.r_last][_.rl("ChgPctATR")] = "";
                      return;
                    }
                    if ( ["Exit_SO","Exit_Tech","Exit_N","Exit_Timed","Exit_Reversing"].indexOf(ent_ext) >= 0 ) {
                      _.r1[_.r_last][_.rl("ChgPctATR")] = CALC.roundN(
                        (_.r1[_.r_last][_.rl("Price")] -_.tp[_.tp_exit][_.rl("Price")])
                        / _.tp[_.tp_exit][_.rl("N_ATR")]
                        * _.tp[_.tp_exit][_.rl("LongShort")]
                      ,3 );
                      return;
                    }
                  }
                  ,ChgPctN_Value : function( ent_ext ) { // N比の値幅 決済時のみ
                    if ( ["Entry_New","Entry_LO2","Entry_LO3","Entry_LO4"].indexOf(ent_ext) >= 0 ) {
                      _.r0[_.r_last][_.rl("ChgPctN_Value")] = "";
                      return;
                    }
                    if ( ["Exit_SO","Exit_Tech","Exit_N","Exit_Timed","Exit_Reversing"].indexOf(ent_ext) >= 0 ) {
                      _.r1[_.r_last][_.rl("ChgPctN_Value")] = CALC.roundN(
                        _.r1[_.r_last][_.rl("PL")]
                        / _.tp[_.tp_exit][_.rl("N_Value")]
                      ,3 );
                      return;
                    }
                    if ( ent_ext === "Mark_to_Market" ) {
                      _.tp[_.mtm][_.rl("ChgPctN_Value")] = CALC.roundN(
                        _.tp[_.mtm][_.rl("PL")]
                        / _.tp[_.mtm][_.rl("N_Value")]
                      ,3 );
                      return;
                    }
                  }
                }
               } // end Records
    /**
     * 損益の計算と記録
     * -- データ構造 (ar > year > 日付 > 項目(ITM))
     */
    ,Fund :{SH:SpreadsheetApp.openById(GV.SS.Fund.ID).getSheetByName(GV.SS.Fund.SHEET)
            ,ar:[]
            ,ITM:{
              Date : function(){_.f[_.fl(itm)] = _.t[_.tl(itm)];}
              ,SumExit : function() { _.f[_.fl("SumExit")] = TP.Sum_PL; }
              ,SumComm : function() { _.f[_.fl("SumComm")] = TP.Sum_Comm; }
              ,SumMTM  : function() { _.f[_.fl("SumMTM")]  = TP.Sum_MTM; }
              ,SumNominalValue : function() {
                _.f[_.fl("SumNominalValue")] = 0;
                if ( _.first ) return;
                for ( var sym_ in GV.SYM ) {
                  var u = U.ar[year][sym_][day];
                  if( u[_.ul("LongShort")] === 1 ) {
                    _.f[_.fl("SumNominalValue")] = CALC.roundN(_.f[_.fl("SumNominalValue")] -0+u[_.ul("NominalValue")] ,0);
                  }
                }
              }
              ,TAX : function() {
                _.f[_.fl("TAX")] = (function() {
                  if ( year === 0 || day !== 0 ) return _.tax;
                  if ( ST.TAX.is === true && _.f_[_.fl("PL_YTD_CashAsset")] > 0 ) {
                    var tax_pct;
                    if ( GU._is( "Function" ,ST.TAX.pct ) ) {
                      tax_pct = ST.TAX.pct( _.f_[_.fl("PL_YTD_CashAsset")] );
                    } else {
                      tax_pct = ST.TAX.pct;
                    }
                    _.tax = CALC.roundN( _.f_[_.fl("PL_YTD_CashAsset")] * tax_pct ,0 );
                  }
                  return _.tax;
                })();
              }
              ,CashAsset : function(){
                _.f[_.fl("CashAsset")] = (function(){
                  if(_.first) return ST.FIRST_FUND;
                  // _.tax は、年初以外「0」
                  var result = _.f_[_.fl("CashAsset")] - _.tax -0+ TP.Sum_PL - TP.Sum_Comm;
                  TP.Sum_PL = 0 ,TP.Sum_Comm = 0;
                  return result;
                })();
              }
              ,NetAssets : function(){
                _.f[_.fl("NetAssets")] = _.first ? ST.FIRST_FUND : (_.f[_.fl("CashAsset")] -0+TP.Sum_MTM);
                TP.Sum_MTM = 0;
              }

              ,PL_DB_CashAsset               : function(){_.f[_.fl(itm)] = _.first ? 0 : _.f[_.fl("CashAsset")] -_.f_[_.fl("CashAsset")] -0+_.tax;}
              ,PL_DB_pct_CashAsset           : function(){_.f[_.fl(itm)] = _.first ? 0 : CALC.roundN(_.f[_.fl("PL_DB_CashAsset")] /F.ar[year][0][_.fl("CashAsset")] ,3);}
              ,PL_YTD_CashAsset              : function(){_.f[_.fl(itm)] = day===0 ? _.f[_.fl("PL_DB_CashAsset")] : _.f_[_.fl(itm)] -0+_.f[_.fl("PL_DB_CashAsset")];}
              ,PL_YTD_pct_CashAsset          : function(){_.f[_.fl(itm)] = _.first ? 0 : CALC.roundN(_.f[_.fl("PL_YTD_CashAsset")] /F.ar[year][0][_.fl("CashAsset")] ,3);}
              ,Beginning_YTD_CashAsset       : function(){_.f[_.fl(itm)] = day===0 ? _.f[_.fl("CashAsset")] : _.f_[_.fl(itm)];}
              ,DD_Beginning_YTD_CashAsset    : function(){_.f[_.fl(itm)] = day===0 ? 0 : _.f[_.fl("CashAsset")] <_.f[_.fl("Beginning_YTD_CashAsset")] ? (_.f[_.fl("Beginning_YTD_CashAsset")] -_.f[_.fl("CashAsset")]) : 0;}
              ,Maximum_YTD_CashAsset         : function(){_.f[_.fl(itm)] = day===0 ? _.f[_.fl("CashAsset")] : _.f[_.fl("CashAsset")] >_.f_[_.fl(itm)] ? _.f[_.fl("CashAsset")] : _.f_[_.fl(itm)];}
              ,DD_Maximum_YTD_CashAsset      : function(){_.f[_.fl(itm)] = day===0 ? 0 : _.f[_.fl("CashAsset")] <_.f[_.fl("Maximum_YTD_CashAsset")] ? (_.f[_.fl("Maximum_YTD_CashAsset")] -_.f[_.fl("CashAsset")]) : 0;}
              ,DD_Maximum_YTD_pct_CashAsset  : function(){_.f[_.fl(itm)] = day===0 ? 0 : _.f[_.fl("DD_Maximum_YTD_CashAsset")]===0 ? 0 : CALC.roundN(_.f[_.fl("DD_Maximum_YTD_CashAsset")] /_.f[_.fl("Maximum_YTD_CashAsset")] ,3);}
              ,DD_Maximum_YTD_span_CashAsset : function(){_.f[_.fl(itm)] = day===0 ? 0 : _.f[_.fl("DD_Maximum_YTD_CashAsset")]===0 ? 0 : (_.f_[_.fl("DD_Maximum_YTD_span_CashAsset")] -0+1);}
              ,PL_whole_pct_CashAsset        : function(){_.f[_.fl(itm)] = _.first ? 0 : CALC.roundN((_.f_[_.fl("CashAsset")] -ST.FIRST_FUND) /ST.FIRST_FUND ,3);}
              ,Maximum_whole_CashAsset       : function(){_.f[_.fl(itm)] = _.first ? 0 : (_.f[_.fl("CashAsset")] >_.f_[_.fl(itm)]) ? _.f[_.fl("CashAsset")] : _.f_[_.fl(itm)];}
              ,DD_whole_CashAsset            : function(){_.f[_.fl(itm)] = _.f[_.fl("CashAsset")] <_.f[_.fl("Maximum_whole_CashAsset")] ? (_.f[_.fl("Maximum_whole_CashAsset")] -_.f[_.fl("CashAsset")]) : 0;}
              ,DD_whole_pct_CashAsset        : function(){_.f[_.fl(itm)] = _.f[_.fl("DD_whole_CashAsset")]===0 ? 0 : CALC.roundN(_.f[_.fl("DD_whole_CashAsset")] /_.f[_.fl("Maximum_whole_CashAsset")] ,3);}
              ,DD_whole_span_CashAsset       : function(){_.f[_.fl(itm)] = _.f[_.fl("DD_whole_CashAsset")]===0 ? 0 : (_.f_[_.fl("DD_whole_span_CashAsset")] -0+1);}
              
              ,PL_DB_NetAssets               : function(){_.f[_.fl(itm)] = _.first ? 0 : _.f[_.fl("NetAssets")] -_.f_[_.fl("NetAssets")] -0+_.tax;}
              ,PL_DB_pct_NetAssets           : function(){_.f[_.fl(itm)] = _.first ? 0 : CALC.roundN(_.f[_.fl("PL_DB_NetAssets")] /F.ar[year][0][_.fl("NetAssets")] ,3);}
              ,PL_YTD_NetAssets              : function(){_.f[_.fl(itm)] = day===0 ? _.f[_.fl("PL_DB_NetAssets")] : _.f_[_.fl(itm)] -0+_.f[_.fl("PL_DB_NetAssets")];}
              ,PL_YTD_pct_NetAssets          : function(){_.f[_.fl(itm)] = _.first ? 0 : CALC.roundN(_.f[_.fl("PL_YTD_NetAssets")] /F.ar[year][0][_.fl("NetAssets")] ,3);}
              ,Beginning_YTD_NetAssets       : function(){_.f[_.fl(itm)] = day===0 ? _.f[_.fl("NetAssets")] : _.f_[_.fl(itm)];}
              ,DD_Beginning_YTD_NetAssets    : function(){_.f[_.fl(itm)] = day===0 ? 0 : _.f[_.fl("NetAssets")] <_.f[_.fl("Beginning_YTD_NetAssets")] ? (_.f[_.fl("Beginning_YTD_NetAssets")] -_.f[_.fl("NetAssets")]) : 0;}
              ,Maximum_YTD_NetAssets         : function(){_.f[_.fl(itm)] = day===0 ? _.f[_.fl("NetAssets")] : _.f[_.fl("NetAssets")] >_.f_[_.fl(itm)] ? _.f[_.fl("NetAssets")] : _.f_[_.fl(itm)];}
              ,DD_Maximum_YTD_NetAssets      : function(){_.f[_.fl(itm)] = day===0 ? 0 : _.f[_.fl("NetAssets")] <_.f[_.fl("Maximum_YTD_NetAssets")] ? (_.f[_.fl("Maximum_YTD_NetAssets")] -_.f[_.fl("NetAssets")]) : 0;}
              ,DD_Maximum_YTD_pct_NetAssets  : function(){_.f[_.fl(itm)] = day===0 ? 0 : _.f[_.fl("DD_Maximum_YTD_NetAssets")]===0 ? 0 : CALC.roundN(_.f[_.fl("DD_Maximum_YTD_NetAssets")] /_.f[_.fl("Maximum_YTD_NetAssets")] ,3);}
              ,DD_Maximum_YTD_span_NetAssets : function(){_.f[_.fl(itm)] = day===0 ? 0 : _.f[_.fl("DD_Maximum_YTD_NetAssets")]===0 ? 0 : (_.f_[_.fl("DD_Maximum_YTD_span_NetAssets")] -0+1);}
              ,PL_whole_pct_NetAssets        : function(){_.f[_.fl(itm)] = _.first ? 0 : CALC.roundN((_.f_[_.fl("NetAssets")] -ST.FIRST_FUND) /ST.FIRST_FUND ,3);}
              ,Maximum_whole_NetAssets       : function(){_.f[_.fl(itm)] = _.first ? 0 : (_.f[_.fl("NetAssets")] >_.f_[_.fl(itm)]) ? _.f[_.fl("NetAssets")] : _.f_[_.fl(itm)];}
              ,DD_whole_NetAssets            : function(){_.f[_.fl(itm)] = _.f[_.fl("NetAssets")] <_.f[_.fl("Maximum_whole_NetAssets")] ? (_.f[_.fl("Maximum_whole_NetAssets")] -_.f[_.fl("NetAssets")]) : 0;}
              ,DD_whole_pct_NetAssets        : function(){_.f[_.fl(itm)] = _.f[_.fl("DD_whole_NetAssets")]===0 ? 0 : CALC.roundN(_.f[_.fl("DD_whole_NetAssets")] /_.f[_.fl("Maximum_whole_NetAssets")] ,3);}
              ,DD_whole_span_NetAssets       : function(){_.f[_.fl(itm)] = _.f[_.fl("DD_whole_NetAssets")]===0 ? 0 : (_.f_[_.fl("DD_whole_span_NetAssets")] -0+1);}

              ,LongN : function(){ // ※ ここの3項目はitmを使わない。(ループで回さないアクセスがあるため)
                _.f[_.fl("LongN")] = 0;
                if(_.first) return;
                for(var sym_ in GV.SYM){
                  var u = U.ar[year][sym_][day];
                  if(u[_.ul("LongShort")]===1) _.f[_.fl("LongN")] = CALC.roundN(_.f[_.fl("LongN")] -0+u[_.ul("N")] ,1);
                }
              }
              ,ShortN : function(){
                _.f[_.fl("ShortN")] = 0;
                if(_.first) return;
                for(var sym_ in GV.SYM){
                  var u = U.ar[year][sym_][day];
                  if(u[_.ul("LongShort")]===-1) _.f[_.fl("ShortN")] = CALC.roundN(_.f[_.fl("ShortN")] -0+u[_.ul("N")] ,1);
                }
              }
              ,TotalN : function(){
                _.f[_.fl("TotalN")] = _.first ? 0 : (_.f[_.fl("LongN")] -0+_.f[_.fl("ShortN")]);
                if(_.f[_.fl("TotalN")]===Infinity) throw new Error("無限大が出現・・・!!!!!");
              }
              ,N_Value_source : function(){
                if(_.first || day===day_len-1){ //年の最終日は翌年のリセットされたNになる
                  _.f[_.fl("N_Value_source")] = CALC.roundN(_.f[_.fl(ST.N_Source)] ,2);
                }else{
                  if(ST.N_Source+ST.N_Interest==="CashAssetCompound"){
                    var
                     principal  = _.f[_.fl("Maximum_YTD_CashAsset")]
                    ,DD_forCalc = _.f[_.fl("DD_Maximum_YTD_CashAsset")]
                    ;
                  }else if(ST.N_Source+ST.N_Interest==="CashAssetSimple"){
                    var
                     principal  = _.f[_.fl("Beginning_YTD_CashAsset")]
                    ,DD_forCalc = _.f[_.fl("DD_Beginning_YTD_CashAsset")]
                    ;
                  }else if(ST.N_Source+ST.N_Interest==="NetAssetsCompound"){
                    var
                     principal  = _.f[_.fl("Maximum_YTD_NetAssets")]
                    ,DD_forCalc=_.f[_.fl("DD_Maximum_YTD_NetAssets")]
                    ;
                  }else if(ST.N_Source+ST.N_Interest==="NetAssetsSimple"){
                    var
                     principal  = _.f[_.fl("Beginning_YTD_NetAssets")]
                    ,DD_forCalc=_.f[_.fl("DD_Beginning_YTD_NetAssets")]
                    ;
                  }
                  _.f[_.fl("N_Value_source")] = CALC.roundN((principal -(DD_forCalc *ST.Virtual_DD)) ,2);
                }
              }
              ,N_Value : function(){_.f[_.fl("N_Value")] = CALC.roundN(_.f[_.fl("N_Value_source")] *ST.N_pct_FUND ,2);}
              ,Log :function(){_.f[_.fl("Log")] = JSON.stringify(TP.log);}
            }
           } // end Fund
  }; // end WORK
  
  
  /**
   * 作業用・記述の煩雑化を防ぐ
   */
  var _ = {u:[] ,u_:[] ,t:[] ,t_:[] ,f:[] ,f_:[] ,r0:[] ,r1:[] ,tp:[] ,log:[]
        ,set_vars_year :function(){
          this.r0 = R.ar[year][0];
          this.r1 = R.ar[year][1];
        }
        ,set_vars_day :function(){
          this.f           = F.ar[year][day];
          this.f_          = year>0 && day===0 ? F.ar[year-1][F.ar[year-1].length-1] : F.ar[year][day-1];
          this.first       = year===0 && day===0;
          this.trade_start = !(year===0 && (day===0 || day===1));
          this.tax = 0;
        }
        ,set_vars_sym :function(){
          this.u   = U.ar[year][sym][day];
          this.u_  = year>0 && day===0 ? U.ar[year-1][sym][U.ar[year-1][sym].length-1] : U.ar[year][sym][day-1];
          this.t   = T.ar[year][sym][day];
          this.t_  = year>0 && day===0 ? T.ar[year-1][sym][T.ar[year-1][sym].length-1] : T.ar[year][sym][day-1];
          this.tp  = TP.ar[sym];
          this.log = TP.log[sym];
        }
        ,ul :function(itm){return U.list.indexOf(itm);}
        ,tl :function(itm){return T.list.indexOf(itm);}
        ,cl :function(itm){return C.list.indexOf(itm);}
        ,fl :function(itm){return F.list.indexOf(itm);}
        ,rl :function(itm){return R.list.indexOf(itm);}
        ,r_last  : 0
        ,mtm     : 0
        ,tp_exit : 0
        ,tp_lo   : 0
        ,tpLen   : 0
        ,tax     : 0
        ,check_time :function(){
          if(TU.isNearlyTimeOut(functionStart ,{SCALE:"seconds" ,NUM:290})){
            Logger.log("一定時間を経過したので作業を終了します。");
            throw new Error("isNearlyTimeOut: True");
          }
        }
   };
  
  /**
   * Fusion tables 用
   */
  var FT = {Years:MO.moment(ST.END ,"YYYY.MM.DD").diff(MO.moment(ST.START ,"YYYY.MM.DD") ,"years") -0+1};
  
  var // その他の変数
   syms = Object.keys(GV.SYM)
  ,T  = WORK.Techs
  ,C  = WORK.Correl
  ,U  = WORK.Units
  ,TP = WORK.TradingPos
  ,R  = WORK.Records
  ,F  = WORK.Fund
  ,w
  ;
  
  /**
   * 作業用のリストを作成
   */
  for(w in WORK){
    if(WORK[w].FT_ID) WORK[w]["list"] = FTU._getItems({ID:WORK[w].FT_ID ,maxResults:ST.FT_maxResults});
    else if(WORK[w].ITM) WORK[w]["list"] = Object.keys(WORK[w].ITM);
  }
  
  /**
   * メインの処理
   * -- 年毎の処理と書き出し
   */
  MAIN:while(true){
    try{
      var year ,sym ,week ,addWeek ,day ,day_len ,itm ,ruin=false;
      
      YEARS:for(year=resumed ? GV.ROOP_COUNT-1 : GV.ROOP_COUNT; year<FT.Years; year++){
        FT.START = MO.moment(ST.START ,"YYYY.MM.DD").clone().add(year ,"years").format("YYYY.MM.DD");
        FT.END   = MO.moment(ST.START ,"YYYY.MM.DD").clone().add(ST.SYCLE -0+year ,"years").subtract(1 ,"day").format("YYYY.MM.DD");
        Logger.log("Fusion tables から次の期間のデータを取得します。\n"+FT.START+"\n"+FT.END+"\n\n");
        
        // データの取得と成形
        for(w in WORK){
          if(WORK[w].FT_ID){
            WORK[w].pre_ar = FTU._getRows({
              ID       :WORK[w].FT_ID
              ,SELECT  :WORK[w].list.toString()
              ,WHERE   :"Date>=\'"+FT.START+"\' AND Date<=\'"+FT.END+"\' "
              ,ORDER_BY:"Date ASC"
            }).RESULT.rows;
            
            WORK[w].ar[year] = {};
            for(sym in GV.SYM){
              WORK[w].ar[year][sym] = WORK[w].pre_ar.filter(function(pre){return ~pre.indexOf(sym);});
              if(w==="Techs"){
                if(sym===syms[0] || day_len===T.ar[year][sym].length){day_len = T.ar[year][sym].length;} // データ数の確認。
                else{throw new Error("各銘柄のデータ数(日数)が合いません。\n確認してください。\n");}
              }
            }
          }else{
            switch(w){
              case "Units": 
                WORK[w].ar[year] = {};
                for(sym in GV.SYM){
                  WORK[w].ar[year][sym] = [];
                  if(resumed){
                    var gp = SP.getProperty(sym);
                    WORK[w].ar[year][sym][0] = JSON.parse(gp==="undefined" ? "[]" : gp);
                  }
                }
                break;
              case "TradingPos":
                if(year===0 || resumed){for(sym in GV.SYM){
                  WORK[w].ar[sym]  = resumed ? JSON.parse(U.ar[year][sym][0][_.ul("TradingPos")]) : [];
                  WORK[w].log[sym] = [];
                }}
                break;
              case "Records":
                WORK[w].ar[year] = [[],[]];
                if(resumed){
                  var
                   gp0 = SP.getProperty("Records[0]")
                  ,gp1 = SP.getProperty("Records[1]")
                  ;
                  WORK[w].ar[year][0][0] = JSON.parse(gp0==="undefined" ? "[]" : gp0);
                  WORK[w].ar[year][1][0] = JSON.parse(gp1==="undefined" ? "[]" : gp1);
                }
                break;
              case "Fund": 
                WORK[w].ar[year] = [];
                if(resumed) WORK[w].ar[year] = F.SH.getRange(F.SH.getLastRow() ,1 ,1 ,F.list.length).getValues();
                break;
            }
          }
        }
        
        // 計算用データ(基軸通貨)の成形
        T.ar_forCalc[year] = {};
        for(var fc=0; fc<GV.forCalc.length; fc++){
          T.ar_forCalc[year][GV.forCalc[fc]] = T.pre_ar.filter(function(pre){return ~pre.indexOf(GV.forCalc[fc]);});
        }

        if(!resumed){
          _.set_vars_year();
          
          DAYS:for(day=0 ,week=0; day<day_len; day++){
            if(ST.Correl_Risk===true){
              addWeek = MO.moment(T.ar[year][sym][day][_.tl("Date")] ,"YYYY/MM/DD").diff(MO.moment(C.ar[year][sym][week][_.cl("Date")] ,"YYYY/MM/DD") ,"week") >=1;
              if(addWeek) week++;
            }
            
            for(sym in GV.SYM){U.ar[year][sym][day] = []; F.ar[year][day] = [];}
            _.set_vars_day();
            
            for(sym in GV.SYM){_.check_time(); _.set_vars_sym(); for(itm in U.ITM) U.ITM[itm]();}   // 前日の値動きにもとづくユニットやシグナル
            F.ITM["LongN"]() ,F.ITM["ShortN"]() ,F.ITM["TotalN"](); F.ITM["SumNominalValue"]();     // Entry・Exitの計算用に事前に取得。相関のリスクは前日のものをユニットで一旦取得している。

            for(sym in GV.SYM){_.check_time(); _.set_vars_sym(); U.Exit();}                         // 前日のSOと当日のTimedにもとづく処理
            for(sym in GV.SYM){_.check_time(); _.set_vars_sym(); U.Entry("B4");}                    // 前日のLOに基づく処理(Timedに該当するものはLOを達成しない前提)
            for(sym in GV.SYM){_.check_time(); _.set_vars_sym(); U.MarkToMarket();}                 // 値洗い

            for(itm in F.ITM) F.ITM[itm]();                                                         // 前日の値動きにもとづく資金量の計算
            for(sym in GV.SYM){_.check_time(); _.set_vars_sym(); U.ITM["N_Size"]() ,U.ITM["N"]();}  // 資金量の計算で算出されたNの価値をもとにN関連の計算
            F.ITM["LongN"]() ,F.ITM["ShortN"]() ,F.ITM["TotalN"]();                                 // 算出されたNにもとづいて
            if(ST.Correl_Risk===true) U.CalcCorrelRisk();                                           // 算出されたNにもとづいて

            if(_.trade_start){ // 当日のシグナルにもとづくエントリーの処理
              if(ST.Check_Strength.is===true){ // 比較処理 有効
                var order_by_strength = [];
                if(["FilStrength","DX","ADX_14"].indexOf(ST.Check_Strength.Order_by)!==-1){
                  for(sym in GV.SYM){_.set_vars_sym();order_by_strength.push([sym ,_.u[_.ul(ST.Order_by)] ,_.u[_.ul("EntrySig")]]);}
                  order_by_strength.sort(function(a,b){
                    if(ST.Check_Strength.ASC_DESC==="ASC")  return a[1]*a[2] -b[1]*b[2]; // 昇順
                    if(ST.Check_Strength.ASC_DESC==="DESC") return b[1]*b[2] -a[1]*a[2]; // 降順
                  });
                }else if(ST.Check_Strength.Order_by==="DI_14"){
                  for(sym in GV.SYM){_.set_vars_sym(); order_by_strength.push([sym ,_.u[_.ul("plus_DI_14")] ,_.u[_.ul("minus_DI_14")] ,_.u[_.ul("EntrySig")]]);}
                  order_by_strength.sort(function(a,b){
                    if(ST.Check_Strength.ASC_DESC==="ASC") {var a_=(a[3]===1 ? a[1] : a[3]===-1 ? a[2] : 100) ,b_=(b[3]===1 ? b[1] : b[3]===-1 ? b[2] : 100); return a_ -b_;}
                    if(ST.Check_Strength.ASC_DESC==="DESC"){var a_=(a[3]===1 ? a[1] : a[3]===-1 ? a[2] : 0)   ,b_=(b[3]===1 ? b[1] : b[3]===-1 ? b[2] : 0); return b_ -a_;}
                  });
                }
                for(var i=0 ,len=order_by_strength.length; i<len; i++){sym = order_by_strength[i][0]; _.check_time(); _.set_vars_sym(); U.Entry("New");}
              }else if(ST.Check_Strength.is===false){ // 比較処理 無効
                for(sym in GV.SYM){_.check_time(); _.set_vars_sym(); U.Entry("New");}
              }
            }
            for(sym in GV.SYM){_.check_time(); _.set_vars_sym(); for(itm in U.ITM){if(_.u[_.ul(itm)]==="later") U.ITM[itm]();}} // すべてのエントリーにもとづくLOやSOの計算
            
            F.ITM.Log(); // ログを残す
            for(sym in GV.SYM){_.check_time(); _.set_vars_sym(); for(itm in U.ITM){if(_.u[_.ul(itm)]==="log") U.ITM[itm]();}}

            if(_.f[_.fl("NetAssets")] <ST.RUIN_FUND){ // 破産した場合は強制終了
              Logger.log("資金量が破産の基準額を下回ったのでバックテストを終了します。\n\n");
              ruin = true;
              GV.ROOP_COUNT = FT.Years;
              break DAYS;
            }
            /*//////////////////
            // ForTEST /////////
            if(day===150){
              var hoge = "test";
              var huga = "test";
            }
            //////////////////*/
          }
            
          // 書き出し処理
          if(year===0){
            //複数あるSS[unit]すべてに項目(列)名を入れる場合はループ
            //for(var i in U.SH) U.SH[i].getRange(1 ,1 ,1 ,U.list.length).setValues([U.list]);
            U.SH[0].getRange(1 ,1 ,1 ,U.list.length).setValues([U.list]);
            F.SH.getRange(1 ,1 ,1 ,F.list.length).setValues([F.list]);
            R.SH.getRange(1 ,1 ,1 ,R.list.length).setValues([R.list]);
          }
          var i = Math.floor(year /4);
          for(sym in GV.SYM){if(U.ar[year][sym].length >0) U.SH[i].getRange(1+U.SH[i].getLastRow() ,1 ,U.ar[year][sym].length ,U.list.length).setValues(U.ar[year][sym]);}
          if(F.ar[year].length >0)    F.SH.getRange(1+F.SH.getLastRow() ,1 ,F.ar[year].length    ,F.list.length).setValues(F.ar[year]);
          if(R.ar[year][0].length >0) R.SH.getRange(1+R.SH.getLastRow() ,1 ,R.ar[year][0].length ,R.list.length).setValues(R.ar[year][0]);
          if(R.ar[year][1].length >0) R.SH.getRange(1+R.SH.getLastRow() ,1 ,R.ar[year][1].length ,R.list.length).setValues(R.ar[year][1]);
          
          if(ruin) break MAIN;
          GV.ROOP_COUNT++;
        }else{
          resumed = false;
          Logger.log("\nturn off resumed mode.\n/////【end resumed mode】////////////////////////////\n");
        }
      }
      break MAIN;
      
    /**
     * エラー処理
     */
    }catch(e){
      if(e.message==="isNearlyTimeOut: True") break MAIN;
      
      Logger.warning("再起動してもダメなようです。確認してみてください。");
      GV.MAIL("Ran script: "+arguments.callee.name+" エラーが発生したので終了します。" ,"エラーが発生したので終了します。\n\n----------\n"+Logger.getLog());
      return;
    } // try catch
  } // MAIN:while
  
  /**
   * 次回への引き継ぎ
   */
  if(GV.ROOP_COUNT <FT.Years && !ruin){
    SP.setProperty("ROOP_COUNT" ,GV.ROOP_COUNT);
    Logger.log("Set property. [ROOP_COUNT: "+GV.ROOP_COUNT+" /"+FT.Years+"]\n");
    for(sym in GV.SYM){
      var json = JSON.stringify(U.ar[GV.ROOP_COUNT-1][sym][U.ar[GV.ROOP_COUNT-1][sym].length-1]);
      SP.setProperty(sym ,json);
      Logger.log("Set property. ["+sym+": "+json+"]\n");
    }
    for(var i=0 ,len=2; i<len; i++){
      var json = JSON.stringify(R.ar[GV.ROOP_COUNT-1][i][R.ar[GV.ROOP_COUNT-1][i].length-1]);
      SP.setProperty("Records["+i+"]" ,json);
      Logger.log("Set property. [Records["+i+"]: "+json+"]\n");
    }
    
    var triggerId = ScriptApp.newTrigger(arguments.callee.name).timeBased().at(MO.moment().add("minutes" ,ST.RUN_SYCLE).toDate()).create().getUniqueId();
    SP.setProperty(triggerKey ,triggerId);
    Logger.log("Make trriger and Set property. ["+triggerKey+": "+triggerId+"]");
    
    Logger.log("End script: "+arguments.callee.name+" "+ST.RUN_SYCLE+"分後に続きの処理を行います。");
    GV.MAIL("Ran script: "+arguments.callee.name+" ["+GV.ROOP_COUNT+" /"+FT.Years+"]"+ST.RUN_SYCLE+"分後に続きの処理を行います。" ,Logger.getLog());
    return;

  /**
   * 全ての処理が完了した時
   */
  }else{
    for(sym in GV.SYM){
      SP.deleteProperty(sym);
      Logger.log("Delete property. ["+sym+"]\n");
    }
    for(var i=0 ,len=2; i<len; i++){
      SP.deleteProperty("Records["+i+"]");
      Logger.log("Delete property. [Records["+i+"]]\n");
    }
    SP.deleteProperty("ROOP_COUNT");
    Logger.log("Delete property. [ROOP_COUNT]\n");

    Logger.log("End script.["+arguments.callee.name+"]");
    GV.MAIL("Ran script: "+arguments.callee.name+" ["+GV.ROOP_COUNT+" /"+FT.Years+"]" ,Logger.getLog());
    return;
  }
} // func

以下は、ざっくりとしたコードの構成です。

  1. (var ST)バックテストの基本的な設定
  2. (var WORK)パーツごとの動作を設定
    • (var WORK.Techs)FT「[BackTest] Prices ,Techs」の価格データもろもろを格納
    • (var WORK.Correl)FT「[作成用] 2.Correl」の相関係数のデータを格納
    • (var WORK.Units)バックテストの銘柄ごとの色々を1日ずつ計算する
    • (var WORK.TradingPos)バックテストで日をまたぐ処理を担当
    • (var WORK.Records)バックテストの売買に関する処理
    • (var WORK.Fund)バックテストの資金に関する処理
  3. (var _)記述を短くするためのもの
  4. ループで計算処理を回していく
    • (MAIN:while)処理全体
    • (YEARS:for)年ごとの処理
    • (DAYS:for)日ごとの処理
      1. 前日の値動きにもとづくユニットやシグナルの計算
      2. 前日の逆指値の処理(イグジット、エントリー)
      3. 値洗い
      4. 資金の計算
      5. リスク関連の計算
      6. 当日のエントリー
      7. 当日の逆指値を仕掛ける
      8. ログを残す
  5. 再起動処理とか

目次へ

資金管理とリスク管理の設定

エントリーとイグジットのシグナルの切り替えもここで行います。どういう条件のシグナルにするかは別の箇所で指定して、必要なテクニカル指標をTechsからUnitsに読み込む必要があります。

設定できる項目は以下の通りです。

  • バックテストの期間
  • 検証開始時の資金
  • 破産と判断する資金
  • 1エントリーでとるリスク(対投資金)
  • 資金管理を決済済みの現金資産で行うか、未決済の純資産で行うか
  • 完全複利で運用するか、単利(年単位の複利)で運用するか
  • 年ごとのドローダウンを、資金管理上「✕ N」で扱うか(リスクを低減できる)
  • 資金管理を細かく計算するかどうか
  • レバレッジの調整、1倍に近いほどエントリーできる量が制限される
  • 税金の有無
  • テクニカル由来の決済
  • 指値タイプの損切り
  • 銘柄ごとのリスク由来の決済
  • 時限式の決済
  • 途転の有無
  • エントリーシグナルの切り替え
  • イグジットシグナルの切り替え
  • 全ポジションでどれくらいのリスクをとるか
  • 買いポジションでどれくらいのリスクをとるか
  • 売りポジションでどれくらいのリスクをとるか
  • 中程度の相関の銘柄間でどれくらいのリスクをとるか
  • 強い相関の銘柄間でどれくらいのリスクをとるか
  • 単一銘柄でどれくらいのリスクをとるか
  • ピラミッティングの回数の制限
  • 相関関係のリスク管理の有無
  • 強い相関のエントリーの制限の有無
  • 同時にでたシグナルからトレンドの強いものを抽出
  • PLフィルターの有無(前回の決済が利益だったら、取引を1回休む)

資金管理とリスク管理は、「2. Back Test.gs」の以下の箇所で設定できます。

var ST (2. Back Test.gs)

  /**
   * セッテイング
   */
  var ST = {
    
    // 再起動の間隔(分)
    RUN_SYCLE : 2
    
    // 計算する期間を指定 "YYYY.MM.DD"
    ,START : "2012.1.1"//"2001.1.1"
    ,END   : "2017.12.31"
    
    // FTでデータを取得する期間(年)
    ,SYCLE : 1 
    ,FT_maxResults : 100
    
    /**
     * 資金管理の設定
     */
    ,FIRST_FUND : 10000000
    ,RUIN_FUND  : 100000
    ,N_pct_FUND : 1/100
    ,N_Source   : "NetAssets"   // *{String} NetAssets or CashAsset リスク管理の基準を変更できる。
    ,N_Interest : "Simple"      // *{String} Compound(複利)or Simple(単利)
    ,Virtual_DD : 2            // *{Number} 1Nの算出時に減算するDDの価値 2とすると現在の元金からDD*2を減算する。年間の損失を最大50%におさえることができる。
    ,N_Calc     : "Correct"     // {String} Correct(しっかり)or Simple(簡単に)Nの計算をポジションとNのサイズにもとづいてしっかり計算するか、単純に1u=1Nとするか。すべてのリスク管理に影響がでる。
    ,Leverage : ({is:true,N:1}) // 1~50程度 1で現物
//    ,Leverage : ({is:true,N:50})
    
    /**
     * 税金
     */
//    ,TAX : ({is:true ,pct:0.2}) // 税金を考慮するかどうか(簡易版、マイナス分の繰越が未実装)
//    ,TAX : ({is:false})
    ,TAX : ({
      is:true
      ,pct:function(profit){ // 総合課税
        if ( profit <= 1950000 ) return 0.15;
        if ( profit <= 3300000 ) return 0.2;
        if ( profit <= 6950000 ) return 0.3;
        if ( profit <= 9000000 ) return 0.33;
        if ( profit <= 18000000 ) return 0.43;
        if ( profit <= 40000000 ) return 0.47;
        return 0.51; // 4000万円以上
      }
    })
                    
    /**
     * テクニカル由来の決済(開発時はExitSigとExitPriceの調整が必要)
     */
//    ,ExitSig : ({is:"HL" ,H:"High10" ,L:"Low10"}) // タートルズの利益確定
//    ,ExitSig : ({is:"Target" ,1:"SMA350"}) // ユニットのテクニカル分析「SMA350」など。
//    ,ExitSig : ({is:"CrossEMA" ,1:"EMA150" ,2:"EMA250"}) //  EMAのクロスで決済
    ,ExitSig : ({is:false})
    
    /**
     * 損切りの決済(開発時はExitSigとExitPriceの調整が必要)
     */
//    ,SO : ({is:"Turtle" ,1:"ATR20" ,N:2 ,CALC:"Change"}) // Fix(固定)or Change(変動)LOとSOの計算値を毎日計算し直すか、初回の計算値で固定とするか。
    ,SO : ({is:false})
    
    /**
     * 資金(Nの価値・為替換算対策)ベースの損切りの有効・無効(計算値を算出orゼロ)
     * ({is:true ,N:9.5 ,ChgPctN_Value:true ,ChgPctATR:true})
     */
//    ,Exit_N : ({is:true ,N:4 ,ChgPctN_Value:true ,ChgPctATR:true})
    ,Exit_N : ({is:false})
    
    /**
     * 時限式Exitの有効・無効
     * 時限式Exitの条件、価格での比較(Entryと直近の終値)の有効・無効
     */
//    ,Timed_Exit : ({is:true ,N:80 ,ComparePrice:false})
    ,Timed_Exit : ({is:false})
    
    /**
     * EntrySigの確認で途転(移動平均線のクロス等)
     */
    ,ReversingEntry : ({is:true})
//    ,ReversingEntry : ({is:false})

    /**
     * Entryの設定
     */
//    ,Entry : ({is:"AllSig",TIMING:"b4Closing",BUY_SELL:"Both"})
    ,Entry : ({is:"AllSig",TIMING:"b4Closing",BUY_SELL:"Buy"})
//    ,Entry : ({is:"NewSig",TIMING:"NextClose",BUY_SELL:"Both"})
    
    /**
     * EntrySignalの設定
     */
//    ,EntrySig : ({is:"Target",1:"EntrySig"}) 
//    ,EntrySig : ({is:"Target",1:"EMA150"})
    ,EntrySig : ({is:"doubleEMA",1:"EMA50",2:"EMA100",SIG:"Cross",SLOPE:false}) //,SIG:Over / Cross
//    ,EntrySig : ({is:"doubleEMA",1:"EMA100",2:"EMA350",SIG:"Cross",SLOPE:true}) //,SIG:Over / Cross
//    ,EntrySig : ({is:"TripleEMA",1:"EMA150",2:"EMA250",3:"EMA350"}) // 3より1と2が上にあるとき、1と2のGCでエントリー
//    ,EntrySig : ({is:"FilteredHL" ,Filter:"Filter_50_300" ,H:"High20" ,L:"Low20" ,SIG:"Cross"}) // 
//    ,EntrySig : ({is:"FilteredMACD" ,Filter:"Filter_50_300" ,1:"MACD2_6_19" ,SIG:"Cross"}) // 
    
    /**
     * ピラミッティングの設定
     * Over or Cross LOのラインを超えていれば良いとするか、LOを上抜けたタイミングだけでエントリーするか
     * Fix(固定)or Change(変動)LOとSOの計算値を毎日計算し直すか、初回の計算値で固定とするか。
     */
    ,LO : ({is:"Turtle" ,1:"EntryPrice" ,2:"ATR20" ,N:1 ,SIG:"Over" ,CALC:"Change"}) // N:1/2
    
    /**
     * 分散投資の上限の設定
     */
    ,LIMIT_TOTAL         : 24
    ,LIMIT_LONG          : 12
    ,LIMIT_SHORT         : 12
    ,LIMIT_YELLOW        : 6.4 //10.4
    ,LIMIT_RED           : 4.4 //6.4
    ,LIMIT_SINGLE        : 4.4
    ,LIMIT_N_Status      : "4u"        // {String} 4u or false 上限をリスク由来とするか、4とするか。リスク由来の場合は、4u以降も、EntrySig→LOで追加していく。
//    ,Correl_Risk         : true        // {boolean} 相関関係のリスクを考慮するかどうか
    ,Correl_Risk         : false
    ,Filter_CorrelRed    : false       // {boolean} false 0.7以上の相関リスクの銘柄のエントリーを制限する
    ,NumDays_Same_Factor : 25          // {Number} 強相関のエントリーを制限するときに、同じ要因のシグナルと見なす日数
    
    /**
     * 同時に出たシグナルから強いトレンドを抽出
     * Order_by (FilStrength,DI_14,DX,ADX_14) Order_by_Strength が有効なときに、何をもとに比較するかを指定する(ユニットの項目)
     * 昇順・降順を指定する(昇順:小▷大、降順:大▷小)
     */
//    ,Check_Strength :({is:true ,Order_by:"ADX_14" ,ASC_DESC:"DESC"})
    ,Check_Strength :({is:false})
    
    /**
     * PLフィルターの設定
     * その銘柄の直前のトレードが利益だった場合はトレードを控える
     * N比で何倍以上の利益をPLフィルターの対象とするか
     */
//    ,PL_FILTER : ({is:true ,N:5 ,ChgPctN_Value:true ,ChgPctATR:true})
    ,PL_FILTER : ({is:false})
  };

目次へ

エントリーシグナルの設定

エントリーシグナルの細かな条件は、「2. Back Test.gs」の以下の箇所で設定できます。

var WORK.Units.ITM.EntrySig (2. Back Test.gs)

               ,EntrySig : function(){
                 _.u[_.ul("EntrySig")] = (function(){ // Entry Signal スキームの切り替え
                   if(_.first) return "";
                   var entry_sig_is = ST.EntrySig.is;
                   
                   function switch_(arg){
                     var cases = {
                       "Target": function(){
                         return _.t_[_.tl(ST.EntrySig[1])]-0
                       }
                       ,"doubleEMA": function(){
                         var
                          s  = _.u[_.ul(ST.EntrySig[1])]  ,m  = _.u[_.ul(ST.EntrySig[2])]
                         ,s_ = _.u_[_.ul(ST.EntrySig[1])] ,m_ = _.u_[_.ul(ST.EntrySig[2])]
                         ,slopeL = (ST.EntrySig.SLOPE === false ? true : ST.EntrySig.SLOPE === true && s_ < s && m_ < m ? true : false)
                         ,slopeS = (ST.EntrySig.SLOPE === false ? true : ST.EntrySig.SLOPE === true && s_ > s && m_ > m ? true : false)
                         ,buy_sell = ST.EntrySig.BUY_SELL
                         ;
                         if ( s == null || m == null ) throw new Error("EntrySig 必要な計算値が読み込まれていません");
                         if ( ST.EntrySig.SIG === "Over" ) {
                           if ( m < s && slopeL ) return 1;
                           if ( s < m && slopeS ) return -1;
                         }
                         if ( ST.EntrySig.SIG==="Cross" ) {
                           if ( s_ <= m_ && m < s && slopeL ) return 1;
                           if ( m_ <= s_ && s < m && slopeS ) return -1;
                         }
                         return 0;
                       }
                       ,"TripleEMA": function(){
                         var
                          s=_.u[_.ul(ST.EntrySig[1])]   ,m=_.u[_.ul(ST.EntrySig[2])]   ,l=_.u[_.ul(ST.EntrySig[3])]
                         ,s_=_.u_[_.ul(ST.EntrySig[1])] ,m_=_.u_[_.ul(ST.EntrySig[2])] ,l_=_.u_[_.ul(ST.EntrySig[3])]
                         ;
                         if(s==null || m==null || l==null) throw new Error("EntrySig 必要な計算値が読み込まれていません");
                         if(((l_>=s_ && s_>=m_)||(s_>=l_ && l_>=m_)) && s>=m && m>=l) return 1;
                         if(((m_>=s_ && s_>=l_)||(m_>=l_ && l_>=s_)) && l>=m && m>=s) return -1;
                         return 0;
                       }
                       ,"FilteredHL": function(){
                         var
                          fil=_.u[_.ul(ST.EntrySig.Filter)] 
                         ,h  =_.u[_.ul("High")]         ,l  =_.u[_.ul("Low")]
                         ,hN_=_.u_[_.ul(ST.EntrySig.H)] ,lN_=_.u_[_.ul(ST.EntrySig.L)]
                         ;
                         if(hN_==null || lN_==null) throw new Error("EntrySig 必要な計算値が読み込まれていません");
                         if(fil>0 && h>hN_) return 1;
                         if(fil<0 && l<lN_) return -1;
                         return 0;
                       }
                       ,"FilteredMACD": function(){
                         var fil=_.u[_.ul(ST.EntrySig.Filter)] ,macd2=_.u[_.ul(ST.EntrySig[1])];
                         if(fil==null || macd2==null) throw new Error("EntrySig 必要な計算値が読み込まれていません");
                         if(ST.EntrySig.SIG==="Over"){
                           if(fil>0 && macd2>0) return 1;
                           if(fil<0 && macd2<0) return -1;
                         }
                         if(ST.EntrySig.SIG==="Cross"){
                           var macd2_=_.u_[_.ul(ST.EntrySig[1])];
                           if(!GU._is("Number" ,macd2_))     return 0;
                           if(fil>0 && macd2_< =0 && macd2>0) return 1;
                           if(fil<0 && macd2_>=0 && macd2<0) return -1;
                         }
                         return 0;
                       }
                       ,_default: function(){
                         throw new Error("EntrySig が正しく設定されていません。");
                       }
                     };
                     return (Object.hasOwnProperty.call(cases,arg) && cases[arg] || cases._default)();
                   };
                   return switch_(entry_sig_is);
                 })();
               }

目次へ

イグジットシグナルの設定

イグジットシグナルの細かな条件は、「2. Back Test.gs」の以下の箇所で設定できます。

var WORK.Units.ITM.ExitSig (2. Back Test.gs)

               /**
                * イグジットシグナル
                * -- 前日のユニット計算表で算出された計算値をもとに、イグジットシグナルを判断する
                * -- また、前日のExitの計算値は、前日のExitPriceで計算するべきだが、ここで算出している(スキーム追加時の編集箇所が1箇所で済む)
                * -- 前日のユニット計算表に入っているテクニカルの値は、前々日の値動きをもとに計算されたもの。前日のExitPriceを計算するには、前日のユニット計算表のテクニカルの値を使えば良い
                */
               ,ExitSig :function(){
                 _.u[_.ul("ExitSig")] = (function(){
                   if(_.first || ST.ExitSig.is===false || _.u_[_.ul("EntryPrice")]==0) return "";
                   var
                    exit_sig_is = ST.ExitSig.is
                   ,ls_ = _.u_[_.ul("LongShort")]
                   ,h   = _.u[_.ul("High")]
                   ,l   = _.u[_.ul("Low")]
                   ;
                   function switch_(arg){
                     var cases = {
                       "CrossEMA": function(){
                         _.u_[_.ul("ExitPrice")] = _.t[_.tl("Close")]-0; //売買履歴の確認用
                         var s=_.u[_.ul(ST.ExitSig[1])] ,s_=_.u_[_.ul(ST.ExitSig[1])] ,m=_.u[_.ul(ST.ExitSig[2])] ,m_=_.u_[_.ul(ST.ExitSig[2])];
                         if(s==null || m==null) throw new Error("ExitSig 必要な計算値が読み込まれていません");
                         if(ls_===1  && s_>m_ && m>s) return 1;
                         if(ls_===-1 && m_>s_ && s>m) return 1;
                         return 0;
                       }
                       ,"Target": function(){
                         if(_.u_[_.ul(ST.ExitSig[1])]==null) throw new Error("ExitSig 必要な計算値が読み込まれていません");
                         _.u_[_.ul("ExitPrice")] = _.u_[_.ul(ST.ExitSig[1])]-0; //売買履歴の確認用
                         if(ls_===1  && _.u_[_.ul("ExitPrice")] >l) return 1;
                         if(ls_===-1 && _.u_[_.ul("ExitPrice")] <h) return 1;
                         return 0;
                       }
                       ,"HL": function(){
                         if(_.u_[_.ul(ST.ExitSig.L)]==null || _.u_[_.ul(ST.ExitSig.H)]==null) throw new Error("ExitSig 必要な計算値が読み込まれていません");
                         _.u_[_.ul("ExitPrice")] = ls_===1 ? _.u_[_.ul(ST.ExitSig.L)]-0 : ls_===-1 ? _.u_[_.ul(ST.ExitSig.H)]-0 : "";
                         if(ls_===1  && _.u_[_.ul("ExitPrice")] >l) return 1;
                         if(ls_===-1 && _.u_[_.ul("ExitPrice")] <h) return 1;
                         return 0;
                       }
                       ,_default: function(){
                         throw new Error("ExitSig が正しく設定されていません。");
                       }
                     };
                     return (Object.hasOwnProperty.call(cases,arg) && cases[arg] || cases._default)();
                   };
                   return switch_(exit_sig_is);
                 })();
               }

目次へ

3-7. データを分析する

検証結果が出力されるスプレッドシートに分析用のシートを用意しておくと、統計的な分析結果が自動的に出来上がります。

table fund changes 投資の資金推移表 table investment record summary 投資の売買表

目次へ

3-8. 視覚化して分析する

ここはPythonでJyupiterをいじれる人が対象です。PythonのPlotlyを使用することで、検証結果の膨大なデータをスムーズにグラフ化することができます。

どんなことをしているのかを知りたい方も、どうぞクリックしてみてください。そんなに複雑なコードを書いてるわけではないです^^

plotly DJI entry exit グラフ NYダウ エントリー イグジット 値洗い plotly asset changes グラフ 資金の推移

目次へ

4. まとめ

さてさて、
とんでもなく長い記事になってしまいました(詰め込み過ぎかな・・)。

おそらく、説明が行き届いていない箇所が、おそろしくたくさんあることと思います。

恐縮ですが、あらかじめその点はご了承いただいて、わからない点があればどんどんコメントをください。コメントいただいたものから、適宜、説明を追加していこうと思います!

また、ここに記載されている内容の多くは、Webで情報収集しながら独学でせっせと勉強したものです。もう、元ネタがなんだかまったくもってわからないのですが、良い情報を提供してくださっているWeb界隈の皆さんには本当に感謝しています。本当にいつも勉強になっています!ありがとうございますm(_ _)m

この記事は、「情報を良い流れでまわしていきたいなー」みたいな気持ちで書いています。動作の保証をできるものではないですが、どうぞ自由にお使いください。

このプログラムを作る、検証をする、そんな一連の取り組みのおかげで、プログラミングも多少そうですが、何より投資にめちゃくちゃ詳しくなりました。頭の中に投資のシミュレーターがある感じです。自信をもって投資ができています。

しかし、独学ゆえに、わからない点、至らない点もたくさんあります(とくにプログラミング。クソコードとか言われるものもあるんじゃないだろうか・・)。お気づきの方はご指摘いただけるとうれしいです。勉強になります。

thank you ありがとう

目次へ

Back to Top

Investment Tech Hack

Sorry... doesn't support your browser

To get the best possible experience using our site we recommend that you upgrade to a modern web browser. Investment Tech Hackではご利用中のブラウザサポートはしていません。
Internet Explorerのアップグレード行う、もしくはその他のブラウザを使用しての閲覧をお願いします。