動画:ポートフォリオ最適化(Python)

動画「QUBO++ ケーススタディ ポートフォリオ最適化(Python)」の手引きです。 章ごとに、要点と動画に出てくるプログラム・出力、詳しい説明のページへのリンクをまとめています。 「▶ 動画の … から再生」をクリックすると、その場面から再生します。

QUBO++ ケーススタディ ポートフォリオ最適化(Python)(18:38・YouTube)

この動画で扱うこと:

  • 定期預金と株の比較から、期待リターン・リスク(標準偏差)・リスクプレミアムの考え方
  • 相関係数と、株を組み合わせるとリスクが下がる理由(相関がなければ下がり、相関係数が 1 なら下がらない)
  • 期待リターン $R$ とリスクの 2 乗 $V$(分散)の式と、$V - \lambda R$ を最小にする平均分散モデル
  • 100 株単位の単元数を整数変数で、予算の範囲を qbpp.cons() で書き、実数係数のモジュール pyqbpp.d で解くこと
  • 買う銘柄の数を 4 つまでに制限すること

文章での説明はポートフォリオ最適化のページにあります。動画(ケーススタディ)の一覧に戻る

この動画の銘柄とデータは架空のもので、投資の判断に使うものではありません。

預金と株

▶ 動画の 0:50 から再生

  • 1000 万円を 1 年間置いておくとします。定期預金なら、金利が例えば 2% のとき、1 年後にほぼ確実に 1020 万円になります。
  • 株 X は、値上がりと配当で、平均して 1 年に 10% 増えると見込まれています。平均の増え方の見込みを期待値、年ごとのぶれの大きさを標準偏差と呼びます。株 X の標準偏差は 30% とします。
  • 増え方が山の形(正規分布)に分布するとすると、およそ 3 分の 2 の年は −20%〜+40%、95% の年は −50%〜+70% に入ります。損をする年はおよそ 37%、10 年に 4 年近くあります。
  • 宝くじや賭け事と違い、期待値はプラスです。期待値の 10% と預金の 2% の差 8% をリスクプレミアムと呼びます。ぶれを引き受ける見返りとして、預金より高い期待値が付きます。
  • 以下では、標準偏差をリスク、期待値を期待リターンと呼びます。

相関のない2つの株

▶ 動画の 2:50 から再生

  • 株 Y も、期待リターン 10%、リスク 30% とします。2 つの株の動き方の関係は相関係数 $\rho$ で表します。いつもそろって動くなら 1、まったく関係なく動くなら 0、逆に動く傾向があるとマイナスです。
  • $\rho = 0$ の株 X と Y を 500 万円ずつ買うと、期待リターンは 10% のままで、リスクは 30% から 21.2% に下がります。片方が大きく下がっても、もう片方まで大きく下がるとは限らないからです。
  • リスクの 2 乗を分散と呼びます。株 X、Y に使う金額の割合を $w_1, w_2$、リスクを $\sigma_1, \sigma_2$ とすると、$\rho = 0$ のとき、合計のリスクの 2 乗は 2 つのぶれ $w_1\sigma_1$、$w_2\sigma_2$ の 2 乗の和です。
\[\sigma^2 = (w_1\sigma_1)^2 + (w_2\sigma_2)^2\]
  • 半分ずつ買うので $w_1\sigma_1 = w_2\sigma_2 = 15\%$ で、$\sigma^2 = 15^2 + 15^2 = 450$、$\sigma = \sqrt{450} = 21.2\%$ です。
  • 2 つのぶれを長さ 15 の 2 本の矢印で表すと、$\rho = 0$ では直角に交わり、合わせた矢印の長さが 21.2 になります。15 と 15 を足しても 30 にはなりません。

相関とリスク

▶ 動画の 5:02 から再生

  • 実際の株は同じ市場で取引されるので、相関係数はふつうプラスです。$\rho$ が 0 でないとき、リスクの 2 乗に項が 1 つ加わり、2 つの株がそろって動く分だけ大きくなります。
