2024年8月12日 星期一

Python 學習筆記 : 在 Django 網頁中用 Matplotlib/mplfinance 繪圖

今天在 "股票發大財-用 Python 預測完轉股市高手精解" 這本書的第 11 章讀到如何在 Django 專案網頁中用 Matplotlib 繪圖, 我覺得這在建構金融資訊系統時很有用, 晚上得空便來實作看看. 關於 Django 用法參考 : 


本篇測試我們只是要在 Django 網頁中顯示 Python 繪圖結果而已, 不會用到資料庫, 也不需要建立 App, 因此可採用最簡化網站配置方式, 將網頁應用程式直接建在專案目錄下, 只要在上層專案目錄 project1 下建立一個專案模板目錄 templates, 以及在下層專案目錄 project1 下建立視圖程式 views.py 即可 :


1. Django 專案的最簡配置用法 :    

使用 django-admin startproject <project_name> 指令建立專案 project1 : 

D:\python\test\django_projects>django-admin startproject project1        

這會在目前工作目錄下建立兩層的 project1 資料夾, 專案主要內容放在第二層專案目錄下 :

D:\python\test\django_projects>tree project1 /f    
列出磁碟區 新增磁碟區 的資料夾 PATH
磁碟區序號為 1258-16B8
D:\PYTHON\TEST\DJANGO_PROJECTS\PROJECT1
│  manage.py
└─project1  
        asgi.py
        settings.py
        urls.py
        wsgi.py
        __init__.py

接下來在上層專案目錄 project1 底下建立一個 templates 專案模板資料夾 : 

D:\python\test\django_projects>mkdir templates 

再次用 tree 顯示樹狀結構, 可見在上層專案目錄下有一個 templates 目錄了 :

D:\python\test\django_projects>tree project1 /f      
列出磁碟區 新增磁碟區 的資料夾 PATH
磁碟區序號為 1258-16B8
D:\PYTHON\TEST\DJANGO_PROJECTS\PROJECT1
│  manage.py
├─project1
│      asgi.py
│      settings.py
│      urls.py
│      wsgi.py
│      __init__.py
└─templates   

然後切換到下層專案目錄 project1 下, 用程式編輯器新增一個 Python 視圖程式 views.py, 再次用 tree 顯示檔案結構 :

D:\python\test\django_projects>tree project1 /f    
列出磁碟區 新增磁碟區 的資料夾 PATH
磁碟區序號為 1258-16B8
D:\PYTHON\TEST\DJANGO_PROJECTS\PROJECT1
│  manage.py
├─project1
│      asgi.py
│      settings.py
│      urls.py
│      views.py   
│      wsgi.py
│      __init__.py
└─templates




接著用記事本開啟下層 project1 目錄下的專案設定檔 settings.py, 搜尋串列變數 "TEMPLATES", 位置大約是在第 54 列左右, 預設內容如下 :

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],                 # 設定專案模板目錄
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

其中的 DIRS 屬性就是用來設定專案模板目錄位置的, 預設為空串列, 請在串列中輸入BASE_DIR/'templates', 然後將 settings.py 存檔 :  

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR/'templates'],    
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

其中 BASE_DIR 為上層專案目錄之絕對路徑, 定義於 DIRS 的前面. 

這樣我們的簡配 Drango 網站結構便準備好了, 只要撰寫 templates 目錄下的網頁模板, 然後編輯 視圖程式 views.py 將變數傳遞給模板網頁顯示, 最後於 urls.py 中設定 URL 請求與視圖程式中函式之對應即可. 

例如要在網頁中顯示 'Hello World" 與現在時間, 先編輯一個如下之模板網頁 helloworld.htm 並儲存於上層專案目錄的 templates 資料夾下 : 

<!doctype html>
<html>
 <head>
  <meta charset="UTF-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Hello World</title>
 </head>
 <body> 
   <h2>Hello World! 現在的時間是 {{now}}</h2>
 </body>
</html>

其中 {{now}} 就是視圖程式 views.py 傳遞過來要顯示的變數, 然後切換到下層專案目錄, 編輯視圖程式 views.py :

from django.shortcuts import render
from datetime import datetime

def helloworld(request):  
    now=datetime.now()       
    return render(request, 'helloworld.htm', locals()) 

此處內建函式 locals() 會把 helloworld() 內的區域變數與其值打包成字典傳給 render() 去給要渲染的網頁 helloworld.htm. 

最後是編輯路由程式 urls.py 設定 URL 與視圖程式內函式的對應 :

from django.contrib import admin
from django.urls import path
from . import views   

urlpatterns = [
    path('admin/', admin.site.urls),
    path('helloworld/', views.helloworld),   # 指定 URL 之對應處理函式
]

