Pythonの純粋仮想関数は引数の数適当に変えて実装してもOK

掲題の件、そういいうことです。 例えば適当に get() メソッドの引数を追加して実装しても

from abc import ABCMeta, abstractmethod

class Hoge(metaclass=ABCMeta):
    @abstractmethod
    def get(self):
        pass

class Moge(Hoge):
    def get(self, x = None, y = None):
        print("hi")

ちゃんと動く&元のクラスのインスタンスは作れない

>>> x = Moge()
>>> x.get(y=123)
hi
>>> 
>>> Hoge()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Hoge with abstract methods get

米国株式とUSDJPYの2020年1月1日からの相関構造

f:id:teramonagi:20200820065748p:plain
米国株式とUSDJPYの2020年1月1日からの相関構造

ドル円(USDJPY)が 0.6% で ダウ平均が2.8% くらいのリスク(標準偏差、日率)

> 100*sd(df$`usdjpy=x`)
[1] 0.6186612
> 100*sd(df$dji)
[1] 2.842537

全体

library("dplyr")
library("tidyr")
library("stringr")
library("tidyquant")
library("PerformanceAnalytics")
get_from_yahoo <- function(x, range_date){
  # Why max(range_date) +1? because: from <= x < to
  df <- tq_get(x, from = min(range_date), to = max(range_date) + 1)
  dplyr::select(df, symbol, date, adjusted) %>%
    rename(price=adjusted) %>%
    mutate(symbol = str_to_lower(str_replace_all(symbol, "\\^", ""))) %>%
    arrange(date)
}
arithmetic_return <- function(x){
  c(NA, x[-1]/x[-length(x)] - 1)
}
# https://finance.yahoo.com/quote/%5EDJI/
range_date <- c(lubridate::ymd("20200101"), lubridate::ymd("20200818"))
symbols <- c("^DJI", "USDJPY=X")
df <- purrr::map_df(symbols, ~ get_from_yahoo(.x, range_date)) %>%
  tidyr::pivot_wider(names_from = symbol, values_from = price) %>%
  arrange(date) %>%
  mutate_at(
    vars(-"date"),
    arithmetic_return
  ) %>%
  na.omit()

chart.Correlation(dplyr::select(df, -date), histogram=TRUE, pch=19)
100*sd(df$`usdjpy=x`)
100*sd(df$dji)

米国株式市場の2020年1月1日からの相関構造

f:id:teramonagi:20200819124957p:plain
米国株式市場の2020年1月1日からの相関構造

すげぇ強かった(おしまい)

  • "^DJI" : ダウ平均
  • "^RUT": ラッセル指数
  • "^GSPC": S&P500
  • "^IXIC": ナスダック指数

再現用のCode

library("dplyr")
library("tidyr")
library("stringr")
library("tidyquant")
library("PerformanceAnalytics")
get_from_yahoo <- function(x, range_date){
  # Why max(range_date) +1? because: from <= x < to
  df <- tq_get(x, from = min(range_date), to = max(range_date) + 1)
  dplyr::select(df, symbol, date, adjusted) %>%
    rename(price=adjusted) %>%
    mutate(symbol = str_to_lower(str_replace_all(symbol, "\\^", ""))) %>%
    arrange(date)
}
arithmetic_return <- function(x){
  c(NA, x[-1]/x[-length(x)] - 1)
}
# https://finance.yahoo.com/quote/%5EDJI/
range_date <- c(lubridate::ymd("20200101"), lubridate::ymd("20200818"))
symbols <- c("^DJI", "^RUT", "^GSPC", "^IXIC")
df <- purrr::map_df(symbols, ~ get_from_yahoo(.x, range_date)) %>%
  tidyr::pivot_wider(names_from = symbol, values_from = price) %>%
  arrange(date) %>%
  mutate_at(
    vars(-"date"),
    arithmetic_return
  ) %>%
  na.omit()

chart.Correlation(dplyr::select(df, -date), histogram=TRUE, pch=19)

特定の列だけ外して処理したい場合には . (dot) を名前に持つ列と lsを組み合わせると良い

株式会社ホクソエムの社長から教えていただいた。 いちいち dplyr::select なぞせんでもこうするだけで .y を除いた処理を実行できる。

> df <- data.frame(
+   x = 1:3,
+   .y = 2:4
+ )
> df
  x .y
