ポートフォリオのリバランス:効果を計算してみた(リバランス編)


前の記事では株価情報を取ってくるところまで紹介しました。

目次

2. リバランス編

こちらでは実際にリバランスの効果を確かめるために税金・手数料のややこしいところも
ざっくりモデル化して計算してみます。

2.1 まずは結果から

コードが取っ散らかっちゃったので関数(getHistoricalMarketValue.m)は纏めて最後に紹介しますが、

targetWeight = [31, 23.4, 6.1, 27.7, 6.8, 5.0]/100;
commission = 0.0054; % 取引手数料(税込み) 0.54%
taxrate = 0.20; % 税金はざっくり 20%
rebalanceThreshold = 0; % 毎週

% pfQty  保有数
% pfMarketValue  評価額
[pfMarketValue, pfQty] = getHistoricalMarketValue(pricedata, divdata, ...
                                                  commission, taxrate, ...
                                                  rebalanceThreshold, targetWeight);

な感じで計算します。まずターゲットの銘柄割合、手数料率、税率を指定。 rebalanceThreshold はバランスがどれくらい崩れたときにリバランスを実施するかの閾値を指定します。0% の場合は毎週(週間データなので)リバランスを実施しますし、100% の場合はリバランスは実施されません。

得られた結果を可視化します。

figure
subplot(2,1,1)
bar(pricedata.time,pfMarketValue,'stacked','DisplayName','tmp');
legend(pricedata.Properties.VariableNames)
title('評価額推移(リバランスあり)')

subplot(2,1,2)
ps = pfMarketValue./sum(pfMarketValue,2);
bar(pricedata.time,ps,'stacked','DisplayName','tmp');
title('各銘柄の保有割合の推移(リバランスあり)')

プロットしてみると綺麗にリバランスできていることが分かりますね。

参考まで、リバランスしなかった場合(+76%)もプロットしておきます。

rebalanceThreshold = 1; % リバランス無しに設定
[pfMarketValue, pfQty] = getHistoricalMarketValue(pricedata, divdata, ...
                                                  commission, taxrate, ...
                                                  rebalanceThreshold, targetWeight);
figure
subplot(2,1,1)
bar(pricedata.time,pfMarketValue,'stacked','DisplayName','tmp');
legend(pricedata.Properties.VariableNames)
title('評価額推移(リバランス無し)')

subplot(2,1,2)
ps = pfMarketValue./sum(pfMarketValue,2);
bar(pricedata.time,ps,'stacked','DisplayName','tmp');
title('各銘柄の保有割合の推移(リバランス無し)')

株価が大きく下がったリーマンショックあたりで債権(AGG)の割合がぐんと上がりました。債権の割合が大きくなってしまったことで、その後の株価の伸びを享受しきれなかった・・って感じでしょうか。

リバランスはしつつ、手数料で持っていかれすぎないように、大きく相場が動いたときにだけリバランスをする、、そんな場合を想定して 15% 崩れたときにだけリバランスを実施するケースをプロットしたのが以下。2回リバランスが発生してますね。結果としてリバランスしないときに比べて 24% (+76% -> +100%) パフォーマンス向上しました。

株価は上がり続けていたので 2回目のリバランスは実施しない方がパフォーマンスは良かったかもしれませんね。

いや、そもそもリバランスは「リバランスによって、リスクを取りすぎることを防」ぐことが目的でした。

2.2 getHistoricalMarketValue.m

実際のリバランスを行っているコードはこちら。手数料・税金の取り扱いが面倒くさいです。
苦し紛れにいろいろ目をつぶってコード化していますのが、致命的な間違いがあれば教えてください。。

getHistoricalMarketValue.m
function [pfMarketValue,pfQty,fees,taxes] = getHistoricalMarketValue(pricedata, divdata, ...
                                                                     commission, taxrate, ...
                                                                     rebalanceTh, targetWeight)


pfQty = zeros(size(pricedata)); % 保有数
pfMarketValue = zeros(size(pricedata)); % 評価額
fees = zeros(height(pricedata)); % 手数料
taxes = zeros(height(pricedata)); % 税金

pfQty(1,:) = pricedata{1,:}.\targetWeight; % 初期保有数
pfMarketValue(1,:) = pricedata{1,:}.*pfQty(1,:); % 初期評価額

% 2週目から順に計算
for ii=2:height(pricedata)

    pfQty_old = pfQty(ii-1,:);

    % 先週と同じ株数での評価額
    % 配当金も再投資しますが購入手数料と税金は引いておきます。
    pfMarketValue(ii,:) = (pricedata{ii,:}+divdata{ii,:}*(1-commission)*(1-taxrate)).*pfQty_old;
    % 配当金再投資後の株数
    pfQty(ii,:) = pfMarketValue(ii,:)./pricedata{ii,:};

    % 現時点での評価額
    MarketValueNow = sum(pfMarketValue(ii,:),2);
    % ポートフォリオバランス確認
    actualWeight = pfMarketValue(ii,:)/MarketValueNow;
    % ターゲットとの差分
    difWeight = actualWeight - targetWeight;

    % 1銘柄でも最低閾値を超えてバランスが崩れていればリバランス実行
    if max(abs(difWeight)) > rebalanceTh

        what2sell = difWeight > 0; % 想定割合を超えている銘柄認識
        amount2sell = difWeight(what2sell)*MarketValueNow; % 各銘柄の売却額

        % 売却益はあるかな?(簡易的に初期購入時の株価との差額を使用)
        priceDif = pricedata{ii,what2sell} - pricedata{1,what2sell}; % 株価の差額(1株当たり)
        capGain = priceDif.*(amount2sell./pricedata{ii,what2sell}); % 差額 x 売却株数 = 売却益
        capGain = capGain - sum(amount2sell)*commission; % 手数料分減額

        % 売却益 x 20% は税金(マイナスの場合還付あり想定)
        taxLoss = sum(capGain)*taxrate;

        % 手数料も引いて購入資金となる・・と。(売却・購入双方)
        totalLoss = sum(amount2sell)*commission*2 + taxLoss;
        % 税引き後の評価額
        pfQty(ii,:) = pricedata{ii,:}.\targetWeight*sum(MarketValueNow - totalLoss);

        pfMarketValue(ii,:) = pfQty(ii,:).*pricedata{ii,:};

        taxes(ii) = taxLoss;
        fees(ii) = sum(amount2sell)*commission*2;
    end
end
end