Skip to content

Commit

Permalink
feat: add KdCrossOver, MAGoldenDeathCrossOver, InstitutionalInvestors…
Browse files Browse the repository at this point in the history
…OverBuy, ShortSaleMarginPurchaseRatio (#277)

* featL add KdCrossOver

* feat: add MAGoldenDeathCrossOver

* feat: add auto add _additional_dataset

* feat: add short_sale_margin_purchase_ratio
  • Loading branch information
linsamtw committed Dec 6, 2023
1 parent c4299a2 commit 3861478
Show file tree
Hide file tree
Showing 14 changed files with 572 additions and 154 deletions.
1 change: 1 addition & 0 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
env:
FINMIND_USER: ${{ secrets.FINMIND_USER }}
FINMIND_PASSWORD: ${{ secrets.FINMIND_PASSWORD }}
FINMIND_API_TOKEN: ${{ secrets.FINMIND_TOKEN }}
run: |
python genenv.py
pipenv sync
Expand Down
2 changes: 1 addition & 1 deletion FinMind/data/finmind_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def get_data(
:param params: finmind api參數
:return:
"""
logger.info(f"download {dataset}")
logger.info(f"download {dataset}, data_id: {data_id}")
params = dict(
dataset=dataset,
data_id=data_id,
Expand Down
18 changes: 17 additions & 1 deletion FinMind/indicators/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
from FinMind.indicators.kd import add_kd_indicators
from FinMind.indicators.bias import add_bias_indicators
from FinMind.indicators.continue_holding import add_continue_holding_indicators
from FinMind.indicators.institutional_investors_follower import (
add_institutional_investors_follower,
)
from FinMind.indicators.institutional_investors_over_buy import (
add_institutional_investors_over_buy_indicators,
)
from FinMind.indicators.kd import add_kd_indicators
from FinMind.indicators.kd_crossover import (
add_kd_golden_death_cross_over_indicators,
)
from FinMind.indicators.ma_cross_orver import (
add_ma_golden_death_cross_orver_indicators,
)
from FinMind.indicators.short_sale_margin_purchase_ratio import (
add_short_sale_margin_purchase_ratio_indicators,
)

INDICATORS_MAPPING = dict(
KD=add_kd_indicators,
BIAS=add_bias_indicators,
ContinueHolding=add_continue_holding_indicators,
InstitutionalInvestorsFollower=add_institutional_investors_follower,
KDGoldenDeathCrossOver=add_kd_golden_death_cross_over_indicators,
MAGoldenDeathCrossOver=add_ma_golden_death_cross_orver_indicators,
InstitutionalInvestorsOverBuy=add_institutional_investors_over_buy_indicators,
ShortSaleMarginPurchaseRatio=add_short_sale_margin_purchase_ratio_indicators,
)
2 changes: 1 addition & 1 deletion FinMind/indicators/institutional_investors_follower.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def add_institutional_investors_follower(
on=["stock_id", "date"],
how="left",
).fillna(0)
stock_price["InstitutionalInvestorsOverBuy"] = __detect_Abnormal_Peak(
stock_price["InstitutionalInvestorsFollower"] = __detect_Abnormal_Peak(
y=stock_price["diff"].values,
lag=n_days,
threshold=3,
Expand Down
56 changes: 56 additions & 0 deletions FinMind/indicators/institutional_investors_over_buy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import numpy as np
import pandas as pd

from FinMind.schema.data import Dataset


def _taiwan_stock_institutional_investors_buy_sell_group_by(
taiwan_stock_institutional_investors_buy_sell: pd.DataFrame,
) -> pd.DataFrame:
taiwan_stock_institutional_investors_buy_sell[["sell", "buy"]] = (
taiwan_stock_institutional_investors_buy_sell[["sell", "buy"]]
.fillna(0)
.astype(int)
)
taiwan_stock_institutional_investors_buy_sell = (
taiwan_stock_institutional_investors_buy_sell.groupby(
["date", "stock_id"], as_index=False
).agg({"buy": np.sum, "sell": np.sum})
)
taiwan_stock_institutional_investors_buy_sell[
"InstitutionalInvestorsOverBuy"
] = (
taiwan_stock_institutional_investors_buy_sell["buy"]
- taiwan_stock_institutional_investors_buy_sell["sell"]
)
return taiwan_stock_institutional_investors_buy_sell


def add_institutional_investors_over_buy_indicators(
stock_price: pd.DataFrame, additional_dataset_obj, **kwargs
) -> pd.DataFrame:
"""
url: "https://blog.above.tw/2018/08/15/%E7%B1%8C%E7%A2%BC%E9%9D%A2%E7%9A%84%E9%97%9C%E9%8D%B5%E6%8C%87%E6%A8%99%E6%9C%89%E5%93%AA%E4%BA%9B%EF%BC%9F/"
summary:
策略概念:法人買超股票會上漲, 反之亦然
策略規則: 法人買超, 買
法人賣超, 賣
"""
stock_price = stock_price.sort_values("date")
taiwan_stock_institutional_investors_buy_sell = getattr(
additional_dataset_obj, Dataset.TaiwanStockInstitutionalInvestorsBuySell
)
taiwan_stock_institutional_investors_buy_sell = (
_taiwan_stock_institutional_investors_buy_sell_group_by(
taiwan_stock_institutional_investors_buy_sell
)
)
stock_price = pd.merge(
stock_price,
taiwan_stock_institutional_investors_buy_sell[
["stock_id", "date", "InstitutionalInvestorsOverBuy"]
],
on=["stock_id", "date"],
how="left",
).fillna(0)
return stock_price
62 changes: 62 additions & 0 deletions FinMind/indicators/kd_crossover.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import numpy as np
import pandas as pd
from ta.momentum import StochasticOscillator


def add_kd_golden_death_cross_over_indicators(
stock_price: pd.DataFrame, k_days: int = 9, **kwargs
) -> pd.DataFrame:
"""
url: "http://smart.businessweekly.com.tw/Reading/WebArticle.aspx?id=68129&p=2"
summary: 日KD黃金交叉和死亡交叉
日K線 < 日D線,翻轉成,日K線 > 日D線 稱為黃金交叉
日K線 > 日D線,翻轉成,日K線 < 日D線 稱為死亡交叉
黃金交叉進場,死亡交叉出場
"""
stock_price = stock_price.sort_values("date")
kd = StochasticOscillator(
high=stock_price["max"],
low=stock_price["min"],
close=stock_price["close"],
n=k_days,
)
rsv_ = kd.stoch().fillna(50)
_k = np.zeros(stock_price.shape[0])
_d = np.zeros(stock_price.shape[0])
for i, r in enumerate(rsv_):
if i == 0:
_k[i] = 50
_d[i] = 50
else:
_k[i] = _k[i - 1] * 2 / 3 + r / 3
_d[i] = _d[i - 1] * 2 / 3 + _k[i] / 3
stock_price["K"] = _k
stock_price["D"] = _d
stock_price.index = range(len(stock_price))
stock_price["diff"] = stock_price["K"] - stock_price["D"]
stock_price.loc[(stock_price.index < k_days), "diff"] = np.nan
stock_price["diff_sign"] = stock_price["diff"].map(
lambda x: 1 if x >= 0 else (-1 if x < 0 else 0)
)
stock_price["diff_sign_yesterday"] = (
stock_price["diff_sign"].shift(1).fillna(0).astype(int)
)
stock_price["KDGoldenDeathCrossOver"] = 0
stock_price.loc[
(
(stock_price["diff_sign"] > 0)
& (stock_price["diff_sign_yesterday"] < 0)
),
"KDGoldenDeathCrossOver",
] = 1
stock_price.loc[
(
(stock_price["diff_sign"] < 0)
& (stock_price["diff_sign_yesterday"] > 0)
),
"KDGoldenDeathCrossOver",
] = -1
stock_price = stock_price.drop(
["diff", "diff_sign", "diff_sign_yesterday"], axis=1
)
return stock_price
69 changes: 69 additions & 0 deletions FinMind/indicators/ma_cross_orver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import pandas as pd
from ta.trend import SMAIndicator


def add_ma_golden_death_cross_orver_indicators(
stock_price: pd.DataFrame,
ma_short_term_days: int = 10,
ma_long_term_days: int = 30,
**kwargs,
) -> pd.DataFrame:
"""
url:
"https://www.cmoney.tw/learn/course/technicalanalysisfast/topic/1811"
summary:
均線黃金交叉
以短線操作來說,當 5日均線 向上突破 20日均線
也就是短期的平均買進成本大於長期平均成本
代表短期買方的力道較大,市場上大多數人獲利
市場易走出「多頭」的趨勢,進而帶動長期均線向上,讓股價上漲機率較大
短期線 突破 長期線(黃金交叉),進場
長期線 突破 短期線(死亡交叉),出場
"""
stock_price = stock_price.sort_values("date")
stock_price[f"ma{ma_short_term_days}"] = SMAIndicator(
stock_price["close"], ma_short_term_days
).sma_indicator()
stock_price[f"ma{ma_long_term_days}"] = SMAIndicator(
stock_price["close"], ma_long_term_days
).sma_indicator()
stock_price["ma_diff"] = (
stock_price[f"ma{ma_short_term_days}"]
- stock_price[f"ma{ma_long_term_days}"]
)
stock_price["bool_signal"] = stock_price["ma_diff"].map(
lambda x: 1 if x > 0 else -1
)
stock_price["bool_signal_shift1"] = (
stock_price["bool_signal"].shift(1).fillna(0)
)
stock_price["bool_signal_shift1"] = stock_price[
"bool_signal_shift1"
].astype(int)
stock_price["MAGoldenDeathCrossOver"] = 0
stock_price.loc[
(
(stock_price["bool_signal"] > 0)
& (stock_price["bool_signal_shift1"] < 0)
),
"MAGoldenDeathCrossOver",
] = 1
stock_price.loc[
(
(stock_price["bool_signal"] < 0)
& (stock_price["bool_signal_shift1"] > 0)
),
"MAGoldenDeathCrossOver",
] = -1
stock_price = stock_price.drop(
[
"ma_diff",
"bool_signal",
"bool_signal_shift1",
f"ma{ma_short_term_days}",
f"ma{ma_long_term_days}",
],
axis=1,
)
return stock_price
51 changes: 51 additions & 0 deletions FinMind/indicators/short_sale_margin_purchase_ratio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import numpy as np
import pandas as pd

from FinMind.schema.data import Dataset


def _create_short_sale_margin_purchase_today_ratio(
taiwan_stock_margin_purchase_shortSale: pd.DataFrame,
) -> pd.DataFrame:
taiwan_stock_margin_purchase_shortSale[
["ShortSaleTodayBalance", "MarginPurchaseTodayBalance"]
] = taiwan_stock_margin_purchase_shortSale[
["ShortSaleTodayBalance", "MarginPurchaseTodayBalance"]
].astype(
int
)
taiwan_stock_margin_purchase_shortSale["ShortSaleMarginPurchaseRatio"] = (
taiwan_stock_margin_purchase_shortSale["ShortSaleTodayBalance"]
/ taiwan_stock_margin_purchase_shortSale["MarginPurchaseTodayBalance"]
)
return taiwan_stock_margin_purchase_shortSale


def add_short_sale_margin_purchase_ratio_indicators(
stock_price: pd.DataFrame, additional_dataset_obj, **kwargs
) -> pd.DataFrame:
"""
url: "https://blog.above.tw/2018/08/15/%E7%B1%8C%E7%A2%BC%E9%9D%A2%E7%9A%84%E9%97%9C%E9%8D%B5%E6%8C%87%E6%A8%99%E6%9C%89%E5%93%AA%E4%BA%9B%EF%BC%9F/"
summary:
策略概念: 券資比越高代表散戶看空,這時候賣可以跟大部分散戶進行相反的操作,反之亦然
策略規則: 券資比>=30%, 賣
券資比<30%, 買
"""
stock_price = stock_price.sort_values("date")
taiwan_stock_margin_purchase_shortSale = getattr(
additional_dataset_obj, Dataset.TaiwanStockMarginPurchaseShortSale
)
taiwan_stock_margin_purchase_shortSale = (
_create_short_sale_margin_purchase_today_ratio(
taiwan_stock_margin_purchase_shortSale
)
)
stock_price = pd.merge(
stock_price,
taiwan_stock_margin_purchase_shortSale[
["stock_id", "date", "ShortSaleMarginPurchaseRatio"]
],
on=["stock_id", "date"],
how="left",
).fillna(0)
return stock_price
55 changes: 52 additions & 3 deletions FinMind/schema/indicators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from pydantic import BaseModel

from FinMind.schema.data import Dataset
from FinMind.schema.rule import Rule


Expand All @@ -17,29 +18,77 @@ class Indicators(str, Enum):
Regular fixed-amount buy-and-hold strategy, buy once every n days,
defalt 30
"""
InstitutionalInvestorsFollower = "InstitutionalInvestorsOverBuy"
InstitutionalInvestorsFollower = "InstitutionalInvestorsFollower"
"""
the formula of InstitutionalInvestorsFollower is n_days,
if InstitutionalInvestors over buy n days,
but the stock has not risen,
so we will follow up and buy,
defalt 10
"""
KDGoldenDeathCrossOver = "KDGoldenDeathCrossOver"
"""
the formula of KD is k_days, defalt 9,
Indicators
1 means Golden Cross Over,
-1 means Death Cross Over
"""
MAGoldenDeathCrossOver = "MAGoldenDeathCrossOver"
"""
the formula of MA is [ma_short_term_days, ma_long_term_days]
defalt [10, 30],
Indicators
1 means Golden Cross Over,
-1 means Death Cross Over
"""
InstitutionalInvestorsOverBuy = "InstitutionalInvestorsOverBuy"
"""
the formula is None, it just calculates ( Buy - Sell )
Indicators
> 0 means OverBuy,
< 0 means OverSell
"""
ShortSaleMarginPurchaseRatio = "ShortSaleMarginPurchaseRatio"
"""
the formula is None, it just calculates ( ShortSaleTodayBalance / MarginPurchaseTodayBalance )
"""


class IndicatorsParams(str, Enum):
KD = "k_days"
BIAS = "ma_days"
ContinueHolding = "buy_freq_day"
InstitutionalInvestorsFollower = "n_days"
KDGoldenDeathCrossOver = "k_days"
MAGoldenDeathCrossOver = ["ma_short_term_days", "ma_long_term_days"]
InstitutionalInvestorsOverBuy = None
ShortSaleMarginPurchaseRatio = None


class IndicatorsInfo(BaseModel):
name: Indicators
formula_value: typing.Union[int, float, str] = None
formula_value: typing.Union[
typing.List[typing.Union[int, float, str]], int, float, str
] = None


class AddBuySellRule(BaseModel):
indicators: Indicators
more_or_less_than: Rule
threshold: typing.Union[int, float, str]
threshold: typing.Union[float, int, str]


class AdditionalDataset(str, Enum):
InstitutionalInvestorsFollower = (
Dataset.TaiwanStockInstitutionalInvestorsBuySell.value
)
InstitutionalInvestorsOverBuy = (
Dataset.TaiwanStockInstitutionalInvestorsBuySell.value
)
ShortSaleMarginPurchaseRatio = (
Dataset.TaiwanStockMarginPurchaseShortSale.value
)

0 comments on commit 3861478

Please sign in to comment.