1 1  2
2 2  3
3 3  4
> ls(df)
[1] "x"

dplyrでうっかり八兵衛にならないように !! を意識する

うっかりやっちまったのでメモっておく。 まず適当な data.frame() を定義する。

> df <- data.frame(x=sample(1:2, 10, replace=TRUE), y=sample(c("a", "b"), 10, replace = TRUE))
> df
   x y
1  2 a
2  2 b
3  2 b
4  2 b
5  1 b
6  1 a
7  2 b
8  1 b
9  1 b
10 2 b

で、これを適当にFilteringしたいとするとこう書く。

> dplyr::filter(df, x == 1, y == "b")
  x y
1 1 b
2 1 b
3 1 b

そして「よくよく考えると x を変数にしておいたほうが楽そうだぞ〜」ということで以下のように書くと うっかり八兵衛になる (答えが狂う)。

> x <- 1
> dplyr::filter(df, x == x, y == "b")
  x y
1 2 b
2 2 b
3 2 b
4 1 b
5 2 b
6 1 b
7 1 b
8 2 b

正しくは以下のように rlang::!! をちゃんとかませておかないといけない。

> dplyr::filter(df, x == !!x, y == "b")
  x y
1 1 b
2 1 b
3 1 b

rlang は偉大なり、うっかりうっかり。

set/list/dictあたりは内部で __str__ではなく __repr__ を呼んでいるっぽい

掲題の件、そういうことです。 __str__() だけ定義しておくと

class Hoge():
    def __init__(self, x):
        self._x = x
    def __str__(self):
        return "Hoge({})".format(self._x)

ちゃんと出てくれない。

> x = {Hoge(1), Hoge(2)}
> print(x)
{<__main__.Hoge object at 0x109fb7880>, <__main__.Hoge object at 0x109fb78e0>}

一方、 __repr__() を定義しておくと

class Hoge():
    def __init__(self, x):
        self._x = x
    def __str__(self):
        return self.__repr__()
    def __repr__(self):
        return "Hoge({})".format(self._x)

ちゃんと出る。

> x = {Hoge(1), Hoge(2)}
> print(x)
{Hoge(1), Hoge(2)}

SBI証券の手数料プランはスタンダード/アクティブプランのどちらを選ぶべきか?

俺はSBI証券を使っているのだが、その手数料プラン(アクティブ or スタンダード)をどっちにするのかあまり真面目に考えてこなかったので、真面目に考えたい。

俺の俺による俺のためのデータサイエンスだ。

さて、国内株式の手数料を教えてくださいのページにあるように国内株式の手数料は下記のように与えられる。 f:id:teramonagi:20200325131548p:plain

この情報を元に

  • 一日の取引回数(The number of trading)
  • 一回の取引金額(Unit trading price)
  • 一回の取引金額のばらつき(これは一回の取引金額の50% ~ 150%になるように一様に振っている)

をある程度の幅で振ってシミュレーションさせた結果が以下になる。

図1 f:id:teramonagi:20200327092606p:plain

この表は

  • 横軸:一日の取引回数(The number of trading)
  • 縦軸:一回の取引金額(Unit trading price)

で、図中に出てくる数値はそれぞれ

  • a: アクティブプラン
  • s: スタンダードプラン

の ”一取引あたりの手数料(平均値)” の数値を表してて、水色背景だとアクティブプランを選ぶのが有利(手数料安い)、赤色背景だとスタンダードプラン有利という見方をする。

これを見るとざっくりとした傾向として

  • 一回の取引金額(Unit trading price)が少ないときはアクティブプラン
  • 一回の取引金額(Unit trading price)が大きいときはスタンダードプラン

と選べば良さそう。あとは意外と甲乙つけ難しというか取引回数に依存しそうだなということがわかった。 ちなみに "一回の取引金額のばらつき"を0にしても結論は変わらずな図になる。

おまけとして、一回の取引金額を固定して、一取引あたりの手数料分布の一日の取引回数依存性を見てみると以下のようになる。 この取引金額のレンジだと取引回数を増やせば増やすほどアクティブプランがお得になってるのがわかる。

図2:一回の取引金額が10万円の時の一取引あたりの手数料分布(横軸は一日の取引回数) f:id:teramonagi:20200327093723p:plain

