Pythonで自動売買Webアプリ自作への道@ StreamlitでCurveFittingアプリ作成編

Python code
スポンサーリンク

こんにちは、ときか姉です♪

10日ほど前に、PythonだけでWebアプリをかけるフレームワークStreamlitを紹介しましたが(以下記事↓)、今回はその続きとなります。

Streamlitを用いると、WebアプリをPythonだけであまりにも簡単に書けるため色々と触ってみました。新しいことを始めるのは楽しいですね♪
最近コーディングの時間が増え、簡単なデモを作成したり自身が過去に書いたコードを順次移植中です。
今後、株式情報を取得・表示するウェブアプリや、将来的にはセミorフルオートの自動売買アルゴリズムを実装したアプリなども公開していければと思います。

今回は、その前段階となる簡単な解析アルゴリズムと描画を実装したアプリ作成の練習として、Streamlitを用いたCurveFittingウェブアプリを作成してみました♪

CurveFittingといえは、様々な分野で用いられる非常に基本的なデータ解析手法です。特に、時間情報を含んだデータに関しては、物理や工学における信号処理はもちろんのこと、株式やBitCoinの価格などのような金融時系列解析においても頻繁に用いられます。そのため、より発展的なアプリを作成するための練習としては、簡単ながらもとても良い題材かと思います。

今回は、以下の動画のようなCurveFittingウェブアプリを作りましたので、その作り方に関して順を追って解説していきたいと思います。

動画だけでは伝わりにくいかも知れませんので、本アプリの概要を以下に記します。

右側のメインパネル

  • シグモイド関数にノイズを加えたデータを表示(青点)
  • そのデータに対してLineかSigmoidによるフィッテイング(赤線)
  • フィッティングの結果得られたパラメータを関数形で表示(タイトル下の式)
  • フィッティングの結果得られたCovariation Matrixを表示(下段ヒートマップ)

左側の補助パネル

  • フィッティングの手法をリストから選択(Line or Sigmoid)
  • データに加えるノイズ強度をスライダーバーで選択

初心者の方はこの記事を読んで頂くと以下が身につきます。

  • Scipyを用いたカーブフィッティング
  • Streamlitを用いたUI(スライダーバーやリストボックス)の扱い
  • PythonでのLatexの扱い方
  • Seabornのヒートマップ表示

それでは順を追って説明していきたいと思います。手っ取り早く完全版コードのみ見たいor欲しいという方は文末をご参照ください

[トレード口座開設はコチラ↓]

ライブラリのインポートとFitting関数の定義

ライブラリは、定番のnumpy, matplotlib, そして必須ではないのですがヒートマップ用のseabornです。描画がきれいなので含めました。そしてウェブアプリのためのstreamlit, 関数フィッティングのためにscipy.optimizeからcurve_fitをインポートします。
また、今回フィッティング関数として、シグモイド関数と一次関数を用いますので、それぞれを定義しておきます。

import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
import seaborn as sns

# Fittingに用いるシグモイド関数
def sigmoid(x, m, k, x0, c):
   y = m / (1 + np.exp(-k*(x-x0))) + c
   return y

# Fittingに用いる一次関数
def line(x, a, b):
   y = a * x + b
   return y

ライブラリがないとimport errorが出る場合にはpipやcondaでインストールしてください。

# 例:
pip install streamlit
pip install seaborn

関数フィッティング(scipyのcurve_fit)

関数フィッティングには、scipy.optimizerのcurve_fitを用います。以下のような書式になります。

opt, cov = curve_fit(func, x, y, init_params, maxfev=1000)

ここで、x, yは元データです。funcはフィッティングに用いる関数で、init_paramsはその初期パラメータです(真の値から遠くなりすぎない範囲で適当に設定しています)。maxfevというのはfittingまでの繰り返し回数です。この値が大きすぎると、解の探索に時間がかかってしまうため、パラメータ変更時の描画がもっさりしてしまいます。また、設定なしだと収束判定が終了しなかったというエラーが出てしまうことがあります。今回のアプリでは、条件を変えた際にリアルタイムで描画したいために少なめの1,000に設定しています。
戻り値のoptがフィッティングを行った結果得られたパラメータです。covはCovariation Matrixで、パラメータ数をNとするとN x Nの行列となっています。
このoptと例えば元データのxを用いるとフィッティング曲線を求めることができます。

opty = func(x, *opt)

また、取得した最適パラメータを用いて、のちに式として表示したいので以下のように文字列として取得しておきます。今回はLatexフォーマットで描画したいため、頭にrを付けます

