追蹤停損單是股票交易中常用的風險管理策略,能根據股票價格波動調整賣出價格,本文將使用 Python Pandas 示範如何計算追蹤停損單價格以及觸發時間點。同時,本文也將示範如何使用 Pandas 分析美國職棒大聯盟資料,找出最佳球員和最佳打擊順序,並使用視覺化工具呈現分析結果。這些技術對於投資者和棒球迷來說都非常實用,能幫助他們更好地理解市場和比賽。透過 Pandas 提供的強大資料處理和分析能力,可以有效地從資料中提取有價值的資訊,並做出更明智的決策。

計算追蹤停損單價格

許多投資者採用的一種基本交易策略是停損單。停損單是一種投資者下達的買入或賣出股票的訂單,當市場價格達到某一特定點時執行。停損單對於防止巨大損失和保護收益都非常有用。

在典型的停損單中,價格在訂單的有效期內保持不變。例如,如果你以每股100美元的價格購買了一隻股票,你可能會設定一個停損單在每股90美元,以限制你的下跌風險為10%。

策略解析

一種更進階的策略是持續修改停損單的賣出價格,以跟蹤股票價值的上漲。這被稱為追蹤停損單。具體來說,如果同樣的100美元股票上漲到120美元,那麼一個比當前市場價值低10%的追蹤停損單就會將賣出價格調整到108美元。

追蹤停損單永遠不會向下調整,並且始終與購買以來的最高價值掛鉤。如果股票從120美元下跌到110美元,停損單仍將保持在108美元。只有當價格超過120美元時,它才會上調。

實作步驟

首先,我們將使用 Nvidia(NVDA)股票的資料,並假設在2020年的第一個交易日進行購買:

import pandas as pd

# 載入資料
df = pd.read_csv(
    "data/NVDA.csv",
    usecols=["Date", "Close"],
    parse_dates=["Date"],
    index_col=["Date"],
).convert_dtypes(dtype_backend="numpy_nullable")

# 轉換為Series
ser = df.squeeze()

# 檢視前幾行資料
print(ser.head())

輸出結果:

Date
2020-01-02    59.977501
2020-01-03    59.017502
2020-01-06    59.264999
2020-01-07    59.982498
2020-01-08    60.095001
Name: Close, dtype: float64

內容解密:

  1. 資料載入:使用pd.read_csv函式載入Nvidia股票的歷史資料,選擇DateClose列,並將Date列解析為日期格式,然後設定為索引。
  2. 資料轉換:由於pd.read_csv傳回的是DataFrame,我們使用squeeze方法將其轉換為Series,以便於後續操作。
  3. 檢視資料:使用head方法檢視前幾行資料,以確認資料是否正確載入。

計算追蹤停損單價格

使用pd.Series.cummax方法來跟蹤迄今為止的最高收盤價:

ser_cummax = ser.cummax()
print(ser_cummax.head())

輸出結果:

Date
2020-01-02    59.977501
2020-01-03    59.977501
2020-01-06    59.977501
2020-01-07    59.982498
2020-01-08    60.095001
Name: Close, dtype: float64

內容解密:

  1. cummax方法:計算迄今為止的累積最大值,即每個時間點之前的最高收盤價。
  2. 結果解析:可以看到,隨著時間的推移,累積最大值要麼保持不變,要麼隨著新的最高收盤價的出現而增加。

設定10%追蹤停損單

將累積最大值乘以0.9,以設定一個比當前最高價低10%的追蹤停損單:

stop_prices = ser.cummax().mul(0.9)
print(stop_prices.head())

輸出結果:

Date
2020-01-02    53.979751
2020-01-03    53.979751
2020-01-06    53.979751
2020-01-07    53.984248
2020-01-08    54.085501
Name: Close, dtype: float64

內容解密:

  1. 計算追蹤停損價:透過將累積最大值乘以0.9,計算出每個時間點的追蹤停損價格。
  2. 結果解析:追蹤停損價格隨著累積最大值的增加而增加,但永遠不會減少。

識別觸發停損單的時間點

找出收盤價低於或等於追蹤停損價的時間點:

trigger_days = ser[ser <= stop_prices]
print(trigger_days.head())

輸出結果:

Date
2020-02-24    68.320000
2020-02-25    65.512497
2020-02-26    66.912498
2020-02-27    63.150002
2020-02-28    67.517502
Name: Close, dtype: float64

內容解密:

  1. 比較收盤價與停損價:找出收盤價低於或等於追蹤停損價的時間點,這些時間點即為觸發停損單的時間。
  2. 結果解析:列出了觸發停損單的前幾個時間點。

取得首次觸發停損單的時間點

使用pd.Series.idxmax方法找出首次觸發停損單的時間點:

first_trigger_day = (ser <= stop_prices).idxmax()
print(first_trigger_day)

輸出結果:

Timestamp('2020-02-24 00:00:00')

內容解密:

  1. idxmax方法:首先計算一個布林Series,其中True表示收盤價低於或等於追蹤停損價。然後,傳回第一個True值出現的索引,即首次觸發停損單的時間點。
  2. 結果解析:輸出了首次觸發停損單的具體日期和時間。

本章節展示瞭如何利用pandas函式庫計算追蹤停損單價格,並識別觸發停損單的時間點,為投資者提供了一個實用的工具,用於風險管理和收益保護。

棒球資料分析:找出最佳球員與最佳打擊順序

找出棒球選手的最佳表現

美國的棒球運動長期以來一直是深入分析研究的物件,資料收集可以追溯到20世紀初。對於美國職業棒球大聯盟(MLB)的球隊來說,先進的資料分析有助於回答諸如「我應該為X球員支付多少薪水?」和「根據目前的比賽狀態,我應該怎麼做?」等問題。對於球迷來說,同樣的資料可以用來進行無盡的辯論,例如「誰是史上最偉大的球員?」。

本篇食譜將使用從retrosheet.org收集的資料。根據Retrosheet的許可要求,您應該瞭解以下法律宣告:

本文使用的資訊是免費取得的,但版權屬於Retrosheet。有興趣的各方可以聯絡Retrosheet網站www.retrosheet.org。

如何執行

首先,我們讀取總結的資料,並將id列(代表唯一的球員)設定為索引:

df = pd.read_parquet(
    "data/mlb_batting_summaries.parquet",
).set_index("id")
df

輸出結果如下:

            ab    r    h   hr
id                               
abadf001    0    0    0    0
abboa001    0    0    0    0
abboc001    3    0    1    0
abrac001  847  116  208   20
abrea001    0    0    0    0
...       ...  ...  ...  ...
zimmk001    0    0    0    0
zimmr001  255   27   62   14
zubet001    1    0    0    0
zunig001    0    0    0    0
zunim001  572   82  111   41
2183 rows × 4 columns

在棒球運動中,很少有球員能夠在所有統計類別中都表現出色。通常,一名擁有大量全壘打(hr)的球員可能更強大,能夠將球擊得更遠,但可能不如專門收集大量安打(h)的球員那樣頻繁。使用pandas,我們很幸運地不需要逐一檢視每個指標;只需簡單地呼叫pd.DataFrame.idxmax,就能檢視每一列,找到最大值,並傳回與該最大值相關的行索引值:

df.idxmax()

輸出結果如下:

ab      semim001
r       freef001
h       freef001
hr      judga001
dtype: string

從結果中可以看出,球員semim001(Marcus Semien)擁有最多的打數,freef001(Freddie Freeman)擁有最多的得分和安打,而judga001(Aaron Judge)則擊出了最多的全壘打。

如果想要更深入地瞭解這些優秀球員在所有類別中的表現,可以利用pd.DataFrame.idxmax的輸出結果,隨後呼叫pd.Series.unique來取得值,並將其用作整體pd.DataFrame的掩碼:

best_players = df.idxmax().unique()
mask = df.index.isin(best_players)
df[mask]

輸出結果如下:

           ab    r    h   hr
id                              
freef001 1849  368  590   81
judga001 1487  301  433  138
semim001 1979  338  521  100

更多內容

為了更好地視覺化這些資料,可以使用pd.DataFrame.style.highlight_max來突出顯示這些球員在哪些類別中表現最佳:

df[mask].style.highlight_max()

輸出結果將會是一個DataFrame,其中每個欄位的最大值都被突出顯示。

瞭解哪個打擊順序得分最多