此處要從目前目錄匯入 views.py 模組, 然後於 urlpatterns 串列中用 path() 函式為 URL='helloworld/' 路由指定由 views.py 裡的 helloworld() 函式處理. 

至此便完成全部專案了, 在上層專案目錄下啟動開發伺服器 :

python manage.py runserver    

D:\python\test\django_projects\project1>python manage.py runserver    
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
August 12, 2024 - 16:12:34
Django version 4.2.4, using settings 'project1.settings'
Starting development server at http://127.0.0.1:8000/   
Quit the server with CTRL-BREAK.

這時在瀏覽器網址列輸入 http://127.0.0.1:8000/helloworld 即可看到網頁內容 :




以上便是 Django 專案網站的最簡單用法. 


2. 在 Django 專案中用 Matplotlib 繪圖 :    

在上面的簡配 Django 專案網站基礎上, 我們想增加一個 URL 來讓網站顯示 Matplotlib 繪製的圖形, 例如畫一個資產配置圓餅圖可參考之前的 Matplotlib 測試 :


程式如下 : 

import numpy as np
import matplotlib.pyplot as plt
data=[600, 200, 100, 50, 50]                                      #資產配置 (百萬元)
labels=['Stock', 'Bond', 'Cash', 'Gold', 'Real estate']   #資產標籤
plt.pie(data, labels=labels, autopct='%.2f%%')          #繪製圓餅圖
plt.title('Asset Allocation')                                          #設定圖形標題
plt.show()

這會開啟一個繪圖視窗顯示圓餅圖 :




這要如何顯示在 Django 網站上呢? 方法是在視圖程式中先將 Matplotlib 所繪製的圖形存檔到 ByteIO 物件串流中, 然後用 base64 模組的 b64encide() 函式讀取串流後解碼為 png 圖檔, 再將其傳遞給網頁模板渲染. 

首先編輯一個網頁模板 show_piechart.htm 儲存到 templates 資料夾下 :

<!doctype html>
<html>
 <head>
  <meta charset="UTF-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Matplotlib Pie Chart</title>
 </head>
 <body> 
   <h3>顯示 Matplotlib 繪製之圓餅圖</h3>
   <img src="{{img}}">
 </body>
</html>

然後編輯視圖程式 views.py, 添加一個處理函式 show_piechart(), 並於其中撰寫 Matplotlib 程式碼繪製圓餅圖, 但不同之處是先將圖形物件存入 IO 串流緩衝器, 再讀出來轉成 PNG 檔, 完整程式碼如下 :

# views.py
from django.shortcuts import render
from datetime import datetime
import numpy as np
import matplotlib.pyplot as plt
import imp
import sys
from io import BytesIO
import base64

def helloworld(request):  
    now=datetime.now()       
    return render(request, 'helloworld.htm', locals())

def show_piechart(request):  
    imp.reload(sys)     # 避免匯入 Matplotlib 時出現編碼錯誤用
    data=[600, 200, 100, 50, 50]
    labels=['Stock', 'Bond', 'Cash', 'Gold', 'Real estate']
    plt.pie(data, labels=labels, autopct='%.2f%%')   
    plt.title('Asset Allocation')
    buffer=BytesIO()     # 建立 ByteIO 串流物件
    plt.savefig(buffer)    # 將圖形物件存入 ByteIO 串流快取內
    plt.close()  # 避免更新頁面出現異常必須關閉 PyPlot 物件
    base64img=base64.b64encode(buffer.getvalue())   # 讀取 IO 快取編碼為 Base64 圖像
    img=f'data:image/png;base64,{base64img.decode()}'   # 指定網頁媒體為圖片資料
    return render(request, 'show_piechart.htm', {'img': img})

注意, show_piechart() 函式一開始必須呼叫 img.reload(sys), 這是用來解決匯入 Matplotlib 時出現編碼錯誤之用. 其次是當圖形物件存入 BytesIO 串流快取後就須將圖形物件關閉, 否則重新整理此網頁時會出現錯誤. 

最後編輯路由程式 urls.py, 添加 URL='show_piechart/' 與 views.show_piechart 之對應 :

# urls.py
from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('helloworld/', views.helloworld),
    path('show_piechart/', views.show_piechart),
]

這樣便完成全部設定了, 於瀏覽器網址列輸入 http://127.0.0.1:8000/show_piechart/ :




接下來測試利用上面的方法在 Django 網站中繪製 K 線圖. 


3. 在 Django 專案中用 mplfinance 畫 K 線圖  : 

繪製 K 線圖可以使用 mplfinance 套件, 用法參考 :


獲取 K 線圖資料源最方便的方法是利用 yfinance 套件, 用法參考 :