disp_format = r'y = \frac{%f}{1 + e^{-%f (x - %f)}} + %f'    
label=(disp_format % (opt[0], opt[1], opt[2], opt[3]))

curve_fitを用いて今回のようにシグモイド関数のフィッティングを行い、フィッティングパラメータと式のための文字列を出力する関数を作るには以下のようになります。

# シグモイド関数によるフィッティングを行う関数
def fitting_sigmoid(x,y,init_params):
   opt, cov = curve_fit(sigmoid, x, y, init_params, maxfev=1000)
   opty = sigmoid(x, *opt)
   disp_format = r'y = \frac{%f}{1 + e^{-%f (x - %f)}} + %f'    
   label=(disp_format % (opt[0], opt[1], opt[2], opt[3]))
   return opty, label, cov
   

戻り値は、fitting曲線であるopty, 最適パラメータの関数描画であるlabel, covです。一方の直線近似は以下のようになります。

# 1次関数によるフィッティングを行う関数
def fitting_line(x,y,init_params):
   opt, cov = curve_fit(line, x, y, init_params, maxfev=1000)
   opty = line(x, *opt)
   disp_format = r'y = %fx + %f'    
   label=(disp_format % (opt[0], opt[1]))
   return opty, label, cov

シグモイドと直線の関数は一つにまとめることもできますが、今回は(やや冗長ですが)このまま2つの関数を用います。

SliderbarとSelectboxの配置

まずはアプリタイトルを表示します。streamlitのtitle関数を用います。

st.title('Curve fitting app')

グラフ描画のためのfigureオブジェクトを用意します。

fig=plt.figure() # Figureキャンバスの生成
ax=fig.add_subplot(211) # パネルの生成

xdataを用意します。

# サンプルデータの作成onset,offset,d=0,200,0.5 # データの範囲x = np.arange(onset,offset,d) # xデータ生成

二種のUIを追加します。ノイズ強度を取得するにはslider(変数名d=0~100)を、フィッティング手法の選択にはselectbox(変数名selected_item=’Line’ or ‘Sigmoid’)を定義します。

d = st.slider('Noise intensity', value=10, min_value=0, max_value=100)
selected_item=st.selectbox('Fitting function',('Line', 'Sigmoid'))

すると、以下のようにタイトル直下にスライダーとリストボックスが描画されます。

これでも構わないのですが、仮にUIをどんどん追加していくとなるとグラフが下に押しやられて見づらくなってしまいます。streamlitには、表示・非表示を切り替えれるUIオブジェクトとしてsidebarがあります。以下のように、sliderとselectboxの前にsidebarを追記します。

d = st.sidebar.slider('Noise intensity', value=10, min_value=0, max_value=100)
selected_item=st.sidebar.selectbox('Fitting function',('Line', 'Sigmoid'))

このように、sidebarの子オブジェクトとしてsliderとselectboxを追加すると、これらUIが以下のように左側にサイドバーとして現れ、すっきりしたレイアウトになります。

このサイドバーは’x’印をクリックすると非表示にでき、’>’印を押すと再表示されます。

このように、UIの定義・配置が非常に簡単に行えるというのが、Streamlitの使いやすい点です。

次に、このスライダーを通じて取得した値をノイズ強度dとし、0から1の一様乱数にかけたものをノイズとして元データに足します。

# Slider: ノイズ強度の調整用d = st.sidebar.slider('Noise intensity', value=10, min_value=0, max_value=100)
y = sigmoid(x,120,0.1,100,20) # Sigmoidデータの生成(パラメータは適当)
y = y + d*np.random.rand(len(y)) # ノイズの印加
ax.plot(x,y,'.b') # 元データの描画

スライダーバーを動かすと、ノイズ強度が変更されその都度グラフも自動的に更新されます。(ノイズの与え方が不自然ですが、簡略化のため敢えてこのようにしています。気になる方(特に物理系)は適宜正規分布などに置き換えてください。その際スライダーバーの範囲指定なども変更する必要があります。)

[Streamlitをもっと学ぶなら↓]

Fittingの実施と結果の描画

このデータに対して行うフィッティングですが、リストボックスの選択肢に応じて実施します。

# 上記Selectboxの選択に応じて描画を切り替える
if selected_item=='Line':
    a,b=0.5,50 # 1次関数パラメータの初期値
    init_params = np.array([a,b]) # パラメータの初期値を配列化
    yinit = line(x, *init_params) # 初期パラメータから成る関数
    opty,label,cov=fitting_line(x,y,init_params)