在棒球比賽中,球隊允許9名打擊手按照順序上場,1代表第一位上場的打擊手,9代表最後一位。通常,球隊會將一些最好的擊球手放在「打擊順序的前面」(即較低的數字位置),以最大限度地增加他們再次上場並得分的機會。然而,這並不總是意味著第一位上場的打擊手總是第一個得分。

如何執行

與前面的食譜類別似,我們將使用從retrosheet.org取得的資料。對於這個特定的資料集,我們將設定年份和球佇列作為行索引,其餘列顯示打擊順序中的位置:

df = pd.read_parquet(
    "data/runs_scored_by_team.parquet",
).set_index(["year", "team"])
df

輸出結果如下:

               1    2    3 ...    7    8    9
year team                               
2000 ANA     124  107  100 ...   77   76   54
     ARI     110  106  109 ...   72   68   40
     ATL     ... 

利用pd.DataFrame.idxmax,我們可以檢視每個年份和球隊哪個位置得分最多。然而,在這個資料集中,我們希望pd.DataFrame.idxmax識別的索引標籤實際上是在列中,而不是在行中。幸運的是,pandas仍然可以透過axis=1引數輕鬆計算出來:

df.idxmax(axis=1)

輸出結果如下:

year team   
2000 ANA      1
     ARI      ...

更多內容

我們可能想要進一步探索並回答這個問題:對於第一位上場的打擊手得分最多的球隊來說,第二位上場的打擊手得分情況如何?

為了計算這一點,我們可以建立一個掩碼來過濾第一位上場的打擊手得分最多的球隊,從我們的資料集中刪除該列,然後重複使用相同的pd.DataFrame.idxmax方法來識別下一個位置:

mask = df.idxmax(axis=1).eq("1")
df[mask].drop(columns=["1"]).idxmax(axis=1).value_counts(normalize=True)

輸出結果將顯示第二位上場的打擊手在第一位上場的打擊手得分最多的球隊中得分的比例。

圖表翻譯:

此圖示呈現了不同打擊順序對得分的影響。 圖表翻譯: 詳細解釋了圖表中呈現的不同打擊順序對得分影響的比例。

資料視覺化:探索性資料分析與呈現的關鍵

在進行探索性資料分析(Exploratory Data Analysis, EDA)時,視覺化是一種至關重要的工具。透過視覺化,我們可以快速理解資料的分佈、識別異常值和缺失資料,並引發進一步的分析。在本章中,我們將探討如何使用 pandas 和 Matplotlib 進行資料視覺化。

從聚合資料建立圖表

pandas 提供了 pd.Series.plotpd.DataFrame.plot 方法,讓我們能夠輕鬆地視覺化資料。首先,我們來建立一個簡單的 pd.Series,代表一週內的書籍銷售量:

ser = pd.Series(
    (x ** 2 for x in range(7)),
    name="book_sales",
    index=(f"Day {x + 1}" for x in range(7)),
    dtype=pd.Int64Dtype(),
)
ser.plot()

內容解密:

  • 這段程式碼建立了一個 pd.Series 物件,代表一週內的書籍銷售量。
  • 使用 ser.plot() 方法建立一個線性圖表,X 軸代表日期,Y 軸代表銷售量。

預設情況下,pd.Series.plot 方法會建立一個線性圖表。然而,如果我們想要顯示每個日期的銷售量,可以使用 kind="bar" 引數建立一個柱狀圖:

ser.plot(kind="bar")

內容解密:

  • 這段程式碼使用 kind="bar" 引數建立一個柱狀圖表,X 軸代表日期,Y 軸代表銷售量。
  • 每個柱子代表該日期的銷售量。

我們也可以使用 kind="barh" 引數建立一個水平柱狀圖:

ser.plot(kind="barh")

內容解密:

  • 這段程式碼使用 kind="barh" 引數建立一個水平柱狀圖表,Y 軸代表日期,X 軸代表銷售量。
  • 每個柱子代表該日期的銷售量。

此外,我們還可以建立面積圖和圓餅圖:

ser.plot(kind="area")
ser.plot(kind="pie")

內容解密:

  • 面積圖使用 kind="area" 引數建立,類別似於線性圖表,但會填滿線下的區域。
  • 圓餅圖使用 kind="pie" 引數建立,每個扇區代表該日期的銷售量。

從 DataFrame 建立圖表