\[\sigma^2 = (w_1\sigma_1)^2 + (w_2\sigma_2)^2 + 2\rho \times w_1\sigma_1 \times w_2\sigma_2\]
  • 半分ずつ買ったときのリスクは次のとおりです。矢印の図では、$\rho$ が大きいほど 2 本の矢印の間の角度が小さくなります。
$\rho$ 0 0.1 0.2 0.3 0.6 0.9 1
リスク 21.2% 22.2% 23.2% 24.2% 26.8% 29.2% 30%
  • $\rho = 1$ では 2 本の矢印が一直線に並び、リスクは 1 つの株だけを買ったときと同じ 30% です。相関係数が 1 なら分けて買う意味はなく、1 より小さいほど、分けて買うとリスクが下がります。
  • 株の組み合わせをポートフォリオ、分けて買うことを分散投資と呼びます。
  • どの 2 つも $\rho = 0.2$ の株を同じ金額ずつ買うと、リスクは 1 銘柄で 30%、2 銘柄で 23.2%、5 銘柄で 18.0%、10 銘柄で 15.9% と下がりますが、いくら増やしても 13.4% より下にはなりません。残るのは市場全体がそろって上がったり下がったりする分で、リスクプレミアムが付くのはこの分です。

平均分散モデル

▶ 動画の 7:06 から再生

  • 銘柄 $i$ に使う金額の割合を $w_i$、銘柄 $i$ の期待リターンを $\mu_i$、リスクを $\sigma_i$、銘柄 $i$ と銘柄 $j$ の相関係数を $\rho_{ij}$($\rho_{ii} = 1$)とします。
  • 期待リターン $R$ と、ポートフォリオ全体のリスクの 2 乗(分散)$V$ は次のとおりです。
\[R = \sum_i \mu_i w_i, \qquad V = \sum_i \sum_j \rho_{ij}\,\sigma_i \sigma_j\, w_i w_j\]
  • $V$ のうち $i = j$ の項は $(w_i\sigma_i)^2$ です。$i \ne j$ の組は $ij$ と $ji$ の 2 回現れ、$2\rho_{ij} \times w_i\sigma_i \times w_j\sigma_j$ の項になります。銘柄が 2 つなら、前の章の 2 つの株の式と同じです。リスクは $V$ の平方根なので、$V$ を小さくすればリスクも小さくなります。
  • $V$ は小さいほど、$R$ は大きいほどうれしい量です。2 つを 1 つの式にまとめるため、$R$ に係数 $\lambda$ を掛けて $V$ から引いた $V - \lambda R$ を最小にします。$\lambda$ を大きくするほど $R$ を重視します。これがマーコウィッツの平均分散モデルです。

問題

▶ 動画の 9:14 から再生

  • 架空の 10 銘柄(5 業種 × 2)を、予算 1000 万円で買います。
銘柄 業種 株価(円) 期待リターン リスク
A 電機 2,450 11.0% 32%
B 電機 6,180 10.0% 28%
C 自動車 1,870 8.0% 26%
D 自動車 3,320 9.0% 30%
E 銀行 1,240 7.0% 24%
F 銀行 4,060 6.5% 22%
G 食品 2,890 4.5% 15%
H 食品 5,510 5.0% 17%
I 通信 1,530 4.0% 14%
J 通信 3,970 5.5% 18%
  • 相関係数は、同じ業種なら 0.6、違う業種なら 0.2 とします。同じ業種の株は、そろって動きやすいからです。
  • 株は 100 株を 1 単元として、単元単位でしか買えません。1 つの銘柄に使えるのは、予算の 30%、300 万円までとします。

単元と予算