elif selected_item=='Sigmoid':
    m,k,x0,c=np.max(y)*0.9,1,120,np.min(y) # Sigmoidパラメータの初期
    init_params = np.array([m,k,x0,c]) # パラメータ初期値を配列化
    yinit = sigmoid(x, *init_params) # 初期パラメータから成る関数
    opty,label,cov=fitting_sigmoid(x,y,init_params)

(この辺りも辞書を用いたりフラグを立てるなどしてもっときれいにかつ簡略に書くことができますが、見通しの良さを優先し、今回はこのままで進めます。)

次に結果をプロットします。

ax.plot(x, yinit,'--g') # 初期パラメータから成る関数を描画
ax.plot(x, opty, color='r',linewidth=2,alpha=0.5)

冒頭の動画では省略していますが、初期パラメータの関数も描画します。これを最適パラメータの関数と比較することによって、以下の図のようにきちんと収束していることがよりはっきりとわかります(緑点線が初期パラメータ、赤実線がfitting後パラメータ)。

最終的に得られたパラメータを関数として描画します。以下を用いてlatex形式で表示します。

st.latex(r'{}'.format(label)) # 最適パラメータをLatex形式の関数表示

以下のようにタイトル下に関数が描画されます。

最後に、Covariation Matrixをヒートマップで表示します。

ax=fig.add_subplot(223)
sns.heatmap(ax=ax,data=cov,annot=True) # CovMatrixをヒートマップ表示
ax.set_title('Covariation matrix') # ヒートマップのタイトル
fig.tight_layout() # パネル間の幅を自動調整

オプションのannotTrueとすると、ヒートマップのマスに値のテキストも表示します。

Pythonに慣れている方はお気づきかと思いますが、ここまでのグラフ描画は全てmatplotlibのfigureオブジェクトで行っています。こうして描画・体裁を整えたfigureオブジェクトをアプリ上に表示するには以下のようにします。

st.pyplot(fig)

このように、htmlやcssを経由しなくてもmatplotlibの形式をそのままウェブ表示できるのがstreamlitの強みでもあります。そのため、Webアプリ上のグラフ描画と関連して新規に知識を仕入れる必要がありません。

[トレード口座開設はコチラ↓]

コードの完全版(コピペで動きます)

本アプリのコードの完全版を以下に記します。

# -*- coding: utf-8 -*-

import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
import seaborn as sns

# Fittingに用いるシグモイド関数
def sigmoid(x, m, k, x0, c):
   y = m / (1 + np.exp(-k*(x-x0))) + c
   return y

# Fittingに用いる一次関数
def line(x,a,b):
   y = a * x + b
   return y

# シグモイド関数によるフィッティングを行う関数
def fitting_sigmoid(x,y,init_params):
   opt, cov = curve_fit(sigmoid, x, y, init_params, maxfev=1000)
   opty = sigmoid(x, *opt)
   disp_format = r'y = \frac{%f}{1 + e^{-%f (x - %f)}} + %f'    
   label=(disp_format % (opt[0], opt[1], opt[2], opt[3]))
   return opty, label, cov

# 1次関数によるフィッティングを行う関数
def fitting_line(x,y,init_params):
   opt, cov = curve_fit(line, x, y, init_params, maxfev=1000)
   opty = line(x, *opt)
   disp_format = r'y = %fx + %f'    
   label=(disp_format % (opt[0], opt[1]))
   return opty, label, cov

# メイン関数
def main():

   # 図の作成(matplotlibでOK)
   st.title('Curve fitting app') # パネルのタイトル表示
   fig=plt.figure() # Figureキャンバスの生成
   ax=fig.add_subplot(211) # パネルの生成

   # サンプルデータの作成
   onset,offset,d=0,200,0.5 # データの範囲
   x = np.arange(onset,offset,d) # データ生成

   # Slider: ノイズ強度の調整用
   d = st.sidebar.slider('Noise intensity', value=10, min_value=0, max_value=100)
   y = sigmoid(x,120,0.1,100,20) # Sigmoidデータの生成(パラメータは適当)
   y = y + d*np.random.rand(len(y)) # ノイズの印加
   ax.plot(x,y,'.b') # 元データの描画

   # Selectbox: Fitting手法の選択肢用
   selected_item=st.sidebar.selectbox('Fitting function',('Line', 'Sigmoid'))

   # 上記Selectboxの選択に応じて描画を切り替える
   if selected_item=='Line':
       a,b=0.5,50 # 1次関数パラメータの初期値
       init_params = np.array([a,b]) # パラメータの初期値を配列化
       yinit = line(x, *init_params) # 初期パラメータから成る関数
       opty,label,cov=fitting_line(x,y,init_params)
   elif selected_item=='Sigmoid':
       m,k,x0,c=np.max(y)*0.9,1,120,np.min(y) # Sigmoidパラメータの初期
       init_params = np.array([m,k,x0,c]) # パラメータ初期値を配列化
       yinit = sigmoid(x, *init_params) # 初期パラメータから成る関数
       opty,label,cov=fitting_sigmoid(x,y,init_params)
       
   ax.plot(x, yinit,'--g') # 初期パラメータから成る関数を描画
   ax.plot(x, opty, color='r',linewidth=2,alpha=0.5)
   st.latex(r'{}'.format(label)) # 最適パラメータをLatex形式の関数表示

   ax=fig.add_subplot(223)
   sns.heatmap(ax=ax,data=cov,annot=True) # CovMatrixをヒートマップ表示
   ax.set_title('Covariation matrix') # ヒートマップのタイトル
   fig.tight_layout() # パネル間の幅を自動調整
   st.pyplot(fig)