當我們有多個欄位時,可以使用 pd.DataFrame.plot 方法建立圖表。例如,我們可以建立一個包含書籍銷售量和退貨量的 DataFrame:

df = pd.DataFrame({
    "book_sales": (x ** 2 for x in range(7)),
    "book_returns": [3, 2, 1, 0, 1, 2, 3],
}, index=(f"Day {x + 1}" for x in range(7)))
df.plot()

內容解密:

  • 這段程式碼建立了一個包含書籍銷售量和退貨量的 DataFrame。
  • 使用 df.plot() 方法建立一個線性圖表,每個欄位代表一條線。

預設情況下,pd.DataFrame.plot 方法會建立一個線性圖表,每個欄位代表一條線。我們也可以使用 kind="bar" 引數建立一個柱狀圖:

df.plot(kind="bar")

內容解密:

  • 這段程式碼使用 kind="bar" 引數建立一個柱狀圖表,每個欄位代表一組柱子。
  • 預設情況下,每個欄位的柱子會並排顯示。

如果我們想要堆積疊顯示每個欄位的柱子,可以使用 stacked=True 引數:

df.plot(kind="bar", stacked=True)

內容解密:

  • 這段程式碼使用 stacked=True 引數堆積疊顯示每個欄位的柱子。

同樣地,我們也可以建立水平柱狀圖:

df.plot(kind="barh")
df.plot(kind="barh", stacked=True)

內容解密:

  • 這段程式碼使用 kind="barh" 引數建立一個水平柱狀圖表。
  • 使用 stacked=True 引數堆積疊顯示每個欄位的柱子。

資料視覺化:區域圖與直方圖的應用

在資料分析中,視覺化是一種強大的工具,可以幫助我們更好地理解資料的分佈和趨勢。本章節將介紹如何使用 pandas 的 plot 方法來建立區域圖和直方圖,並探討如何自定義這些圖表以滿足特定的需求。

區域圖的應用

區域圖是一種用於展示資料累積效果的視覺化工具。當使用 pandas 的 DataFrame 繪製區域圖時,預設行為是堆積疊列資料:

df.plot(kind="area")

若要取消堆積疊,可以傳遞 stacked=False 引數,並使用 alpha 引數引入透明度,以提高視覺化效果:

df.plot(kind="area", stacked=False, alpha=0.5)

內容解密:

  • kind="area" 指定繪製區域圖。
  • stacked=False 取消列資料的堆積疊。
  • alpha=0.5 設定透明度為 0.5,使圖表更具層次感。

自定義視覺化

pandas 的 plot 方法提供了多種引數來自定義視覺化結果,包括標題、標籤、顏色等。

設定標題與顏色

可以使用 title 引數為圖表新增標題:

ser.plot(kind="bar", title="每日書籍銷售量")

同樣地,可以使用 color 引數更改線條、條形和標記的顏色:

ser.plot(kind="bar", title="每日書籍銷售量", color="seagreen")

內容解密:

  • title 引數為圖表新增標題。
  • color 引數更改圖表的顏色,可以使用 RGB 十六進位製程式碼或 Matplotlib 命名顏色。

控制座標軸與網格

可以使用 xlabelylabel 引數控制 x 軸和 y 軸的標籤:

ser.plot(kind="bar", title="每日書籍銷售量", xlabel="日期", ylabel="銷售量")

同樣地,可以使用 grid 引數控制網格的顯示:

ser.plot(kind="bar", title="每日書籍銷售量", grid=False)

內容解密:

  • xlabelylabel 引數用於設定 x 軸和 y 軸的標籤。
  • grid=False 隱藏網格線。

繪製分佈圖

直方圖是一種用於展示資料分佈的視覺化工具。可以使用 kind="hist" 引數建立直方圖:

ser.plot(kind="hist")

此外,可以透過 bins 引數調整直方圖的箱子數量,以更好地展示資料的分佈特徵:

ser.plot(kind="hist", bins=100)

內容解密:

  • kind="hist" 指定繪製直方圖。
  • bins=100 設定直方圖的箱子數量為 100。

Kernel Density Estimate (KDE) 圖

KDE 圖是一種用於展示資料分佈的另一種視覺化工具。雖然本章節未直接展示 KDE 圖的範例,但它是一種強大的工具,可以提供比直方圖更平滑的分佈估計。