Pythonでゴールデンクロスの情報を1日1回メールで通知する

# 目次
株のデータ収集についての記事一覧を[こちら](https://qiita.com/fjunya/items/dbe5ef36246c88acd983)に記載しております。

# 目的
- 過去にゴールデンクロスが起きた日付を特定する機能の実装
- ゴールデンクロスが起きている株式の情報をメールで通知する機能の実装

### ゴールデンクロスとは？
ゴールデンクロスは短期線が長期線を下から上へ突き抜ける状態のことを言います。直近の価格傾向が上向きに転じたと捉えられるので「買いシグナル」です。
![pic4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149245/ffaa501a-22c3-02a1-f226-dadd37abcf34.gif)
###デッドクロスとは？
ゴールデンクロスとは逆に、短期移動平均線が長期移動平均線を上から下へ突き抜けることを言います。為替レートが下降トレンドに転じたと捉えられるため「売りシグナル」となります。
![pic5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149245/3fb94e17-864f-e9e2-01ae-cbb23ea0c5b1.gif)
引用：外為ドットコム
# ゴールデンクロスとデッドクロスの算出方法
ゴールデンクロスとデッドクロスの算出方法は単純で、以下の条件で求めることが可能です。

**ゴールデンクロス**
前日の短期線 < 前日の長期線 AND 当日の短期線 > 当日の長期戦

**デッドクロス**
前日の長期線 < 前日の短期線 AND 当日の長期線 > 当日の短期戦

# 過去にゴールデンクロスが起きた日付を特定する
## 前提
- 前もってデータベースに株価の情報と25日平均線と75日平均線が登録されている状態から開始します
- Djangoを使用します
- Djangoのmodelは以下のようになっております

```python:models.py
class Company(models.Model):
    code = models.IntegerField("銘柄コード", primary_key=True)
    name = models.CharField("会社名", max_length=200)

class RawPrices(models.Model):
    code = models.IntegerField("銘柄コード")
    date = models.DateField("日付")
    open_price = models.IntegerField("始値")
    close_price = models.IntegerField("終値")
    high_price = models.IntegerField("高値")
    low_price = models.IntegerField("安値")
    volume = models.IntegerField("出来高")
    adjustment_close_price = models.FloatField("調整後終値")
    moving_averages25 = models.FloatField("25日移動平均線")
    moving_averages75 = models.FloatField("75日移動平均線")
```
## 実装
- サイボウズの2017/1/1から2019/9/27までの間でゴールデンクロスが起きた日を特定します
- DjangoのCommand機能を使用します

```python
from django.core.management.base import BaseCommand
from app.models import RawPrices
import pandas as pd

import logging
logger = logging.getLogger(__name__)

class Command(BaseCommand):
    help = ''

    def handle(self, *args, **options):
        self.print_cross_date()

    def print_cross_date(self):
        """
        ゴールデンクロスが起きた日付を表示
        """
        CODE = 4776 #サイボウズの証券コード
        START_DATE = "2017-12-29" #東証が2017/12/30から2018/1/3まで休みの為、2018/1/1からのゴールデンクロスを求める為に2017/12/29を指定
        raw_prices = self.get_raw_price(CODE, START_DATE)
        # 25日平均線のDataFrameを作成
        series1 = pd.DataFrame(list(map(lambda x: [x.date, x.moving_averages25], raw_prices)),
                               columns=["date", "sma"])
        series1.set_index("date", inplace=True)
        # 75日平均線のDataFrameを作成
        series2 = pd.DataFrame(list(map(lambda x: [x.date, x.moving_averages75], raw_prices)),
                               columns=["date", "sma"])
        series2.set_index("date", inplace=True)
        cross_date_list, cross_result_list = self.search_golden_cross_date(series1, series2)
        # 結果を表示
        for i in cross_date_list:
            print(i)

    def get_raw_price(self, code, start_date):
        # データベースから株価のデータを取得
        raw_prices = RawPrices.objects.filter(code=code)\
            .filter(date__gte=start_date).exclude(moving_averages75=-1).order_by("date")
        return raw_prices

    def search_golden_cross_date(self, series1: pd.Series, series2: pd.Series):
        cross_date_list = []
        cross_result_list = []
        for i in range(series1.size):
            # 2日毎のデータに分割してゴールデンクロスを算出
            result = self.crossover(series1=series1[i:i+2], series2=series2[i:i+2])
            cross_result_list.append([series2.iloc[[i]].index.values[0], result])
            if result:
                cross_date_list.append(series2.iloc[[i+1]].index.values[0])
        return cross_date_list, cross_result_list

    def crossover(self, series1: pd.Series, series2: pd.Series) -> bool:
        """
        ゴールデンクロスが起きたらTrueを返す
        """
        if(series1.size < 2):
            return False
        try:
            return series1.sma[0] < series2.sma[0] and series1.sma[1] > series2.sma[1]
        except IndexError:
            return False
```
実行結果

```
python manage.py golden_cross_notification
2018-01-04
2018-05-21
2018-08-20
2019-03-08
```
実際に楽天証券のチャートで確認。正しく算出できているようです。
![テクニカルチャート.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149245/a6e51a68-1c22-d39f-ec51-9e80b2daf303.png)

# ゴールデンクロスの情報をメールで通知する
- 1日1回ゴールデンクロスが起きている株式の情報をメールで通知する機能を実装してみました
- 以下のコードをcronなどで一日一回実行するとゴールデンクロスが起きた株式の情報がメールで届きます(DBに保存されている株価の情報は別途更新する必要があります)

```python
from django.core.management.base import BaseCommand
from app.models import RawPrices, Company
import pandas as pd
from django.core.mail import send_mail
from django.conf import settings

import logging
logger = logging.getLogger(__name__)

class Command(BaseCommand):
    help = ''

    def handle(self, *args, **options):
        self.notification()

    def notification(self):
        """
        ゴールデンクロスの情報をメールで送信
        """
        companies = Company.objects.all()
        notification_list = []
        for company in companies:
            raw_prices = RawPrices.objects.filter(code=company.code).order_by("-date")[:2]
            series1 = pd.DataFrame(list(map(lambda x: [x.date, x.moving_averages25], raw_prices)),
                                   columns=["date", "sma"])
            series1.set_index("date", inplace=True)
            series2 = pd.DataFrame(list(map(lambda x: [x.date, x.moving_averages75], raw_prices)),
                                   columns=["date", "sma"])
            series2.set_index("date", inplace=True)
            if self.crossover(series1, series2):
                notification_list.append(company)
        if len(notification_list) > 0:
            self.send_mail(notification_list)

    def crossover(self, series1: pd.Series, series2: pd.Series) -> bool:
        """
        ゴールデンクロスが起きたらTrueを返す
        """
        if(series1.size < 2):
            return False
        try:
            return series1.sma[1] < series2.sma[1] and series1.sma[0] > series2.sma[0]
        except IndexError:
            return False

    def send_mail(self, company_list):
        message = ""
        for company in company_list:
            text = "証券コード : {}\n".format(company.code)
            message+=text
        subject = "ゴールデンクロス情報"
        from_email = settings.FROM_EMAIL  # 送信者
        recipient_list = settings.RECIPIENT_LIST  # 宛先リスト
        send_mail(subject, message, from_email, recipient_list)
```

実行すると以下のようなメールが届きます。
>タイトル：ゴールデンクロス情報
本文
証券コード : 4443
証券コード : 4776

Djangoでメールを送信するための設定はこちらの記事が参考になります。
https://narito.ninja/blog/detail/64/