図3:一回の取引金額が100万円の時の一取引あたりの手数料分布(横軸は一日の取引回数) f:id:teramonagi:20200327093720p:plain

結論

「月にO(10万円)、ETFで積み増しする」のをメインに、動かしても一日1~2百万レベルだとするとアクティブプランでいいんだろうな~。 ちなみに米国株でも似たような計算をしたが、明らかに手数料が高くてアレな気持ちになった。

スタンダードプランだと手数料に応じたポイント還元もあるようだが、還元率が1.1%なのであまり魅力的には見えない。

Reproducible Code

library("dplyr")
library("tidyr")
library("ggplot2")

fee_us <- function(x, usdjpy = 110){
  min(usdjpy * 20, 0.45 * 0.01 * x)
}
fee_active <- function(x){
  x <- x/10^4
  if(x <= 50){
    0
  } else if(x > 50 & x <= 100){
    762
  } else{
    762 + 400*ceiling((x - 100) / 100)
  }
}

fee_standard <- function(x){
  x <- x/10^4
  if(x <= 5){
    50
  } else if(x >   5 & x <=  10){
    90
  } else if(x >  10 & x <=  20){
    105
  } else if(x >  20 & x <=  50){
    250
  } else if(x >  50 & x <= 100){
    487
  } else if(x > 100 & x <= 150){
    582
  } else if(x > 150 & x <= 3000){
    921
  } else{
    973
  }
}

simulate_one <- function(size_mc, size_trading, mean_price, usdjpy, noize=c(min=0.5, max=1.5)){
  x <- list()
  for(i in seq_len(size_mc)){
    price <- rep(mean_price, size_trading)
    if(!is.null(noize)){
      price <- price*runif(size_trading, min=noize["min"], max=noize["max"])
    }
    active <- fee_active(sum(price))
    standard <- sum(purrr::map_dbl(price, fee_standard))
    us <- sum(purrr::map_dbl(price, ~ fee_us(.x, usdjpy)))
    total_cost <- c(active, standard)
    x[[i]] <- list(
      type = c("active", "standard"),
      total_cost = total_cost,
      cost_per_trade = total_cost/size_trading,
      total_amount = rep(sum(price), 2),
      amount_per_trade <- rep(mean(price), 2)
    )
  }
  cbind(size_mc, size_trading, mean_price, bind_rows(x))
}

parameter <- expand.grid(
  size_trading = c(1, 2, 3, 5, 10, 20, 50, 100), 
  mean_price   = c(1, 5, 10, 15, 20, 30, 50, 100, 200, 300, 500, 1000, 3500) * 10^4
)
size_mc <- 10^2
usdjpy <- 110
df <- purrr::pmap_dfr(parameter, function(...) {
    x <- tibble(...)
    #simulate_one(size_mc, x$size_trading, x$mean_price, usdjpy, noize=c(min=1, max=1))
    simulate_one(size_mc, x$size_trading, x$mean_price, usdjpy, noize=c(min=0.5, max=1.5))
}
)
dfs <- df %>% group_by(size_trading, mean_price, type) %>% 
  summarize(
    total_cost_mean     = mean(total_cost), 
    total_cost_sd       = sd(total_cost), 
    cost_per_trade_mean = mean(cost_per_trade), 
    cost_per_trade_sd   = sd(cost_per_trade)
  ) %>% 
  select(size_trading, mean_price, type, cost_per_trade_mean) %>%
  tidyr::pivot_wider(names_from = type, values_from = cost_per_trade_mean) %>%
  mutate(
    active_is_better = active < standard, 
    text = paste0("a: ", round(active), "\ns: ", round(standard)))
# 図1
ggplot(dfs, aes(factor(size_trading), factor(mean_price), fill=active_is_better)) +
  geom_tile(color = "gray") + 
  geom_text(aes(label = text), lineheight = 0.8) + 
  labs(y="Unit trading price", x = "The number of trading")
# 図2, 3
ggplot(filter(df, mean_price ==  10*10^4, size_trading <= 100), aes(x=factor(size_trading), y=cost_per_trade, fill=type)) + geom_violin(scale = "width") 
ggplot(filter(df, mean_price == 100*10^4, size_trading <= 100), aes(x=factor(size_trading), y=cost_per_trade, fill=type)) + geom_violin(scale = "width")