if __name__ == '__main__':
   main()

以上となります。かなり短いコードでも、それなりの動作をするアプリが書けてしまいます。しかもPythonだけです。上記もまだまだ冗長な箇所があるのでさらに短く書くこともできてしまいます。もし、書き方で間違っている点やもっと簡略に書けるなどのご提案ございましたらぜひ教えて下さいね♪

いかがでしたか?楽しんでいただけましたでしょうか?
私もStreamlit歴はまだまだ10日程度ですが、とても便利そうなのでこれからもどんどん書いていきたいと思います。

♪♪♪ Have a nice coding day ♪♪♪

[Streamlitをもっと学ぶなら↓]

[トレード口座開設はコチラ↓]

コメント

タイトルとURLをコピーしました

Fatal error: Uncaught JSMin_UnterminatedStringException: JSMin: Unterminated String at byte 845: "スタイリッシュなGUIを提供してくれるStreamlitでRealtime Curve Fitting♪ in /home/tokikaneesan/tokikaneesan.com/public_html/wp-content/plugins/autoptimize/classes/external/php/jsmin.php:214 Stack trace: #0 /home/tokikaneesan/tokikaneesan.com/public_html/wp-content/plugins/autoptimize/classes/external/php/jsmin.php(152): JSMin->action(1) #1 /home/tokikaneesan/tokikaneesan.com/public_html/wp-content/plugins/autoptimize/classes/external/php/jsmin.php(86): JSMin->min() #2 /home/tokikaneesan/tokikaneesan.com/public_html/wp-content/plugins/autoptimize/classes/external/php/ao-minify-html.php(257): JSMin::minify('\n{\n "@context"...') #3 [internal function]: AO_Minify_HTML->_removeScriptCB(Array) #4 /home/tokikaneesan/tokikaneesan.com/public_html/wp-content/plugins/autoptimize/classes/external/php/ao-minify-html.php(108): preg_replace_callback('/(\\s*)(<script\\...', Array, '<!doctype html>...') #5 /home/tokikaneesan/tokikaneesan.com/public_html/wp-content/plugins/autoptimize/classes/external/php/ao-minify-html.php(47): AO_Minify_HTML->process() #6 /home/tokikaneesan/tokikaneesan.com/public_html/wp-content/plugins/autoptimize/classes/autoptimizeHTML.php(105): AO_Minify_HTML::minify('<!doctype html>...', Array) #7 /home/tokikaneesan/tokikaneesan.com/public_html/wp-content/plugins/autoptimize/classes/autoptimizeMain.php(592): autoptimizeHTML->minify() #8 [internal function]: autoptimizeMain->end_buffering('<!doctype html>...', 9) #9 /home/tokikaneesan/tokikaneesan.com/public_html/wp-includes/functions.php(5420): ob_end_flush() #10 /home/tokikaneesan/tokikaneesan.com/public_html/wp-includes/class-wp-hook.php(324): wp_ob_end_flush_all('') #11 /home/tokikaneesan/tokikaneesan.com/public_html/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters('', Array) #12 /home/tokikaneesan/tokikaneesan.com/public_html/wp-includes/plugin.php(517): WP_Hook->do_action(Array) #13 /home/tokikaneesan/tokikaneesan.com/public_html/wp-includes/load.php(1270): do_action('shutdown') #14 [internal function]: shutdown_action_hook() #15 {main} thrown in /home/tokikaneesan/tokikaneesan.com/public_html/wp-content/plugins/autoptimize/classes/external/php/jsmin.php on line 214