▶ 動画の 10:22 から再生

  • 銘柄 $i$ の単元数を $x_i$(0 以上の整数)、1 単元の金額を $c_i$、予算を $B$ とすると、金額の割合は $w_i = c_i x_i / B$ です。例えば銘柄 A は株価 2,450 円なので、1 単元は 24 万 5 千円です。
  • 割合を単元数で書き換えると、$R$ は $x_i$ の 1 次式、$V$ は 2 次式になります。$V$ の式に現れる $\rho_{ij}\sigma_i\sigma_j$ は 10 × 10 の表に並び、同じ業種の組には相関係数 0.6 が掛かります。
  • 予算の制約として、合計金額を 980 万円以上、1000 万円以下とします。下限がないと、$\lambda = 0$ のとき何も買わないのが最良になってしまいます。使わずに残ったお金は、リスクも期待リターンも 0 です。
  • 単元数の上限 $u_i$ は、300 万円までで買える単元数です(銘柄 A なら 12 単元、294 万円)。単元数の組み合わせは、予算を考えなければおよそ 233 億通りです。
\[\begin{aligned} \text{最小化}\quad & V - \lambda R \\ \text{制約}\quad & 9800 \le \sum_i c_i x_i \le 10000 \quad (\text{千円}) \\ & 0 \le x_i \le u_i \quad (\text{すべての銘柄 } i) \end{aligned}\]

詳しくはポートフォリオ最適化のページをご覧ください。

QUBO++での書き方

▶ 動画の 11:55 から再生

  • 実数の係数:期待リターンやリスクは小数なので、目的関数の係数も小数になります。import pyqbpp.d as qbpp で実数係数のモジュールを使います。QUBO++ が内部で係数を整数に直してから解きます。
  • 整数変数:qbpp.var("x", shape=n, integer=(0, cap)) で単元数の整数変数の配列を作ります。上限 cap をリストで渡すと、銘柄ごとに違う上限になります。整数変数は 2 値の変数に分けずに、整数のまま探索されます。
  • 範囲の制約:予算は qbpp.cons(cost, between=(9800, budget)) で書きます。制約を破ると、重み × 違反量² がペナルティとして加わります。不等式の制約でも補助の変数は要りません。
  • 制約の重みは 1 で十分です。金額は千円単位の整数なので、範囲を外れると少なくとも 1 ずれ、ペナルティは 1 以上になります。一方、目的関数の値は 0.1 にも満たないので、予算の範囲を外れた解は、どれも範囲内の解より悪くなります。

詳しくは実数(double)係数・整数変数・制約のページをご覧ください。

プログラム

▶ 動画の 13:32 から再生

動画のプログラムです(文章のページのプログラムと同じで、5 行目のコメントだけ「リスク」にしています)。

import math

import pyqbpp.d as qbpp

# 銘柄 A〜J の業種、株価(円)、期待リターン、リスク(年率)
sector = [0, 0, 1, 1, 2, 2, 3, 3, 4, 4]
price = [2450, 6180, 1870, 3320, 1240, 4060, 2890, 5510, 1530, 3970]
mu = [0.11, 0.10, 0.08, 0.09, 0.07, 0.065, 0.045, 0.05, 0.04, 0.055]
sigma = [0.32, 0.28, 0.26, 0.30, 0.24, 0.22, 0.15, 0.17, 0.14, 0.18]
n = len(price)
budget = 10000  # 千円
lam = 0.5