例如繪製近 30 天的 0050 K 線圖, 先匯入 yfinace 與 mplfinance 套件 :

>>> import yfinance as yf    
>>> import mplfinance as mpf      

呼叫 yfinance 的 download() 函式下載 0050 收盤資料, 預設為近 30 天資料 : 

>>> data=yf.download('0050.tw', period='1mo')    
[*********************100%%**********************]  1 of 1 completed
>>> data   
                  Open        High  ...   Adj Close    Volume
Date                                ...                      
2024-07-12  198.199997  198.350006  ...  195.351791  14158267
2024-07-15  197.649994  198.100006  ...  195.699997   9790185
2024-07-16  196.550003  199.000000  ...  196.850006  13255538
2024-07-17  196.399994  196.500000  ...  194.100006  14489778
2024-07-18  188.600006  190.899994  ...  190.600006  17880013
2024-07-19  188.750000  189.000000  ...  186.250000  16453910
2024-07-22  185.399994  185.399994  ...  180.699997  21211846
2024-07-23  183.699997  186.300003  ...  186.300003  12527079
2024-07-24  186.300003  186.300003  ...  186.300003         0
2024-07-25  186.300003  186.300003  ...  186.300003         0
2024-07-26  178.050003  179.449997  ...  179.000000  25906618
2024-07-29  181.800003  182.000000  ...  180.600006   9946264
2024-07-30  179.250000  181.300003  ...  180.600006  13107381
2024-07-31  178.850006  181.149994  ...  180.850006   9078554
2024-08-01  185.000000  185.100006  ...  184.000000  14061699
2024-08-02  178.149994  178.899994  ...  174.699997  29706815
2024-08-05  166.649994  166.649994  ...  158.750000  55577204
2024-08-06  167.300003  170.399994  ...  167.500000  63140961
2024-08-07  169.850006  174.750000  ...  174.149994  31824740
2024-08-08  171.050003  172.399994  ...  170.550003  25841105
2024-08-09  175.000000  176.899994  ...  175.850006  23353756
2024-08-12  177.250000  179.800003  ...  178.050003  17571739

[22 rows x 6 columns] 

然後呼叫 mplfinace 的 plot() 函式指定 type='candle' 即可繪製 K 線圖 : 

>>> mpf.plot(data, type='candle')    




由於 mplfinance 是在 Matplotlib 套件基礎上打造的, 因此可以用上面繪製 Matplotlib 圖形的方法在 Django 上畫 K 線圖. 

先編輯網頁模板 show_0050_candle.htm :

<!doctype html>
<!doctype html>
<html>
 <head>
  <meta charset="UTF-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>0050 Candle Chart</title>
 </head>
 <body> 
   <h3>顯示 mplfinance 繪製之 0050 K 線圖</h3>
   <img src="{{img}}">
 </body>
</html>

在視圖程式需增加匯入 yfinance 與 mplfinance :

import yfinance as yf
import mplfinance as mpf

然後添加 show_0050_candle(), 完整程式碼如下 :

# views.py
from django.shortcuts import render
from datetime import datetime
import numpy as np
import matplotlib.pyplot as plt
import imp
import sys
from io import BytesIO
import base64
import yfinance as yf
import mplfinance as mpf

def helloworld(request):  
    now=datetime.now()       
    return render(request, 'helloworld.htm', locals())

def show_piechart(request):  
    imp.reload(sys)
    data=[600, 200, 100, 50, 50]
    labels=['Stock', 'Bond', 'Cash', 'Gold', 'Real estate']
    plt.pie(data, labels=labels, autopct='%.2f%%')   
    plt.title('Asset Allocation')
    buffer=BytesIO()
    plt.savefig(buffer)
    plt.close()
    base64img=base64.b64encode(buffer.getvalue())
    img=f'data:image/png;base64,{base64img.decode()}'
    return render(request, 'show_piechart.htm', {'img': img})

def show_0050_candle(request):  
    imp.reload(sys)
    data=yf.download('0050.tw', period='1mo')
    fig, axes=mpf.plot(data, type='candle', returnfig=True, figsize=(6, 4))
    buffer=BytesIO()
    fig.savefig(buffer)
    base64img=base64.b64encode(buffer.getvalue())
    img=f'data:image/png;base64,{base64img.decode()}'
    return render(request, 'show_0050_candle.htm', {'img': img})

最後修改 urls.py, 增加 URL='show_0050_candle/' 與 views.show_0050_candle 的對應 :

# urls.py 
from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('helloworld/', views.helloworld),
    path('show_piechart/', views.show_piechart),
    path('show_0050_candle/', views.show_0050_candle),
]

於瀏覽器網址列輸入 http://127.0.0.1:8000/show_0050_candle/ :




參考 :


沒有留言 :