# 1 単元(100 株)の金額(千円)と、予算の 30% までで買える単元数
lot = [p // 10 for p in price]
cap = [3000 // c for c in lot]

x = qbpp.var("x", shape=n, integer=(0, cap))

risk = 0
ret = 0
cost = 0
for i in range(n):
    wi = lot[i] / budget
    for j in range(n):
        wj = lot[j] / budget
        rho = 1.0 if i == j else (0.6 if sector[i] == sector[j] else 0.2)
        risk += rho * sigma[i] * sigma[j] * wi * wj * x[i] * x[j]
    ret += mu[i] * wi * x[i]
    cost += lot[i] * x[i]
f = risk - lam * ret + qbpp.cons(cost, between=(9800, budget))
f.simplify()

sol = qbpp.EasySolver(f).search(time_limit=1.0)
for i in range(n):
    if sol(x[i]) > 0:
        print(f"{chr(ord('A') + i)}: {sol(x[i]):2.0f} lots ({lot[i] * sol(x[i]):4.0f} thousand yen)")
print(f"cost = {sol(cost):.0f} thousand yen, return = {100 * sol(ret):.2f}%, "
      f"risk = {100 * math.sqrt(sol(risk)):.2f}%")
  • lot は 1 単元(100 株)の金額で、千円単位にするため株価を 10 で割ります。cap は 300 万円までで買える単元数です。
  • risk、ret、cost は、$V$、$R$、合計金額の式です。wi・wj は 1 単元が予算に占める割合、rho は相関係数(同じ銘柄は 1、同じ業種は 0.6、違う業種は 0.2)で、二重のループで $V$ の 100 個の項を足し合わせます。
  • f は $V - \lambda R$ に予算の制約を加えた式です。整数変数だけの式なので、simplify() で整理します。
  • EasySolver で解を探し、time_limit で探索の時間を 1 秒にします。sol(x[i]) は解での銘柄 i の単元数です。リスクは risk の値の平方根で、期待リターンとリスクは 100 倍してパーセントで表示します。

実行結果

▶ 動画の 15:33 から再生

A:  4 lots ( 980 thousand yen)
B:  2 lots (1236 thousand yen)
C:  5 lots ( 935 thousand yen)
D:  2 lots ( 664 thousand yen)
E:  7 lots ( 868 thousand yen)
F:  2 lots ( 812 thousand yen)
G:  4 lots (1156 thousand yen)
H:  2 lots (1102 thousand yen)
I:  4 lots ( 612 thousand yen)
J:  4 lots (1588 thousand yen)
cost = 9953 thousand yen, return = 6.98%, risk = 12.75%
  • 10 銘柄すべてを、少しずつ買っています。例えば銘柄 A は 4 単元、98 万円です。合計金額は 995 万 3 千円で、予算の範囲に入っています。
  • 期待リターンは 6.98%、リスクは 12.75% です。リスク 12.75% は、どの銘柄のリスク(最も小さい I でも 14%)よりも小さく、分けて買う効果が表れています。

銘柄数の制限

▶ 動画の 16:13 から再生

  • 買う銘柄を 4 つまでに制限します。銘柄 $i$ を買うとき 1 になる 2 値の変数 $y_i$ を加え、次の 2 つの制約を課します。
\[x_i \le u_i\, y_i \quad (\text{すべての銘柄 } i), \qquad \sum_i y_i \le 4\]
  • $y_i = 0$ なら $x_i \le 0$ なので銘柄 $i$ は買えず、$y_i = 1$ なら $x_i \le u_i$ で、もとの範囲と同じです。
  • プログラムでは、予算の制約の後に次の行を加えます。x - qbpp.array(cap) * y は要素ごとに計算した 10 個の式の配列で、0 以下と比べて qbpp.cons() で包むと 10 本の制約になります。2 値変数を含むので、整理は simplify_as_binary() で行います。
# 買う銘柄は 4 つまで: 銘柄 i を買うとき y[i] = 1
y = qbpp.var("y", n)
f += qbpp.cons(x - qbpp.array(cap) * y <= 0)
f += qbpp.cons(qbpp.sum(y) <= 4)
f.simplify_as_binary()
B:  4 lots (2472 thousand yen)
C:  9 lots (1683 thousand yen)
G: 10 lots (2890 thousand yen)
J:  7 lots (2779 thousand yen)
cost = 9824 thousand yen, return = 6.65%, risk = 13.19%
  • 電機・自動車・食品・通信から 1 つずつ、業種が重ならないように選ばれています。銘柄を絞った分、期待リターンは 6.65% に下がり、リスクは 13.19% に上がりました。

まとめ

▶ 動画の 17:55 から再生

  • 預金と株を比べ、期待リターン、リスク、リスクプレミアムを説明しました。相関係数が 1 より小さい株を組み合わせると、リスクが下がります。
  • 期待リターンとリスクのバランスは、$V - \lambda R$ の最小化で決めます。
  • QUBO++ では、実数の係数、整数変数、qbpp.cons() でそのまま書けます。100 株単位、予算の範囲、銘柄の数の制限も、制約として加えられます。

Back to top

Page last modified: 2026.11.07.

© 2026 中野浩嗣, 広島大学