├── .gitattributes ├── Trading ├── holiday.xlsx ├── stock_list.xlsx ├── png │ ├── result_2317.TW.png │ ├── result_2330.TW.png │ ├── result_2454.TW.png │ ├── result_2603.TW.png │ └── result_6505.TW.png ├── AES_Encryption │ ├── __pycache__ │ │ ├── en_decrype.cpython-37.pyc │ │ └── encrype_process.cpython-37.pyc │ ├── encrype_process.py │ └── en_decrype.py ├── strategy_research.py ├── requirement.txt ├── requirement_python310.txt ├── 2_buy_with_devidend.py ├── 1_buy_follow_corp.py ├── 2_2_buy_with_dividend_price.py ├── 3_buy_with_price_fall.py ├── tech1_ma_strategy.py ├── utility_f.py ├── backtest_research.py ├── tech3_macd_ma.py └── tech2_highest.py ├── Trading Strategy_EX ├── Chapter3 │ ├── holidaySchedule.csv │ ├── __pycache__ │ │ └── is_open.cpython-37.pyc │ ├── AES_Encryption │ │ ├── __pycache__ │ │ │ ├── en_decrype.cpython-37.pyc │ │ │ └── encrype_process.cpython-37.pyc │ │ ├── encrype_process.py │ │ └── en_decrype.py │ ├── mine_ta.py │ ├── deal_holiday.py │ ├── is_open.py │ ├── pd_example.py │ ├── yfinance_example.py │ ├── ta_example.py │ ├── generate_picture_example.py │ ├── smtp.py │ └── smtp2.py └── Chapter2 │ ├── __pycache__ │ ├── yahoo_price.cpython-37.pyc │ └── yahoo_finance.cpython-37.pyc │ ├── TWSE.py │ ├── yahoo_price.py │ ├── stock_list.py │ ├── yahoo_news.py │ └── yahoo_news_2.py └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /Trading/holiday.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arleigh418/python-and-Taiwan-stock-market/HEAD/Trading/holiday.xlsx -------------------------------------------------------------------------------- /Trading/stock_list.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arleigh418/python-and-Taiwan-stock-market/HEAD/Trading/stock_list.xlsx -------------------------------------------------------------------------------- /Trading/png/result_2317.TW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arleigh418/python-and-Taiwan-stock-market/HEAD/Trading/png/result_2317.TW.png -------------------------------------------------------------------------------- /Trading/png/result_2330.TW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arleigh418/python-and-Taiwan-stock-market/HEAD/Trading/png/result_2330.TW.png -------------------------------------------------------------------------------- /Trading/png/result_2454.TW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arleigh418/python-and-Taiwan-stock-market/HEAD/Trading/png/result_2454.TW.png -------------------------------------------------------------------------------- /Trading/png/result_2603.TW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arleigh418/python-and-Taiwan-stock-market/HEAD/Trading/png/result_2603.TW.png -------------------------------------------------------------------------------- /Trading/png/result_6505.TW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arleigh418/python-and-Taiwan-stock-market/HEAD/Trading/png/result_6505.TW.png -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter3/holidaySchedule.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arleigh418/python-and-Taiwan-stock-market/HEAD/Trading Strategy_EX/Chapter3/holidaySchedule.csv -------------------------------------------------------------------------------- /Trading/AES_Encryption/__pycache__/en_decrype.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arleigh418/python-and-Taiwan-stock-market/HEAD/Trading/AES_Encryption/__pycache__/en_decrype.cpython-37.pyc -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter3/__pycache__/is_open.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arleigh418/python-and-Taiwan-stock-market/HEAD/Trading Strategy_EX/Chapter3/__pycache__/is_open.cpython-37.pyc -------------------------------------------------------------------------------- /Trading/AES_Encryption/__pycache__/encrype_process.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arleigh418/python-and-Taiwan-stock-market/HEAD/Trading/AES_Encryption/__pycache__/encrype_process.cpython-37.pyc -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter2/__pycache__/yahoo_price.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arleigh418/python-and-Taiwan-stock-market/HEAD/Trading Strategy_EX/Chapter2/__pycache__/yahoo_price.cpython-37.pyc -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter2/__pycache__/yahoo_finance.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arleigh418/python-and-Taiwan-stock-market/HEAD/Trading Strategy_EX/Chapter2/__pycache__/yahoo_finance.cpython-37.pyc -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter3/AES_Encryption/__pycache__/en_decrype.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arleigh418/python-and-Taiwan-stock-market/HEAD/Trading Strategy_EX/Chapter3/AES_Encryption/__pycache__/en_decrype.cpython-37.pyc -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter3/AES_Encryption/__pycache__/encrype_process.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arleigh418/python-and-Taiwan-stock-market/HEAD/Trading Strategy_EX/Chapter3/AES_Encryption/__pycache__/encrype_process.cpython-37.pyc -------------------------------------------------------------------------------- /Trading/strategy_research.py: -------------------------------------------------------------------------------- 1 | #%% 2 | #import需要的套件 3 | import pyfolio as pf 4 | import yfinance as yf 5 | import pandas as pd 6 | import matplotlib.pyplot as plt 7 | stock = yf.Ticker(f'2330.TW') 8 | return_ser = stock.history(start='2012-01-01',end='2021-05-16') 9 | pf.create_returns_tear_sheet(return_ser['Close'].pct_change()) 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | # %% 18 | -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter2/TWSE.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import pandas as pd 4 | def twse_data(r_date:str): 5 | #一樣我們對api進行請求 6 | data = requests.get(f'https://www.twse.com.tw/fund/T86?response=json&date={r_date}&selectType=ALLBUT0999&_=1614316365630') 7 | #使用json套件將他loads成json格式之後處理 8 | data_json = json.loads(data.text) 9 | #我們知道了欄位是fields,資料是data 10 | data_store = pd.DataFrame(data_json['data'],columns=data_json['fields']) 11 | return data_store 12 | 13 | 14 | -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter3/mine_ta.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | def mine_add_all_features(df:pd.DataFrame): 4 | #rolling以6為單位位移並取最大值 5 | Highest_high =df['High'].rolling(6).max() 6 | #rolling以6為單位位移並取最小值 7 | Lowest_low = df['Low'].rolling(6).min() 8 | #一樣用6根作為rolling,並且設計計算函數第一個值減去最後一個值 9 | O_C_high = df['High'].rolling(6).apply(lambda x : x[0]-x[-1]) 10 | #加入dataframe 11 | df['OCHIGH'] = O_C_high 12 | df['Highest_high'] = Highest_high 13 | df['Lowest_Low'] = Lowest_low 14 | return df -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter3/deal_holiday.py: -------------------------------------------------------------------------------- 1 | #import必要套件 2 | import pandas as pd 3 | import datetime 4 | 5 | #讀取下載的csv檔案,skiprows可以讓你跳過第一列,或甚至多列 6 | x = pd.read_csv("D:\Trading Strategy_EX\Chapter3\holidaySchedule.csv",encoding='big5',skiprows=[0]) 7 | #today獲取今天的日期 8 | today = datetime.date.today() 9 | #透過strftime只留下年 10 | convert_today = today.strftime('%Y') 11 | #針對日期apply,將2021加上年與原先的日期 12 | x['日期'] = x['日期'].apply(lambda x:convert_today+'年'+x) 13 | 14 | #一樣對日期做apply,並且replace年月為/,日為空白 15 | x['日期'] = x['日期'].apply(lambda x:x.replace('年','/')) 16 | x['日期'] = x['日期'].apply(lambda x:x.replace('月','/')) 17 | x['日期'] = x['日期'].apply(lambda x:x.replace('日','')) 18 | print(x['日期']) 19 | #儲存成名為holiday.xlsx的檔案 20 | x['日期'].to_excel('D:\Trading Strategy_EX\Chapter3\holiday.xlsx',columns=['日期']) -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter3/is_open.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import datetime 3 | 4 | def is_open(target_date:datetime.date): 5 | #讀取剛剛的休市日期檔案 6 | hd = pd.read_excel(r"D:\Trading Strategy_EX\Chapter3\holiday.xlsx") 7 | #轉換為list備用 8 | hd_date = pd.to_datetime(hd['日期']).tolist() 9 | #將日期進行格式化 10 | str_date = target_date.strftime('%Y%m%d') 11 | #將原先是today的地方換掉 12 | day = target_date.weekday() 13 | #判定是不是星期六或日,如果是的話就print N出來 14 | if day == 5 or day==6: 15 | return 'N' 16 | #Loop國定假日的list 17 | for i in hd_date: 18 | #將Timestamp類格式化為與目標日期同樣的字串 19 | i = i.strftime('%Y%m%d') 20 | #檢查是否有國定假日跟目標日期一樣,如果一樣print N,表示目標日期為國定假日,結束程式 21 | if (i==str_date): 22 | return 'N' 23 | return 'Y' 24 | 25 | 26 | -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter2/yahoo_price.py: -------------------------------------------------------------------------------- 1 | #import套件 2 | import requests 3 | #from xxx import xxx意思是我只需要用到bs4裡面的BeautifulSoup這個Class,我就不用全部import了,我只import BeautifulSoup就好,省時省資源 4 | from bs4 import BeautifulSoup 5 | #定義函數名stock_price,並且需要傳入字串類型的股票代號 6 | def stock_price(stock:str): 7 | #準備headers 8 | headers = { 9 | "user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36" 10 | } 11 | #使用f-string變動網址中的股價部分,而帶入的資訊就是函數所需的股票代號 12 | data = requests.get(f"https://finance.yahoo.com/quote/{stock}?p={stock}",headers=headers) 13 | #準備使用BeautifulSoup進行解析 14 | soup = BeautifulSoup(data.text) 15 | #find尋找元素 16 | price = soup.find("fin-streamer", {"data-test": "qsp-price"}) 17 | #返回float的價格資訊 18 | return price.text 19 | -------------------------------------------------------------------------------- /Trading/requirement.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.9.3 2 | bs4==0.0.1 3 | certifi==2020.12.5 4 | chardet==3.0.4 5 | colorama==0.4.4 6 | cycler==0.10.0 7 | et-xmlfile==1.0.1 8 | idna==2.8 9 | importlib-metadata==1.6.0 10 | jdcal==1.4.1 11 | kiwisolver==1.3.1 12 | loguru==0.4.1 13 | lxml==4.6.2 14 | matplotlib==3.3.4 15 | mpl-finance==0.10.1 16 | msgpack==1.0.2 17 | multitasking==0.0.9 18 | numpy==1.20.1 19 | openpyxl==3.0.6 20 | orjson==3.3.1 21 | pandas==1.2.3 22 | Pillow==8.1.2 23 | pycryptodome==3.9.7 24 | pydantic==1.0 25 | pyparsing==2.4.7 26 | pysolace==0.9.8 27 | python-dateutil==2.8.1 28 | pytz==2021.1 29 | requests==2.22.0 30 | sentry-sdk==0.14.1 31 | shioaji==0.3.1.dev8 32 | six==1.15.0 33 | soupsieve==2.2 34 | ta==0.7.0 35 | urllib3==1.25.11 36 | win32-setctime==1.0.3 37 | xxhash==2.0.0 38 | yfinance==0.1.59 39 | zipp==3.4.1 40 | backtrader 41 | pyfolio-reloaded 42 | -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter3/pd_example.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import yfinance as yf 3 | #yfinance產出台積電股價資料 4 | stock = yf.Ticker('2330.TW') 5 | #獲取20170101-20210202 6 | df = stock.history(start="2017-01-01",end="2021-02-02") 7 | #rolling以6為單位位移並取最大值 8 | Highest_high =df['High'].rolling(6).max() 9 | #rolling以6為單位位移並取最小值 10 | Lowest_low = df['Low'].rolling(6).min() 11 | 12 | #一樣用6根作為rolling,並且設計計算函數第一個值減去最後一個值 13 | O_C_high = df['High'].rolling(6).apply(lambda x : x[0]-x[-1]) 14 | #加入dataframe 15 | df['OCHIGH'] = O_C_high 16 | #如果遇到 ValueError: Excel does not support datetimes with timezones. 17 | #Please ensure that datetimes are timezone unaware before writing to Excel. 18 | # df.index = df.index.tz_localize(None) 19 | #存成Excel來看一下結果 20 | df.to_excel(r'D:\Trading Strategy_EX\Chapter3\final.xlsx') 21 | 22 | df['Highest_high'] = Highest_high 23 | df['Lowest_Low'] = Lowest_low 24 | df['OCHIGH'] = O_C_high 25 | print(df) 26 | -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter3/yfinance_example.py: -------------------------------------------------------------------------------- 1 | import yfinance as yf 2 | #指定2330.TW這支股票 3 | stock = yf.Ticker('2330.TW') 4 | print('======獲取歷史股價-指定區間======') 5 | df = stock.history(start="2017-01-01",end="2021-02-02") #period=max 6 | print(df) 7 | print('======獲取歷史股價-所有區間======') 8 | df = stock.history(period='max') 9 | print(df) 10 | 11 | 12 | print('======股票基本信息======') 13 | df_info = stock.info 14 | print(df_info) 15 | print('======主要持有人======') 16 | major_holders = stock.major_holders 17 | print(major_holders) 18 | 19 | print('======主要持有之機構法人======') 20 | ins_holders =stock.institutional_holders 21 | print(ins_holders) 22 | 23 | print('======取得損益表,執行看看結果======') 24 | fin_data = stock.financials 25 | print(fin_data) 26 | 27 | print('======取得資產負債表,執行看看結果======') 28 | balance_data = stock.balance_sheet 29 | print(balance_data) 30 | 31 | print('======取得現金流量表,執行看看結果======') 32 | cf_data = stock.cashflow 33 | print(cf_data) 34 | 35 | print('======分析師建議======') 36 | print(stock.recommendations) -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter3/ta_example.py: -------------------------------------------------------------------------------- 1 | import ta 2 | import yfinance as yf 3 | import pandas as pd 4 | 5 | #yfinance產出台積電股價資料 6 | stock = yf.Ticker('2330.TW') 7 | #獲取20170101-20210202 8 | df = stock.history(start="2017-01-01",end="2021-02-02") 9 | 10 | 11 | #一次性產生40多種技術指標 12 | data = ta.add_all_ta_features(df, "Open", "High", "Low", "Close", "Volume", fillna=True) 13 | data 14 | #使用Class ma並且呼叫他的function sma_indicator() 15 | ma = ta.trend.SMAIndicator(df['Close'],10,fillna=True) 16 | ma = ma.sma_indicator() 17 | ma 18 | 19 | #呼叫布林通道 20 | indicator_bb = ta.volatility.BollingerBands(close=df["Close"], window=20, window_dev=2) 21 | #布林中線 22 | bb_bbm = indicator_bb.bollinger_mavg() 23 | #布林上線 24 | bb_bbh = indicator_bb.bollinger_hband() 25 | #布林下線 26 | bb_bbl = indicator_bb.bollinger_lband() 27 | print('布林中線\n',bb_bbm) 28 | 29 | # 返回Close是否大於布林上軌,大於的話返回1,反之為0 30 | bb_bbhi = indicator_bb.bollinger_hband_indicator() 31 | # 返回Close是否小於布林下軌,小於的話返回1,反之為0 32 | bb_bbli = indicator_bb.bollinger_lband_indicator() 33 | # 布林帶寬 34 | bb_bbw = indicator_bb.bollinger_wband() 35 | # 布林%b指標 (%b值 = (收盤價−布林帶下軌值) ÷ (布林帶上軌值−布林帶下軌值)) 36 | bb_bbp = indicator_bb.bollinger_pband() 37 | -------------------------------------------------------------------------------- /Trading/requirement_python310.txt: -------------------------------------------------------------------------------- 1 | annotated-types==0.7.0 2 | base58==2.1.1 3 | beautifulsoup4==4.13.3 4 | bs4==0.0.2 5 | certifi==2025.1.31 6 | cffi==1.17.1 7 | chardet==5.2.0 8 | charset-normalizer==3.4.1 9 | colorama==0.4.6 10 | contourpy==1.3.1 11 | cycler==0.12.1 12 | et_xmlfile==2.0.0 13 | filelock==3.4.1 14 | fonttools==4.56.0 15 | frozendict==2.4.6 16 | idna==3.10 17 | importlib_metadata==8.6.1 18 | jdcal==1.4.1 19 | kiwisolver==1.4.8 20 | loguru==0.6.0 21 | lxml==5.3.1 22 | matplotlib==3.10.1 23 | mpl-finance==0.10.1 24 | msgpack==1.1.0 25 | multitasking==0.0.11 26 | numpy==2.2.3 27 | openpyxl==3.1.5 28 | orjson==3.10.15 29 | packaging==24.2 30 | pandas==2.2.3 31 | peewee==3.17.9 32 | pillow==11.1.0 33 | platformdirs==4.3.6 34 | pycparser==2.22 35 | pycryptodome==3.21.0 36 | pydantic==2.10.6 37 | pydantic_core==2.27.2 38 | PyNaCl==1.5.0 39 | pyparsing==3.2.1 40 | pyrsca==0.1.2 41 | pysolace==0.9.40 42 | python-dateutil==2.9.0.post0 43 | pytz==2025.1 44 | requests==2.32.3 45 | sentry-sdk==1.5.12 46 | shioaji==1.2.5 47 | six==1.17.0 48 | soupsieve==2.6 49 | ta==0.11.0 50 | typing_extensions==4.12.2 51 | tzdata==2025.1 52 | urllib3==2.3.0 53 | win32_setctime==1.2.0 54 | xxhash==3.5.0 55 | yfinance==0.2.54 56 | zipp==3.21.0 57 | backtrader 58 | pyfolio-reloaded -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter2/stock_list.py: -------------------------------------------------------------------------------- 1 | #import套件,可將pandas簡寫成pd,呼叫函數pd.xxx而不需pandas.xx 2 | import pandas as pd 3 | import requests 4 | #加入headers 5 | headers = { 6 | "user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36" 7 | } 8 | #對網站進行requests,並加入指定的headers一同請求 9 | html_data = requests.get("https://isin.twse.com.tw/isin/C_public.jsp?strMode=2",headers=headers) 10 | 11 | #使用pandas的read_html處理表格式 12 | x = pd.read_html(html_data.text) 13 | #list取出list裡面的第一個元素,就是我們的Dataframe 14 | x = x[0] 15 | #pandas的好用函數iloc切片,我們指定dataframe的欄位為第一列 16 | x.columns = x.iloc[0,:] 17 | #欄位雖然變成了正確的,但本來的那一列仍然存在,我們把它拿掉 18 | x = x.iloc[1:,:] 19 | #使用split方法,以兩個空白切割字串,並取切割完後第一個,儲存至新增的代號欄位 20 | x['代號'] = x['有價證券代號及名稱'].apply(lambda x: x.split()[0]) 21 | #使用split方法,以兩個空白切割字串,並取切割完後第一個,儲存至新增的股票名稱欄位 22 | x['股票名稱'] = x['有價證券代號及名稱'].apply(lambda x: x.split()[-1]) 23 | #善用to_datetime函數,並將無法轉成datetime的資料化為Nan 24 | x['上市日'] = pd.to_datetime(x['上市日'], errors='coerce') 25 | #把上市日的Nan去掉即可 26 | x = x.dropna(subset=['上市日']) 27 | #Drop掉不要的欄位 28 | x = x.drop(['有價證券代號及名稱', '國際證券辨識號碼(ISIN Code)', 'CFICode','備註'], axis=1) 29 | #更換剩餘的欄位順序 30 | x = x[['代號','股票名稱', '上市日', '市場別', '產業別']] 31 | #Drop掉產業別是空的欄位 32 | x = x.dropna(subset=['產業別']) 33 | #pandas的str.isdigit()函數,確認是不是為數字 34 | x = x[x["代號"].str.isdigit()] 35 | #印出x來看 36 | print(x) 37 | #儲存成excel 38 | x.to_excel('D:\Trading Strategy_EX\Chapter2\stock_list.xlsx') 39 | -------------------------------------------------------------------------------- /Trading/2_buy_with_devidend.py: -------------------------------------------------------------------------------- 1 | #import需要的套件 2 | import yfinance as yf 3 | import pandas as pd 4 | import numpy as np 5 | import time 6 | import datetime 7 | import traceback 8 | import utility_f as uf 9 | try: 10 | #讀取台股列表 11 | stock_list = pd.read_excel('D:\Trading\stock_list.xlsx') 12 | #獲取所有的股票代號 13 | all_stock = stock_list['代號'].values 14 | 15 | #儲存每支股票的殖利率 16 | dividend_store = [] 17 | stock_store = [] 18 | #計數用 19 | count = 0 20 | #迴圈loop每一支股票 21 | for i in all_stock: 22 | #我們來計算一下每一筆處理的時間,在開頭記錄一個start時間點 23 | start = time.time() 24 | #計數用參數 25 | count+=1 26 | #yfinance呼叫該股Ticker類 27 | stock = yf.Ticker(f'{i}.TW') 28 | #info中具備殖利率信息,拿來使用,並排除None 29 | try: 30 | if stock.info['dividendYield'] !=None: 31 | #有的話取殖利率 32 | d_y = stock.info['dividendYield'] 33 | #有時候yfinance回傳的資料有異常需做排除,如果值不是0且大於0.05的才儲存 34 | if d_y!=None and d_y>=0.05: 35 | stock_store.append(i) 36 | dividend_store.append(d_y) 37 | else: 38 | d_y=None 39 | #記錄每一筆結束時間 40 | end = time.time() 41 | #將進度print出來 42 | print(f'Dealing: {count} | All: {len(all_stock)} | Stock: {i} | DY: {d_y} | Cost Time: {end-start}s') 43 | except: 44 | print(f'Error Stock ! Dealing: {count} | All: {len(all_stock)} | Stock: {i}') 45 | data = pd.DataFrame() 46 | data['代號'] = stock_store 47 | data['殖利率'] = dividend_store 48 | data.to_excel('D:\Trading\dividend_list.xlsx') 49 | except SystemExit: 50 | print('Its OK') 51 | except: 52 | today = datetime.date.today() 53 | #收件名單 54 | mail_list = ['a*****@gmail.com','0*****@gm.scu.edu.tw'] 55 | #標題我們加上日期,淺顯易懂 56 | subject = f'{today} 小幫手高配息名單篩選異常' 57 | #內容就是剛剛篩選完成的股票 58 | body = traceback.format_exc() 59 | #寄信 60 | uf.send_mail(mail_list, subject, body,'text', None, None) 61 | 62 | 63 | -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter2/yahoo_news.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | import pandas as pd 4 | 5 | def get_yahoo_news(stock:str,target_page:int): 6 | #===========================獲取頁數=========================== 7 | #準備headers 8 | headers = { 9 | "user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36" 10 | } 11 | #requests指定網址,並且帶入headers 12 | data = requests.get(f"https://tw.stock.yahoo.com/q/h?s={stock}",headers=headers) 13 | #使用BeautifulSoup解析 14 | soup = BeautifulSoup(data.text) 15 | #find我們找到的共幾頁元素 16 | page = soup.find('span',{'class':'mtext'}) 17 | #建立一個空字串 18 | x = '' 19 | #迴圈處理page這個元素,符合的就加起來 20 | for i in page.text: 21 | if i.isdigit(): 22 | x+=i 23 | x = int(x) 24 | #如果目標頁數比x小,那我們的拿來帶入頁數的x就可以直接替換成目標頁數 25 | if target_page= 1: 29 | i = i + 1 30 | new_byte = org_bytes[(i-1)*32:32*i-1] 31 | new_bytes += new_byte 32 | n = n - 1 33 | if len(org_bytes) % 32 == 0: 34 | all_bytes = org_bytes 35 | elif len(org_bytes) % 32 != 0 and n>1: 36 | all_bytes = new_bytes + add_to_32 (org_bytes[i*32:]) 37 | else: 38 | all_bytes = add_to_32 (org_bytes) 39 | return all_bytes 40 | 41 | 42 | def aes_encrypt(data, key): 43 | cryptor = AES.new(key, AES.MODE_CBC, iv=b'0123456789abcdef') 44 | encrypt_aes = cryptor.encrypt(cut_value(data)) 45 | encrypted_text = str(base64.encodebytes(encrypt_aes), encoding='utf-8') 46 | return encrypted_text 47 | 48 | def aes_decrypt(secret_str, key): 49 | cryptor = AES.new(key, AES.MODE_CBC, iv=b'0123456789abcdef') 50 | base64_decrypted = base64.decodebytes(secret_str.encode(encoding='utf-8')) 51 | decrypted_text = str(cryptor.decrypt(base64_decrypted), encoding='utf-8').replace('\0', '') 52 | return decrypted_text 53 | 54 | 55 | 56 | def token_bytes(): 57 | nbytes = DEFAULT_ENTROPY 58 | return os.urandom(nbytes) 59 | 60 | 61 | 62 | 63 | def get_key(key_path,result_path): 64 | key_file_path = (key_path+'key.key') 65 | result_file_path = (result_path+'encrype.config') 66 | if os.path.isfile(key_file_path): 67 | with open(key_file_path, 'rb+') as outfile: 68 | return outfile.read() 69 | 70 | else: 71 | nbytes = DEFAULT_ENTROPY 72 | key = token_bytes() 73 | with open(key_file_path,'wb+') as f: 74 | f.write(key) 75 | os.remove(result_file_path) 76 | return key 77 | 78 | -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter3/AES_Encryption/en_decrype.py: -------------------------------------------------------------------------------- 1 | from Crypto.Cipher import AES 2 | import os 3 | import hashlib 4 | import base64 5 | import json 6 | ''' 7 | reference: 8 | https://stackoverflow.com/questions/53320143/pycryptodome-aes-cbc-encryption-does-not-give-desired-output 9 | https://blog.csdn.net/sinat_37967865/article/details/100125445 10 | https://samkuo.me/post/2015/09/python-aes-256-and-sha-256-examples/?fbclid=IwAR3Mr9WPKMpPjLr-3P6DVr7suG6GMIXCC3oTbTTRR0pQ4b5-mEg4wwiKvns 11 | ''' 12 | 13 | DEFAULT_ENTROPY = 32 14 | 15 | 16 | def add_to_32(value): 17 | while len(value) % 32 != 0: 18 | value += b'\x00' 19 | return value 20 | 21 | 22 | 23 | def cut_value(org_str): 24 | org_bytes = str.encode(org_str) 25 | n = int(len(org_bytes) / 32) 26 | i = 0 27 | new_bytes = b'' 28 | while n >= 1: 29 | i = i + 1 30 | new_byte = org_bytes[(i-1)*32:32*i-1] 31 | new_bytes += new_byte 32 | n = n - 1 33 | if len(org_bytes) % 32 == 0: 34 | all_bytes = org_bytes 35 | elif len(org_bytes) % 32 != 0 and n>1: 36 | all_bytes = new_bytes + add_to_32 (org_bytes[i*32:]) 37 | else: 38 | all_bytes = add_to_32 (org_bytes) 39 | return all_bytes 40 | 41 | 42 | def aes_encrypt(data, key): 43 | cryptor = AES.new(key, AES.MODE_CBC, iv=b'0123456789abcdef') 44 | encrypt_aes = cryptor.encrypt(cut_value(data)) 45 | encrypted_text = str(base64.encodebytes(encrypt_aes), encoding='utf-8') 46 | return encrypted_text 47 | 48 | def aes_decrypt(secret_str, key): 49 | cryptor = AES.new(key, AES.MODE_CBC, iv=b'0123456789abcdef') 50 | base64_decrypted = base64.decodebytes(secret_str.encode(encoding='utf-8')) 51 | decrypted_text = str(cryptor.decrypt(base64_decrypted), encoding='utf-8').replace('\0', '') 52 | return decrypted_text 53 | 54 | 55 | 56 | def token_bytes(): 57 | nbytes = DEFAULT_ENTROPY 58 | return os.urandom(nbytes) 59 | 60 | 61 | 62 | 63 | def get_key(key_path,result_path): 64 | key_file_path = (key_path+'key.key') 65 | result_file_path = (result_path+'encrype.config') 66 | if os.path.isfile(key_file_path): 67 | with open(key_file_path, 'rb+') as outfile: 68 | return outfile.read() 69 | 70 | else: 71 | nbytes = DEFAULT_ENTROPY 72 | key = token_bytes() 73 | with open(key_file_path,'wb+') as f: 74 | f.write(key) 75 | os.remove(result_file_path) 76 | return key 77 | 78 | -------------------------------------------------------------------------------- /Trading Strategy_EX/Chapter2/yahoo_news_2.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | import pandas as pd 4 | import requests 5 | from bs4 import BeautifulSoup 6 | import pandas as pd 7 | 8 | """ 9 | 設計上是先在新聞列表中取得每一篇新聞的連結,再去每一篇新聞獲取標題以及日期資訊 10 | 其實新聞列表就已經具有標題以及連結這些元素,但問題是他的時間是例如1小時前、50分鐘前、三天前 11 | 這並不是我們想要的格式,所以我們還需要一個步驟,獲取每個詳細新聞網址去裡頭獲取詳細的年月日時間(例如2021/9/27) 12 | 當然如果你不介意例如1小時前這樣的時間,那爬蟲速度應該至少可以快50%以上 13 | 因為就不需要每一篇新聞都還要為了時間資訊去request獲取資訊 14 | 看個人取捨設計了,我先寫比較繁複的,這樣讀者要改成簡單的也比較容易 15 | """ 16 | 17 | 18 | def get_yahoo_news2(stock: str): 19 | # 準備headers 20 | headers = { 21 | "user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36" 22 | } 23 | # requests指定網址,並且帶入headers 24 | data = requests.get( 25 | f"https://tw.stock.yahoo.com/quote/{stock}/news", headers=headers 26 | ) 27 | # 使用BeautifulSoup解析,指定解析方法html.parser可以避免warning 28 | soup = BeautifulSoup(data.text, "html.parser") 29 | 30 | # 選取標題的tag來使用,讀者自行練習的時候應該會發現有許多tag可以使用,基本上可以使用就可以,未必要跟我的一樣 31 | all_news = soup.find_all("h3", {"class": "Mt(0) Mb(8px)"}) 32 | 33 | # 創建空list儲存每一篇新聞的連結 34 | all_news_store = [] 35 | # loop每一個抓回來的元素 36 | for a in all_news: 37 | # 藉由find獲得的物件還可以再find,因此我們find a元素,並且取得裡面的href,也就是網址 38 | news_path = a.find("a")["href"] 39 | # 雅虎新聞目前的設計是中間會夾雜一兩篇廣告,這些廣告的特點是他們的連結最後沒有html的文字,因此可以用這個方法剔除他們 40 | if news_path[-4:] == "html": 41 | # 最後append進list備用 42 | all_news_store.append(news_path) 43 | 44 | # 開始進行獲取日期,標題我也在這裡做,當然如果你要再上一個步驟就獲取標題也可以 45 | # 創建空list備用 46 | date_store, title_store = [], [] 47 | # loop剛剛獲得的每一篇新聞網址 48 | for new in all_news_store: 49 | # 做requests 50 | each_data = requests.get(f"{new}", headers=headers) 51 | # BeautifulSoup整理格式,指定解析方法html.parser可以避免warning 52 | each_soup = BeautifulSoup(each_data.text, "html.parser") 53 | # find獲取title 54 | title = each_soup.find("h1", {"data-test-locator": "headline"}).text 55 | # find獲取時間 56 | news_time = each_soup.find("div", {"class": "caas-attr-time-style"}).text 57 | # 整理時間格式,只想要前面的年月日資訊,中間有空白剛好可以利用split切割空白,取第一個元素 58 | news_time = news_time.split(" ")[0] 59 | # 這裡看個人要不要做,我不喜歡例如2021年9月27日這樣的格式,因此我用replace轉換成2021/9/27 60 | news_time = news_time.replace("年", "/") 61 | news_time = news_time.replace("月", "/") 62 | news_time = news_time.replace("日", "") 63 | # append整理資料 64 | title_store.append(title) 65 | date_store.append(news_time) 66 | # 整理成DataFrame 67 | result = pd.DataFrame() 68 | result["title"] = title_store 69 | result["url"] = all_news_store 70 | result["date"] = date_store 71 | return result 72 | -------------------------------------------------------------------------------- /Trading/1_buy_follow_corp.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('D:\Trading') 3 | #import剛剛的函數包as uf 4 | import utility_f as uf 5 | import datetime 6 | import os 7 | import pandas as pd 8 | import traceback 9 | try: 10 | #獲取今日日期 11 | today = datetime.date.today() 12 | #傳入判斷今天是否為營業日 13 | if_trade = uf.is_open(today) 14 | #如果是N,表示沒開盤,準備寄信 15 | if if_trade=='N': 16 | #沒開盤應該收件者 17 | mail_list = ['a********@gmail.com'] 18 | #標題為三大法人篩選,今日休市 19 | subject = f'{today} 小幫手三大法人篩選 - 今日休市' 20 | #郵件內容為空 21 | body = '' 22 | #寄信 23 | uf.send_mail(mail_list, subject, body, 'text' ,None, None) 24 | exit() 25 | #用control來控制獲得了幾個有開市的資料 26 | control = 0 27 | #迴圈搜索10次 28 | for i in range(0,10): 29 | #如果control比2還小(0、1、2實際上是三次),我們就展開搜索 30 | if control <=2: 31 | #datetime.timedelta()可以對datetime類型作日期運算 32 | date_target = today + datetime.timedelta(days =-int(i)) 33 | #用我們的函數is_open判斷是否開盤 34 | if_trade = uf.is_open(date_target) 35 | print(date_target) 36 | print(if_trade) 37 | #未開盤的話則continue直接進行下一批檢查 38 | if if_trade=='N': 39 | continue 40 | #有開盤則control要記得+1,並且進行三大法人買賣超處理 41 | else: 42 | #將日期轉為字串以便傳入twse_data()函數 43 | convert_today = date_target.strftime('%Y%m%d') 44 | #獲取三大法人買賣超日報 45 | data = uf.twse_data(convert_today) 46 | #儲存成excel 47 | data.to_excel(f'{convert_today}_twse.xlsx') 48 | #讀取時使用thousands參數讀取,處理數字問題 49 | data = pd.read_excel(f'{convert_today}_twse.xlsx',thousands=',') 50 | #刪除檔案避免檔案堆積 51 | os.remove(f'{convert_today}_twse.xlsx') 52 | #只保留三大法人買賣超股數大於0的 53 | d_s = data[(data[u'三大法人買賣超股數']>0)] 54 | #獲取前50名三大法人買超最大量的 55 | d_s = d_s[:50] 56 | #當control ==0的時候意味著是第一次搜尋到清單,因此當主軸 57 | if control==0: 58 | result = set(d_s[u'證券代號'].tolist()) 59 | #如果不是的話我們用intersection函數來取交集 60 | else: 61 | result = result.intersection(set(d_s[u'證券代號'].tolist())) 62 | #全部處理了control才+1,要注意位階,是在if else之外 63 | control+=1 64 | else: 65 | break 66 | #收件名單 67 | mail_list = ['a********@gmail.com','0********@gm.scu.edu.tw'] 68 | #將結果用逗號黏成字串,非必要,但是我不喜歡list的中括號,看起來蠻醜的 69 | result = ",".join(result) 70 | #標題我們加上日期,淺顯易懂 71 | subject = f'{today} 小幫手三大法人篩選' 72 | #內容就是剛剛篩選完成的股票 73 | body = f'目標股票 {result} 連續三日法人買超' 74 | #寄信 75 | uf.send_mail(mail_list, subject, body, 'text' ,None, None) 76 | except SystemExit: 77 | print('Its OK') 78 | except: 79 | #收件名單 80 | mail_list = ['a********@gmail.com','0********@gm.scu.edu.tw'] 81 | #標題我們加上日期,淺顯易懂 82 | subject = f'{today} 小幫手三大法人篩選異常' 83 | #內容就是剛剛篩選完成的股票 84 | body = traceback.format_exc() 85 | #寄信 86 | uf.send_mail(mail_list, subject, body, 'text' ,None, None) 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /Trading/2_2_buy_with_dividend_price.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('D:\Trading') 3 | import utility_f as uf 4 | import pandas as pd 5 | import yfinance as yf 6 | import numpy as np 7 | import datetime 8 | import traceback 9 | try: 10 | #讀取高配息清單 11 | data = pd.read_excel('D:/Trading/dividend_list.xlsx') 12 | #獲取代號 13 | target_stock = data['代號'].tolist() 14 | #獲取今日日期 15 | today = datetime.date.today() 16 | #傳入判斷今天是否為營業日 17 | if_trade = uf.is_open(today) 18 | # #如果是N,表示沒開盤,準備寄信 19 | if if_trade=='N': 20 | #沒開盤應該收件者 21 | mail_list = ['a*****@gmail.com'] 22 | #標題為三大法人篩選,今日休市 23 | subject = f'{today} 小幫手高配息低股價,每日價格比對 - 今日休市' 24 | #郵件內容為空 25 | body = '' 26 | #寄信 27 | uf.send_mail(mail_list, subject, body, 'text',None, None) 28 | exit() 29 | 30 | #獲取過去一年的日期,我們以365天估算 31 | date_start = today + datetime.timedelta(days =-365) 32 | #轉為str格式等一下準備傳入yfinance的history獲取歷史股價 33 | date_start = date_start.strftime('%Y-%m-%d') 34 | #獲取T+1日,因為yfinance的end日期是到T-1 35 | date_end = today + datetime.timedelta(days =1) 36 | #轉為str格式等一下準備傳入yfinance的history獲取歷史股價 37 | date_end = date_end.strftime('%Y-%m-%d') 38 | 39 | #創建空list備用 40 | target_store = [] 41 | highest_store = [] 42 | now_price_store = [] 43 | #迴圈處理每一支目標股票 44 | for target in target_stock: 45 | #獲取目標股票的Ticker類 46 | stock = yf.Ticker(f'{target}.TW') 47 | #傳入start跟end獲取歷史股價 48 | df = stock.history(start=date_start,end=date_end) 49 | #轉為array並使用np.max獲取最大值 50 | highest = np.max(df['High'].values) 51 | #取最後一筆的Close價格作為目標 52 | now_price =df['Close'].values[-1] 53 | #如果目標小於一年內最高價的70%則執行以下操作 54 | if now_price< highest*0.7: 55 | #儲存股票代號 56 | target_store.append(target) 57 | #儲存一年內最高價 58 | highest_store.append(highest) 59 | #儲存最近的收盤價 60 | now_price_store.append(now_price) 61 | #print出來觀賞 62 | print(f'Stock: {target} | high 70%: {highest*0.7} | now: {now_price} | Status : Get!') 63 | #讀取所有股票清單 64 | all_stock_list = pd.read_excel('D:/Trading/stock_list.xlsx') 65 | #創建空list備用 66 | stock_name_store = [] 67 | #loop符合我們目標的股票 68 | for st in target_store: 69 | #dataframe的篩選,篩選出目標股票 70 | select_data = all_stock_list[(all_stock_list[u'代號']==st)] 71 | #取得後取股票平稱後轉values再取裡面的元素 72 | target = select_data['股票名稱'].values[0] 73 | #append進list 74 | stock_name_store.append(target) 75 | #創建空的dataframe準備寄信 76 | empty_df = pd.DataFrame() 77 | empty_df['日期'] = len(target_store)*[today] 78 | empty_df['股票代號'] = target_store 79 | empty_df['股票名稱'] = stock_name_store 80 | empty_df['最近收盤'] = now_price_store 81 | empty_df['一年內最高'] = highest_store 82 | #將dataframe轉為html格式 83 | empty_df = empty_df.to_html(index=False) 84 | print(empty_df) 85 | #製作html格式 86 | body = f''' 87 | 88 | 89 |

90 | 小幫手系列偵測下表股票配息高且股價相對低 91 |

92 | {empty_df} 93 |
投資理財有賺有賠,請謹慎評估風險
94 | 95 | ''' 96 | #寄信名單 97 | mail_list = ['a*****@gmail.com','0*****@gm.scu.edu.tw'] 98 | #標題我們加上日期,淺顯易懂 99 | subject = f'{today} 小幫手高配息低股價-每日價格比對' 100 | #寄信 101 | uf.send_mail(mail_list, subject, body,'html', None, None) 102 | except SystemExit: 103 | print('Its OK') 104 | except: 105 | today = datetime.date.today() 106 | #收件名單 107 | mail_list = ['a*****@gmail.com','0*****@gm.scu.edu.tw'] 108 | #標題我們加上日期,淺顯易懂 109 | subject = f'{today} 小幫手高配息低股價-每日價格比對異常' 110 | #內容就是剛剛篩選完成的股票 111 | body = traceback.format_exc() 112 | #寄信 113 | uf.send_mail(mail_list, subject, body,'text', None, None) -------------------------------------------------------------------------------- /Trading/3_buy_with_price_fall.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('D:\Trading') 3 | import utility_f as uf 4 | import pandas as pd 5 | import yfinance as yf 6 | import numpy as np 7 | import datetime 8 | import time 9 | import traceback 10 | try: 11 | #讀取股票清單 12 | data = pd.read_excel('D:/Trading/stock_list.xlsx') 13 | #獲取代號 14 | target_stock = data['代號'].tolist() 15 | #獲取今日日期 16 | today = datetime.date.today() 17 | #傳入判斷今天是否為營業日 18 | if_trade = uf.is_open(today) 19 | #如果是N,表示沒開盤,準備寄信 20 | if if_trade=='N': 21 | #沒開盤應該收件者 22 | mail_list = ['a*****@gmail.com'] 23 | #標題為三大法人篩選,今日休市 24 | subject = f'{today} 小幫手暴跌中的股票偵測 - 今日休市' 25 | #郵件內容為空 26 | body = '' 27 | #寄信 28 | uf.send_mail(mail_list, subject, body, 'text',None, None) 29 | exit() 30 | #獲取過去10天的收盤價 31 | date_start = today + datetime.timedelta(days =-20) 32 | #轉為str格式等一下準備傳入yfinance的history獲取歷史股價 33 | date_start = date_start.strftime('%Y-%m-%d') 34 | #獲取T+1日,因為yfinance的end日期是到T-1 35 | date_end = today + datetime.timedelta(days =1) 36 | #轉為str格式等一下準備傳入yfinance的history獲取歷史股價 37 | date_end = date_end.strftime('%Y-%m-%d') 38 | #創建空list備用 39 | stock_store = [] 40 | today_store = [] 41 | today_fall = [] 42 | count = 0 43 | #迴圈處理每一支目標股票 44 | for target in target_stock: 45 | count+=1 46 | time.sleep(1) 47 | #獲取目標股票的Ticker類 48 | stock = yf.Ticker(f'{target}.TW') 49 | #傳入start跟end獲取歷史股價 50 | df = stock.history(start=date_start,end=date_end) 51 | #做一點基本的檢核,防止資料回傳0筆或1筆 52 | if len(df) >=5: 53 | #將收盤轉為array並且取最後一筆,今天的收盤 54 | today_price = df['Close'].values[-1] 55 | #將收盤轉為array並且取倒數第二筆,昨天的收盤 56 | yes_price = df['Close'].values[-2] 57 | #將收盤轉為array並且取倒數第二筆,前天的收盤 58 | be_yes_price = df['Close'].values[-3] 59 | #根據公式取得今日的漲跌幅 60 | fall_today = ((today_price - yes_price)/yes_price)*100 61 | #根據公式取得昨日的漲跌幅 62 | fall_yes = ((yes_price-be_yes_price)/be_yes_price)*100 63 | #兩個漲跌幅皆小於-5則是我們的目標,儲存起來。 64 | if fall_today<=-5 and fall_yes<=-5: 65 | print(f'Stock: { target} | fall today: {fall_today} | today: {today_price} | yes: {yes_price}') 66 | today_store.append(today_price) 67 | today_fall.append(fall_today) 68 | stock_store.append(target) 69 | #print出處理進度 70 | print(f'Dealing Stock: {target} | All Stock: {len(target_stock)} | Now: {count}') 71 | #控制用 72 | print('Get it:',stock_store) 73 | main_df = pd.DataFrame() 74 | #Loop剛剛篩選的結果 75 | for t in stock_store: 76 | #使用到跟requests有關的東西,習慣先sleep一下 77 | time.sleep(1) 78 | #我們要將目標股票的新聞連接再一起,contorl=0代表第一次loop 79 | #使用新聞函數獲取新聞,並且先獲取主要的dataframe,其他股票的就連接在後面 80 | merge_df = uf.get_yahoo_news(t,1) 81 | #多一個欄位叫做stock儲存這篇新聞是屬於那一支股票 82 | merge_df['stock'] = len(merge_df)*[t] 83 | main_df = main_df.append(merge_df) 84 | #處理要寄信的部分,首先將剛剛的新聞列表儲存成excel保存 85 | main_df.to_excel(f'D:/Trading/fall_stock_news.xlsx',index=False) 86 | #處理要寄信的部分,這裡處理我希望放在信件body的暴跌中的股票清單 87 | empty_dataframe = pd.DataFrame() 88 | empty_dataframe['股票代號'] = stock_store 89 | empty_dataframe['今價'] = today_store 90 | empty_dataframe['今日跌幅%'] = today_fall 91 | #希望寄出的是表格,因此我們將他轉為html備用 92 | empty_dataframe = empty_dataframe.to_html(index=False) 93 | #製作html格式 94 | body = f''' 95 | 96 | 97 |

98 | 小幫手系列偵測下表股票為暴跌中股票 99 |

100 | {empty_dataframe} 101 |
投資理財有賺有賠,請謹慎評估風險
102 | 103 | ''' 104 | #寄信名單 105 | mail_list = ['a******@gmail.com','0******@gm.scu.edu.tw'] 106 | #標題我們加上日期,淺顯易懂 107 | subject = f'{today} 小幫手暴跌中股票偵測' 108 | #寄信 109 | uf.send_mail(mail_list, subject, body,'html', [f'D:/Trading/fall_stock_news.xlsx'], ['相關新聞表.xlsx']) 110 | except SystemExit: 111 | print('Its OK') 112 | except: 113 | today = datetime.date.today() 114 | #收件名單 115 | mail_list = ['a******@gmail.com','0******@gm.scu.edu.tw'] 116 | #標題我們加上日期,淺顯易懂 117 | subject = f'{today} 小幫手暴跌中股票異常' 118 | #內容就是剛剛篩選完成的股票 119 | body = traceback.format_exc() 120 | #寄信 121 | uf.send_mail(mail_list, subject, body,'text', None, None) -------------------------------------------------------------------------------- /Trading/tech1_ma_strategy.py: -------------------------------------------------------------------------------- 1 | #%% 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | import datetime 6 | import os.path 7 | import sys 8 | import pyfolio 9 | import backtrader as bt 10 | # 建立一個backtrader回測框架 11 | class TestStrategy(bt.Strategy): 12 | #設置sma的參數,根據官方照此設置可進行暴力演算,得知何種參數最佳 13 | params = ( 14 | ('fast_period', 3), 15 | ('slow_period', 60), 16 | ) 17 | 18 | #這裡是log,當交易發生時呼叫log函數可以將交易print出來 19 | def log(self, txt, dt=None): 20 | ''' Logging function fot this strategy''' 21 | dt = dt or self.datas[0].datetime.date(0) 22 | print('%s, %s' % (dt.isoformat(), txt)) 23 | 24 | #init定義你會用到的數據 25 | def __init__(self): 26 | #呼叫close序列備用 27 | self.dataclose = self.datas[0].close 28 | #追蹤order、buyprice跟buycomm使用,可用可不用 29 | self.order = None 30 | self.buyprice = None 31 | self.buycomm = None 32 | #定義5ma跟60ma 33 | self.sma1 = bt.ind.SimpleMovingAverage(self.datas[0].close,period=self.params.fast_period) 34 | self.sma2 = bt.ind.SimpleMovingAverage(self.datas[0].close,period=self.params.slow_period) 35 | #使用bt.ind.CrossOver方法判斷兩條線的穿越關係 36 | self.crossover = bt.ind.CrossOver(self.sma1, self.sma2) 37 | #notify_order當每次有訂單由next偵測出來的條件送出時,會觸發notify_order,好處是顯示出訂單執行的狀況以及偵測是否有資金不足的情況 38 | def notify_order(self, order): 39 | if order.status in [order.Submitted, order.Accepted]: 40 | #當訂單為提交狀態時則不做任何事 41 | return 42 | 43 | # 當訂單完成時,若為Buy則print出買入狀況;反之亦然 44 | if order.status in [order.Completed]: 45 | if order.isbuy(): 46 | self.log( 47 | 'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' % 48 | (order.executed.price, 49 | order.executed.value, 50 | order.executed.comm)) 51 | self.buyprice = order.executed.price 52 | self.buycomm = order.executed.comm 53 | else: 54 | self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' % 55 | (order.executed.price, 56 | order.executed.value, 57 | order.executed.comm)) 58 | 59 | # 當因策略取消或是現今不足訂單被拒絕等狀況則print出訂單取消 60 | elif order.status in [order.Canceled, order.Margin, order.Rejected]: 61 | self.log('Order Canceled/Margin/Rejected') 62 | 63 | #完成該有的提醒之後則將oder設置回None 64 | self.order = None 65 | 66 | #notify_trade交易通知,預設如果有倉在手就不做事,如果執行賣出則print出獲利 67 | def notify_trade(self, trade): 68 | if not trade.isclosed: 69 | return 70 | 71 | self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % 72 | (trade.pnl, trade.pnlcomm)) 73 | #next可以把它想像成一個內建的for loop,他把數據打包好供我們使用 74 | def next(self): 75 | # print出每日收盤價 76 | self.log('Close, %.2f' % self.dataclose[0]) 77 | # 檢查有無pending的訂單 78 | if self.order: 79 | return 80 | 81 | #有無倉位在手,如果無代表 82 | 83 | if not self.position: 84 | # cross over>0意味著向上穿越 85 | if self.crossover>0: 86 | 87 | # 紀錄買單提交 88 | self.log('BUY CREATE, %.2f' % self.dataclose[0]) 89 | # 買進 90 | self.order = self.buy() 91 | 92 | else: 93 | if self.crossover<0: 94 | # 紀錄賣單提交 95 | self.log('SELL CREATE, %.2f' % self.dataclose[0]) 96 | # 賣出 97 | self.order = self.sell() 98 | #回測終止時print出結果 99 | # def stop(self): 100 | # print(f'Fast MA: {self.params.fast_period} | Slow MA: {self.params.slow_period} | End Value: {self.broker.getvalue()}') 101 | 102 | if __name__ == '__main__': 103 | # 創建框架 104 | cerebro = bt.Cerebro() 105 | # # 放入策略 106 | cerebro.addstrategy(TestStrategy) 107 | # # 放入策略 108 | # strats = cerebro.optstrategy( 109 | # TestStrategy, 110 | # fast_period = range(3, 7), 111 | # slow_period = range(40, 70, 10)) 112 | # 使用框架的資料取得函數 113 | data = bt.feeds.YahooFinanceData( 114 | dataname='2330.TW', 115 | # 開始日期 116 | fromdate=datetime.datetime(2014, 1, 1), 117 | # 結束日期 118 | todate=datetime.datetime(2020, 12, 31), 119 | reverse=False) 120 | # 將datafeed餵入框架 121 | cerebro.adddata(data) 122 | # 設置起始金額 123 | cerebro.broker.setcash(1000000.0) 124 | #設置一次購買的股數,台股以1000股為主 125 | cerebro.addsizer(bt.sizers.SizerFix, stake=1000) 126 | # 設置傭金,稍微設置高一點作為滑價付出成本 127 | cerebro.broker.setcommission(commission=0.0015) 128 | # print出起始金額 129 | print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue()) 130 | # 執行策略 131 | cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio') 132 | # Run over everything 133 | results = cerebro.run() 134 | # Print out the final result 135 | print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue()) 136 | strat = results[0] 137 | pyfoliozer = strat.analyzers.getbyname('pyfolio') 138 | returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items() 139 | 140 | # # pyfolio showtime 141 | import pyfolio as pf 142 | pf.create_full_tear_sheet( 143 | returns, 144 | positions=positions, 145 | transactions=transactions, 146 | live_start_date='2018-01-01') # This date is sample specific) 147 | # %% 148 | -------------------------------------------------------------------------------- /Trading/utility_f.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import pandas as pd 4 | import sys 5 | sys.path.append('D:\Trading') 6 | import smtplib 7 | from email.mime.multipart import MIMEMultipart 8 | from email.mime.text import MIMEText 9 | from email.mime.application import MIMEApplication 10 | from AES_Encryption.encrype_process import * 11 | import pandas as pd 12 | import datetime 13 | from bs4 import BeautifulSoup 14 | 15 | #是否開盤用函數,返回字串Y、N,Y代表有開盤,N反之 16 | ''' 17 | target_date = 傳入datetime格式日期,為需要判定是否開盤的日期 18 | ''' 19 | def is_open(target_date:datetime.date): 20 | #讀取剛剛的休市日期檔案 21 | hd = pd.read_excel(r"D:/Trading/holiday.xlsx") 22 | #轉換為list備用 23 | hd_date = pd.to_datetime(hd['日期']).tolist() 24 | #將日期進行格式化 25 | str_date = target_date.strftime('%Y%m%d') 26 | #將原先是today的地方換掉 27 | day = target_date.weekday() 28 | #判定是不是星期六或日,如果是的話就print N出來 29 | if day == 5 or day==6: 30 | return 'N' 31 | #Loop國定假日的list 32 | for i in hd_date: 33 | #將Timestamp類格式化為與目標日期同樣的字串 34 | i = i.strftime('%Y%m%d') 35 | #檢查是否有國定假日跟目標日期一樣,如果一樣print N,表示目標日期為國定假日,結束程式 36 | if (i==str_date): 37 | return 'N' 38 | #Y代表不符合六日也非國定假日,有開盤返回Y 39 | return 'Y' 40 | 41 | #三大法人買賣超日報,返回一份dataframe 42 | ''' 43 | r_date = 字串格式日期,為需要查詢三大法人買賣超日報的目標日期 44 | ''' 45 | def twse_data(r_date:str): 46 | #一樣我們對api進行請求 47 | data = requests.get(f'https://www.twse.com.tw/fund/T86?response=json&date={r_date}&selectType=ALLBUT0999&_=1614316365630') 48 | #使用json套件將他loads成json格式之後處理 49 | data_json = json.loads(data.text) 50 | #我們知道了欄位是fields,資料是data 51 | data_store = pd.DataFrame(data_json['data'],columns=data_json['fields']) 52 | return data_store 53 | 54 | #寄信函數 55 | ''' 56 | mail_list = 列表,需要寄信的清單 57 | subject = 字串,標題 58 | body = 字串,內容 59 | mode = 字串,支援text跟html兩種寄信模式 60 | file_path = 列表,想要寄出的檔案的位置 61 | file_name = 列表,希望收件者看到的檔名 62 | ''' 63 | def send_mail(mail_list:list, subject:str, body:str, mode :str , file_path:list, file_name:list): 64 | #決定金鑰跟config檔位置 65 | key_path = 'D:/key/' 66 | config_path = 'D:/config/' 67 | #引用加解密的主要程式check_encrype 68 | user_id, password = check_encrype('gmail',key_path,config_path) 69 | #創建一個MIMEMultipart()類 70 | msg = MIMEMultipart() 71 | #對它傳入三個基本信息: 寄件者(From)、收件者(To)、標題(Subject) 72 | msg['From'] = user_id 73 | #使用join將list中的元素以逗號黏起來 74 | msg['To'] = ",".join(mail_list) 75 | msg['Subject'] = subject 76 | #呼叫Attach,並傳入content信件內容 77 | if mode =='html': 78 | msg.attach(MIMEText(body, mode)) 79 | else: 80 | msg.attach(MIMEText(body)) 81 | #if else條件判斷使用者傳入的是否為None 82 | if file_path==None: 83 | pass 84 | #如果有傳入 85 | else: 86 | for x in range(len(file_path)): 87 | #先透過內建的with open讀取檔案 88 | with open(file_path[x], 'rb') as opened: 89 | openedfile = opened.read() 90 | #呼叫MIMEApplication並放入byte類型 91 | attachedfile = MIMEApplication(openedfile) 92 | #根據指示加入至附檔,並可以指定與原本檔名不一樣的獨立檔名 93 | attachedfile.add_header('content-disposition', 'attachment', filename = file_name[x]) 94 | #跟上面attach信件內容一樣,我們把附檔資訊也attach進去 95 | msg.attach(attachedfile) 96 | #設定smtp server,以gmail當例子 97 | server = smtplib.SMTP('smtp.gmail.com', 587) 98 | #TLS安全傳輸設定 99 | server.starttls() 100 | #登入你的gmail帳密 101 | server.login(user_id, password) 102 | #msg是Mimemulipart類,我們將他轉為sendmail函數才接受的字串 103 | text = msg.as_string() 104 | #指定寄件者跟收件者,還有剛剛加的信件內容、標題、附檔等等資訊(text) 105 | server.sendmail(user_id, mail_list , text) 106 | #斷線 107 | server.quit() 108 | 109 | #取得目表股票新聞函數 110 | ''' 111 | stock = 字串,目標股票 112 | target_page = 整數,要抓取的頁數 113 | ''' 114 | def get_yahoo_news(stock:str,target_page:int): 115 | try: 116 | #===========================獲取頁數=========================== 117 | #準備headers 118 | headers = { 119 | "user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36" 120 | } 121 | #requests指定網址,並且帶入headers 122 | data = requests.get(f"https://tw.stock.yahoo.com/q/h?s={stock}",headers=headers) 123 | #使用BeautifulSoup解析 124 | soup = BeautifulSoup(data.text) 125 | #find我們找到的共幾頁元素 126 | page = soup.find('span',{'class':'mtext'}) 127 | #建立一個空字串 128 | x = '' 129 | #迴圈處理page這個元素,符合的就加起來 130 | for i in page.text: 131 | if i.isdigit(): 132 | x+=i 133 | x = int(x) 134 | #如果目標頁數比x小,那我們的拿來帶入頁數的x就可以直接替換成目標頁數 135 | if target_page self.sma[0]: 87 | 88 | # BUY, BUY, BUY!!! (with all possible default parameters) 89 | self.log('BUY CREATE, %.2f' % self.dataclose[0]) 90 | 91 | # Keep track of the created order to avoid a 2nd order 92 | self.order = self.buy() 93 | 94 | else: 95 | 96 | if self.dataclose[0] < self.sma[0]: 97 | # SELL, SELL, SELL!!! (with all possible default parameters) 98 | self.log('SELL CREATE, %.2f' % self.dataclose[0]) 99 | 100 | # Keep track of the created order to avoid a 2nd order 101 | self.order = self.sell() 102 | if __name__ == '__main__': 103 | # Create a cerebro entity 104 | cerebro = bt.Cerebro() 105 | 106 | # Add a strategy 107 | cerebro.addstrategy(TestStrategy) 108 | # Create a Data Feed with YahooFinanceData function 109 | data = bt.feeds.YahooFinanceData( 110 | dataname='2330.TW', 111 | # 開始日期 112 | fromdate=datetime.datetime(2014, 1, 1), 113 | # 結束日期 114 | todate=datetime.datetime(2020, 12, 31), 115 | reverse=False) 116 | 117 | # 如果 YahooFinanceData 的 bug 尚未修復,使用以下 code 118 | #====================start==================== 119 | # # 下載台積電 (2330.TW) 的歷史數據 - 使用 yf.download 120 | # import yfinance as yf 121 | # df = yf.download('2330.TW', start='2014-01-01', end='2020-12-31') 122 | # # 轉換日期索引格式,避免 Backtrader 錯誤 123 | # df.index = df.index.tz_localize(None) 124 | # # # 將下載的資料轉為 Backtrader 可用格式 125 | # df = df.droplevel("Ticker", axis=1) 126 | # print(df) 127 | # data = bt.feeds.PandasData(dataname=df) 128 | #====================end==================== 129 | cerebro.adddata(data) 130 | 131 | # Set our desired cash start 132 | cerebro.broker.setcash(1000000.0) 133 | 134 | # Add a FixedSize sizer according to the stake 135 | cerebro.addsizer(bt.sizers.FixedSize, stake=1000) 136 | 137 | # Set the commission0.001425 138 | cerebro.broker.setcommission(commission=0.0015) 139 | # Print out the starting conditions 140 | print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue()) 141 | 142 | cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio') 143 | # Run over everything 144 | results = cerebro.run() 145 | # Print out the final result 146 | print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue()) 147 | #畫Kbars 148 | # cerebro.plot(style='candlestick', barup='red', bardown='green') 149 | 150 | strat = results[0] 151 | pyfoliozer = strat.analyzers.getbyname('pyfolio') 152 | returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items() 153 | 154 | # # pyfolio showtime 155 | import pyfolio as pf 156 | pf.create_full_tear_sheet( 157 | returns, 158 | positions=positions, 159 | transactions=transactions, 160 | live_start_date='2018-01-01') # This date is sample specific) 161 | 162 | 163 | # %% 164 | -------------------------------------------------------------------------------- /Trading/tech3_macd_ma.py: -------------------------------------------------------------------------------- 1 | #%% 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | import datetime # For datetime objects 6 | import os.path # To manage paths 7 | import sys # To find out the script name (in argv[0]) 8 | 9 | # Import the backtrader platform 10 | import backtrader as bt 11 | import pandas as pd 12 | import matplotlib.pyplot as plt 13 | import yfinance as yf 14 | import numpy as np 15 | import os 16 | 17 | class MACD_Sta(bt.Strategy): 18 | params = ( 19 | ('period_me1', 12), 20 | ('period_me2', 26), 21 | ('period_signal', 9), 22 | ('period_sma1', 5), 23 | ('period_sma2', 20), 24 | ('period_sma3', 60), 25 | ('stoploss',0.3), 26 | ('takeprofit',0.1), 27 | ) 28 | 29 | def __init__(self): 30 | #定義基本的東西 31 | self.order = None 32 | self.buyprice = None 33 | self.buycomm = None 34 | self.dataclose = self.datas[0].close 35 | self.datahigh = self.datas[0].high 36 | self.dataopen = self.datas[0].open 37 | #獲取快線與慢線的差值histo 38 | self.histogram= bt.ind.MACDHisto(period_me1=12,period_me2= 26,period_signal=9) 39 | #呼叫histo 40 | self.histo = self.histogram.histo 41 | #加入三個ma 42 | self.sma1 = bt.indicators.SimpleMovingAverage( 43 | self.datas[0].close, period=self.params.period_sma1) 44 | 45 | self.sma2 = bt.indicators.SimpleMovingAverage( 46 | self.datas[0].close, period=self.params.period_sma2) 47 | self.sma3 = bt.indicators.SimpleMovingAverage( 48 | self.datas[0].close, period=self.params.period_sma3) 49 | 50 | 51 | def log(self, txt, dt=None): 52 | ''' Logging function fot this strategy''' 53 | dt = dt or self.datas[0].datetime.date(0) 54 | print('%s, %s' % (dt.isoformat(), txt)) 55 | 56 | 57 | 58 | def notify_order(self, order): 59 | if order.status in [order.Submitted, order.Accepted]: 60 | return 61 | 62 | if order.status in [order.Completed]: 63 | if order.isbuy(): 64 | self.log( 65 | 'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' % 66 | (order.executed.price, 67 | order.executed.value, 68 | order.executed.comm)) 69 | 70 | self.buyprice = order.executed.price 71 | self.buycomm = order.executed.comm 72 | else: # Sell 73 | self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' % 74 | (order.executed.price, 75 | order.executed.value, 76 | order.executed.comm)) 77 | 78 | self.bar_executed = len(self) 79 | 80 | elif order.status in [order.Canceled, order.Margin, order.Rejected]: 81 | self.log('Order Canceled/Margin/Rejected') 82 | 83 | self.order = None 84 | 85 | def notify_trade(self, trade): 86 | if not trade.isclosed: 87 | return 88 | 89 | self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % 90 | (trade.pnl, trade.pnlcomm)) 91 | #策略的核心 92 | def next(self): 93 | #print出收盤價的部分 94 | # self.log('Close, %.2f' % self.dataclose[0]) 95 | #檢查是否有訂單卡住,為官方示範的範例 96 | if self.order: 97 | return 98 | #當庫存為0 99 | if self.position.size==0: 100 | #條件一,當5ma、20ma、60ma三條線齊揚 101 | buy_condition1 = self.sma1[0]>self.sma1[-1] and self.sma2[0]>self.sma2[-1] and self.sma3[0]>self.sma3[-1] 102 | #條件二,當macd的柱狀圖有負轉正 103 | buy_condition2 =self.histo[-2]<0 and self.histo[-1]<0 and self.histo[0]>=0 104 | #同時符合兩者則進場買入 105 | if buy_condition1 and buy_condition2: 106 | #買入log 107 | self.log('BUY CREATE, %.2f' % self.dataclose[0]) 108 | #買入動作 109 | self.order = self.buy() 110 | #反之有庫存則判斷是否有賣出機會 111 | else: 112 | #條件一,當前(n)小於昨日(n-1)高,但n-1高大於n-2高且n-2大於n-3 113 | sell_condition1 = self.datahigh[0] < self.datahigh[-1] and self.datahigh[-1] > self.datahigh[-2] and self.datahigh[-2]>self.datahigh[-3] 114 | #條件二,當出現紅紅綠的線形 115 | sell_condition2 = self.dataopen[0] >self.dataclose[0] and self.dataclose[-1] > self.dataopen[-1] and self.dataclose[-2] > self.dataopen[-2] 116 | #條件三,當收盤價至少比成本價高 117 | sell_condition3 = self.dataclose[0] > self.position.price*(1+self.params.takeprofit) 118 | #條件四,當收盤價至少比成本價低stoploss,視為停損 119 | sell_condition4 = self.dataclose[0] < self.position.price*(1-self.params.stoploss) 120 | #將條件1、2、3組合再一起 121 | if (sell_condition1 and sell_condition2 and sell_condition3): 122 | #賣出log,改為sell profit 123 | self.log('SELL Profit, %.2f' % self.dataclose[0]) 124 | #平倉賣出 125 | self.order = self.close() 126 | #條件4為停損特別拉出 127 | elif sell_condition4: 128 | #賣出log,改為sell loss 129 | self.log('Stop Loss CREATE, %.2f' % self.dataclose[0]) 130 | #停損賣出 131 | self.order = self.close() 132 | #準備開始設置框加 133 | if __name__ == '__main__': 134 | #列出目標股票 135 | stock_list = ['2317.TW'] 136 | #空list備用 137 | final_list = [] 138 | #迴圈目標股票 139 | for stock in stock_list: 140 | #基本的框架設置 141 | cerebro = bt.Cerebro() 142 | cerebro.addstrategy(MACD_Sta) 143 | #使用yfinance先獲取資料 144 | yf_data = yf.Ticker(stock) 145 | yf_data = yf_data.history(start='2018-01-01', end='2020-12-31') 146 | #計算期間內最高的股價,乘以1.5作為基本資金 147 | set_cash = np.max(yf_data['High'].values)*1.5*1000 148 | #pandasData讀取自備資料 149 | data = bt.feeds.PandasData(dataname=yf_data) 150 | #加入資料、設置初始資金、設置每次買入股數、設置傭金 151 | cerebro.adddata(data) 152 | cerebro.broker.setcash(set_cash) 153 | cerebro.addsizer(bt.sizers.FixedSize, stake=1000) 154 | cerebro.broker.setcommission(commission=0.0015) 155 | 156 | #設置三種分析 157 | cerebro.addanalyzer(bt.analyzers.SharpeRatio) 158 | cerebro.addanalyzer(bt.analyzers.Returns) 159 | cerebro.addanalyzer(bt.analyzers.DrawDown) 160 | #運行 161 | results = cerebro.run() 162 | #獲取三種分析的值 163 | a_return = results[0].analyzers.returns.get_analysis() 164 | drawDown = results[0].analyzers.drawdown.get_analysis() 165 | sharpe = results[0].analyzers.sharperatio.get_analysis() 166 | 167 | #將資料放入同一個list中 168 | con_data = [stock, set_cash, cerebro.broker.getvalue(), cerebro.broker.getvalue()-set_cash, a_return['rtot'], drawDown['max']['drawdown'], sharpe['sharperatio']] 169 | #append到一開始新增的list 170 | final_list.append(con_data) 171 | #plot出來 172 | figure = cerebro.plot(style='candlestick', barup='red', bardown='green',volume=False) 173 | #儲存相片 174 | figure[0][0].savefig(f'png/result_{stock}.png') 175 | #關閉相片 176 | plt.close() 177 | #產製檔案 178 | col = ['股票代號','投入金額','結束金額','報酬($)','總報酬(%)','MDD','Sharpe Ratio'] 179 | final_pd = pd.DataFrame(final_list,columns=col) 180 | final_pd.to_excel('result.xlsx') 181 | -------------------------------------------------------------------------------- /Trading/tech2_highest.py: -------------------------------------------------------------------------------- 1 | 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | import datetime 6 | from math import e 7 | import os.path 8 | import sys 9 | import pyfolio 10 | import backtrader as bt 11 | import numpy as np 12 | import warnings 13 | import pandas as pd 14 | warnings.filterwarnings("ignore") 15 | # 建立一個backtrader回測框架 16 | class Highest_high(bt.Strategy): 17 | #設置sma的參數,根據官方照此設置可進行暴力演算,得知何種參數最佳 18 | params = ( 19 | ('highest', 6), 20 | ('in_amount',4), 21 | ('stoploss', 0.1), 22 | ('takeprofit', 0.2), 23 | ) 24 | 25 | #這裡是log,當交易發生時呼叫log函數可以將交易print出來 26 | def log(self, txt, dt=None): 27 | ''' Logging function fot this strategy''' 28 | dt = dt or self.datas[0].datetime.date(0) 29 | # print('%s, %s' % (dt.isoformat(), txt)) 30 | 31 | #init定義你會用到的數據 32 | def __init__(self): 33 | #呼叫high序列備用 34 | self.datahigh = self.datas[0].high 35 | #呼叫close序列備用 36 | self.dataclose = self.datas[0].close 37 | #追蹤order、buyprice跟buycomm使用,可用可不用 38 | self.order = None 39 | self.buyprice = None 40 | self.buycomm = None 41 | #使用指標套件給的最高價判斷函數Highest 42 | self.the_highest_high = bt.ind.Highest(self.datahigh, period=self.params.highest) 43 | #notify_order當每次有訂單由next偵測出來的條件送出時,會觸發notify_order,好處是顯示出訂單執行的狀況以及偵測是否有資金不足的情況 44 | def notify_order(self, order): 45 | if order.status in [order.Submitted, order.Accepted]: 46 | #當訂單為提交狀態時則不做任何事 47 | return 48 | 49 | # 當訂單完成時,若為Buy則print出買入狀況;反之亦然 50 | if order.status in [order.Completed]: 51 | if order.isbuy(): 52 | self.log( 53 | 'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' % 54 | (order.executed.price, 55 | order.executed.value, 56 | order.executed.comm)) 57 | self.buyprice = order.executed.price 58 | self.buycomm = order.executed.comm 59 | else: 60 | self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' % 61 | (order.executed.price, 62 | order.executed.value, 63 | order.executed.comm)) 64 | 65 | # 當因策略取消或是現今不足訂單被拒絕等狀況則print出訂單取消 66 | elif order.status in [order.Canceled, order.Margin, order.Rejected]: 67 | self.log('Order Canceled/Margin/Rejected') 68 | 69 | #完成該有的提醒之後則將oder設置回None 70 | self.order = None 71 | 72 | #notify_trade交易通知,預設如果有倉在手就不做事,如果執行賣出則print出獲利 73 | def notify_trade(self, trade): 74 | if not trade.isclosed: 75 | return 76 | 77 | self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % 78 | (trade.pnl, trade.pnlcomm)) 79 | #next可以把它想像成一個內建的for loop,他把數據打包好供我們使用 80 | def next(self): 81 | # 檢查有無pending的訂單 82 | if self.order: 83 | return 84 | #self.position.size獲得目前倉位資訊,當size<指定進場次數時則允許買入 85 | if self.position.size < self.params.in_amount*1000: 86 | #當現在的高大於前面n根的最高價時準備執行買入 87 | if self.datahigh > self.the_highest_high[-1]: 88 | # 紀錄買單提交 89 | self.log('BUY CREATE, %.2f' % self.dataclose[0]) 90 | # 買進 91 | self.order = self.buy() 92 | #當庫存部位不為0但表有庫存 93 | if self.position.size !=0: 94 | #獲取庫存成本 95 | costs = self.position.price 96 | #當收盤價大於平均成本的10%停利賣出 97 | if self.dataclose[0] > costs + (costs*self.params.takeprofit): 98 | self.close() 99 | self.log('Take Profit, %.2f' % self.dataclose[0]) 100 | #當收盤價小於平均成本 101 | elif self.dataclose[0] < costs-(costs*self.params.stoploss): 102 | self.close() 103 | self.log('Stop Loss, %.2f' % self.dataclose[0]) 104 | 105 | # #回測終止時print出結果 106 | # def stop(self): 107 | # print(f'Fast MA: {self.params.fast_period} | Slow MA: {self.params.slow_period} | End Value: {self.broker.getvalue()}') 108 | 109 | if __name__ == '__main__': 110 | # 創建框架 111 | cerebro = bt.Cerebro() 112 | # # 放入策略 113 | # cerebro.addstrategy(Highest_high) 114 | # # 放入策略 115 | cerebro.optstrategy( 116 | Highest_high, 117 | highest = range(5, 9), 118 | in_amount = range(1, 5), 119 | stoploss = np.arange(0.1, 0.5, 0.1), 120 | takeprofit = np.arange(0.1, 0.5 ,0.1) 121 | ) 122 | 123 | # 使用框架的資料取得函數 124 | data = bt.feeds.YahooFinanceData( 125 | dataname='2317.TW', 126 | # 開始日期 127 | fromdate=datetime.datetime(2014, 1, 1), 128 | # 結束日期 129 | todate=datetime.datetime(2020, 12, 31), 130 | reverse=False) 131 | # 將datafeed餵入框架 132 | cerebro.adddata(data) 133 | # 設置起始金額 134 | cerebro.broker.setcash(1000000.0) 135 | #設置一次購買的股數,台股以1000股為主 136 | cerebro.addsizer(bt.sizers.SizerFix, stake=1000) 137 | # 設置傭金,稍微設置高一點作為滑價付出成本 138 | cerebro.broker.setcommission(commission=0.0015) 139 | 140 | #===================Pyfolio=================== 141 | #cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio') 142 | 143 | # 在設置完傭金、起始金額以及買入股數之後,我們加入三種分析 144 | cerebro.addanalyzer(bt.analyzers.SharpeRatio) 145 | cerebro.addanalyzer(bt.analyzers.Returns) 146 | cerebro.addanalyzer(bt.analyzers.DrawDown) 147 | results = cerebro.run(maxcpus=1) 148 | #準備list存放每一個參數及結果 149 | par1,par2,par3,par4,ret,down,sharpe_r = [],[],[],[],[],[],[] 150 | #迴圈每一個結果 151 | for strat in results: 152 | #因為結果是用list包起來(範例在下註解),所以我們要[0]取值 153 | #[] 154 | strat = strat[0] 155 | #get_analysis()獲得值 156 | a_return = strat.analyzers.returns.get_analysis() 157 | drawDown = strat.analyzers.drawdown.get_analysis() 158 | sharpe = strat.analyzers.sharperatio.get_analysis() 159 | #依序裝入資料,可用strat.params.xx獲取參數 160 | par1.append(strat.params.highest) 161 | par2.append(strat.params.in_amount) 162 | par3.append(strat.params.stoploss) 163 | par4.append(strat.params.takeprofit) 164 | #rtot代表總回報,獲取總回報 165 | ret.append(a_return['rtot']) 166 | #我們關注最大的drawdown,因此如下取值 167 | down.append(drawDown['max']['drawdown']) 168 | #獲取sharpe ratio 169 | sharpe_r.append(sharpe['sharperatio']) 170 | #組裝成dataframe 171 | result_df = pd.DataFrame() 172 | result_df['Highest'] = par1 173 | result_df['in_amount'] = par2 174 | result_df['stoploss'] = par3 175 | result_df['takeprofit'] = par4 176 | result_df['total profit'] = ret 177 | result_df['Max Drawdown'] = down 178 | result_df['Sharpe Ratio'] = sharpe_r 179 | #根據總報酬來排列 180 | result_df = result_df.sort_values(by=['total profit'],ascending=False) 181 | print(result_df) 182 | 183 | #畫Kbars 184 | # cerebro.plot(style='candlestick', barup='red', bardown='green') 185 | 186 | #===================Pyfolio=================== 187 | # strat = results[0] 188 | # pyfoliozer = strat.analyzers.getbyname('pyfolio') 189 | # returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items() 190 | 191 | # # # pyfolio showtime 192 | # import pyfolio as pf 193 | # pf.create_full_tear_sheet( 194 | # returns, 195 | # positions=positions, 196 | # transactions=transactions, 197 | # live_start_date='2018-01-01') # This date is sample specific) 198 | 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python 金融市場賺大錢聖經:寫出你的專屬指標 2 | 3 | 4 | ## 重要事記 5 | 6 | #### 快速索引 7 | 8 | - **新書開始預購!** 9 | 本書 **Python 金融市場賺大錢聖經:寫出你的專屬指標** 聚焦在入門者,此本新書介紹了更進階的量化及分析技術,手把手建構出績效數據亮眼的股票多空平衡策略,並完整帶到如何透過券商 API 實現自動交易 10 | 📖 [問 ChatGPT 也不會的 Python 量化交易聖經 - 從分析到真實交易一本全會](https://www.tenlong.com.tw/products/9786267569566#) 11 | 12 | - [📧 Google寄信已不支援低安全性模式](https://github.com/arleigh418/python-and-Taiwan-stock-market/blob/main/README.md#google寄信已不支援低安全性模式) 13 | 14 | - [📈 2-3 小節 Yahoo_price 爬蟲更新](https://github.com/arleigh418/python-and-Taiwan-stock-market/blob/main/README.md#2-3小節yahoo_price爬蟲更新) 15 | 16 | - [❌ Backtrader FileNotFoundError](https://github.com/arleigh418/python-and-Taiwan-stock-market/blob/main/README.md#backtrader-filenotfounderror) 17 | 18 | - [📢 借串宣傳線上課程 - Python 期貨程式交易課程](https://github.com/arleigh418/python-and-Taiwan-stock-market/blob/main/README.md#%E5%80%9F%E4%B8%B2%E5%AE%A3%E5%82%B3%E7%B7%9A%E4%B8%8A%E8%AA%B2%E7%A8%8B---python%E5%85%A8%E6%96%B9%E4%BD%8D%E6%9C%9F%E8%B2%A8%E8%AA%B2%E7%A8%8B---%E5%BE%9E%E5%9F%BA%E7%A4%8E%E7%88%AC%E8%9F%B2%E5%9B%9E%E6%B8%ACline%E6%8F%90%E9%86%92%E5%88%B0ai%E6%87%89%E7%94%A8) 19 | 20 | - [📊 Yahoo 股市網站更新](https://github.com/arleigh418/python-and-Taiwan-stock-market/blob/main/README.md#yahoo%E8%82%A1%E5%B8%82%E7%B6%B2%E7%AB%99%E6%9B%B4%E6%96%B0) 21 | 22 | - [📚 新增進階補充](https://github.com/arleigh418/python-and-Taiwan-stock-market/blob/main/README.md#%E6%96%B0%E5%A2%9E%E9%80%B2%E9%9A%8E%E8%A3%9C%E5%85%85) 23 | 24 |
25 |
26 | 27 | 28 | ## google寄信已不支援低安全性模式 29 | #### 最後更新2022/6/18 30 | 31 | google在五月底時不支援低安全模式,因此書中介紹的寄信方式需要修改. 32 | 基本上現在要採用google的應用程式密碼功能產生出來的密碼才可做使用 33 | 步驟如下可參考: 34 | 1. 確保您的帳戶通過兩步驟驗證 35 | 2. 通過後,一樣至安全性,您應該可以如下圖看到應用程式密碼,請您利用該功能產出密碼 (要通過兩步驟驗證 才會看到應用程式密碼這個選項) 36 | 3. 將您原先程式中的密碼換成第二步驟google所產生出來的密碼應就可寄信,其他部分無需調整 37 | 38 | 您也可以參考: https://github.com/arleigh418/python-and-Taiwan-stock-market/issues/28 39 | 40 |
41 | 42 | 43 | ## 2-3小節yahoo_price爬蟲更新 44 | #### 最後更新2025/1/24 45 | 經讀者反應,該網頁有小幅度的變更,tag變得不一樣導致爬蟲無法獲取資料。 46 | 不過變更的幅度不大,您可以先嘗試自行抓抓看新tag。 47 | 經測試以下tag目前可正常獲取資料 48 | ``` 49 | price = soup.find("span", {"data-testid": "qsp-price"}) 50 | ``` 51 | 52 | 另外網址使用這個 53 | ``` 54 | data = requests.get(f"https://finance.yahoo.com/quote/{stock}?p={stock}",headers=headers) 55 | ``` 56 | 57 |
58 | 59 | #### 最後更新2023/1/27 60 | 61 | 經讀者反應,該網頁有小幅度的變更,tag變得不一樣導致爬蟲無法獲取資料。 62 | 不過變更的幅度不大,您可以先嘗試自行抓抓看新tag。 63 | 經測試以下tag目前可正常獲取資料 64 | ``` 65 | price = soup.find("fin-streamer", {"data-test": "qsp-price"}) 66 | ``` 67 |
68 | 69 | 70 | ## backtrader FileNotFoundError 71 | #### 最後更新2022/1/3 72 | 73 | backtrader的FileNotFoundError看起來是有一段小段時間都未被官方修復,因此如果您看到此Error,請首先嘗試書中4.2章節,4-48頁的backtrader的可能問題之一。 74 | 75 |
76 | 77 | 78 | ## 借串宣傳線上課程 - Python全方位期貨課程 - 從基礎、爬蟲、回測、Line提醒到AI應用 79 | #### 最後更新2021/11/10 80 | #### masterTalk平台線上課程 - [Python全方位期貨課程 - 從基礎、爬蟲、回測、Line提醒到AI應用](https://mastertalks.tw/products/python-futures?ref=ArleighChang) 81 | 82 | 最近有幸受邀在mastertalk上開設線上課程,對於看完本書股票應用的同學,如果對期貨這個領域有興趣,歡迎參考。 83 |
84 | 85 | 我們會將書上的部分技術如何應用在期貨上做出教學,如backtrader。並且實際的應用一些深度學習、機器學習的模型用於價格、漲跌預測。 86 | (當然如果您只是一些期貨應用的小問題,您可以提出issue,我們可以來聊聊,或者我可以在另一篇進階補充中特別說明。此課程純推廣。) 87 | 88 |
89 | 90 | 91 | ## Yahoo股市網站更新 92 | #### 最後更新2021/09/27 93 | #### 新版的Yahoo新聞爬蟲請參考[yahoo_news_2.py](https://github.com/arleigh418/python-and-Taiwan-stock-market/tree/main/Trading%20Strategy_EX/Chapter2/yahoo_news_2.py) 94 | 需要特別注意,原先utility.py裡面使用的yahoo_new.py,因為已經失效,建議您將yahoo_news_2.py的函數貼utility.py後,引用yahoo_news_2來獲取新聞 95 | 96 | Yahoo股市的網站看起來經歷了一場巨大的更新。 97 | 很可惜我三四年前爬新聞到撰寫此書時都沒有什麼會影響到程式的更新,所以我認為他算是教學的穩定標的。 98 | 不過最近有一波巨大的更新,因此2.4章節(頁數2.49)開始的爬取Yahoo新聞的環節以及3.9章節(3-148)有使用到新聞的部分失效,但我還是希望您能夠看過內容,大致了解一下舊版的網站的爬蟲過程 99 |
100 | 101 | 除網站的tag變更,風格大幅改變之外,我認為在技術上影響最大的在於原本是頁數,現在變成滾動式下拉才會有新聞出來。 102 | 如果要爬取完整新聞,在技術上來說我認為難度就提升了一個檔次,變得不太適合初學者爬蟲的標的。 103 | 因為滾動式網頁通常解法就是要用Selenium瀏覽器模擬滾動,然後邊滾邊收集新聞。 104 | 我初步測試過,這個新式網頁是可以滾到底的,滾到1個月前的新聞。 105 | 如果大家對Selenium有興趣可以在issue中提出,如果人數有個大概三四個,大約一兩周我會生出一個範例(抱歉還有正職工作要做,只能用零碎時間開發)。 106 |
107 | 108 | 不過如果是較基本的應用,倒是挺容易的。 109 | 原則上較初階的設計方式是這樣,網頁若直接爬取,大約可獲得18-21篇左右的新聞。 110 | 因此在設計上初階的方法就是我們將舊的新聞爬蟲的頁數改為想要獲得頭幾篇新聞。 111 | 例如舊的傳入2代表我想要2頁新聞,新的傳入2則代表我只看最新的兩篇新聞。 112 | 這樣的設計對初學者來說是更加友善的。 113 | 如果您要正式使用,請記得之後章節的utility.py通用那一包的Yahoo新聞的函式要記得替換。 114 | 因為設計上較為倉促,有任何bug或者是您希望有任何更活潑的設計,都歡迎提出來大家一起討論研究!感謝您的體諒! 115 | 116 |
117 | 118 | 119 | ## 新增進階補充 120 | #### 最後更新2021/09/20 121 | 我會在另一個地方不定時的分享一些書中沒有說到,但我們有在使用的其他技術。 122 | 123 | 如果您閱讀完此書,具備一些基本的了解,可以來這裡看看。有任何問題歡迎提出issue或是透過信箱聯繫我。 124 | 125 | https://github.com/arleigh418/python-and-Taiwan-stock-market-Advanced 126 |
127 | 128 | 129 | 130 | ## 章節對照表 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 |
檔案名稱對照章節
Trading_Strategy_EX/Chapter2/stock_list.py2.2
Trading_Strategy_EX/Chapter2/yahoo_price.py2.3
Trading Strategy_EX/Chapter2/yahoo_news.py (因網站更新爬蟲失效)
Trading Strategy_EX/Chapter2/yahoo_news_2.py (因應新網站的新爬蟲)
2.4
Trading_Strategy_EX/Chapter2/TWSE.py2.5
Trading_Strategy_EX/Chapter3/yfinance_example.py3.1
Trading_Strategy_EX/Chapter3/pd_example.py
Trading_Strategy_EX/Chapter3/ta_example.py
Trading_Strategy_EX/Chapter3/mine_ta.py
3.2
Trading_Strategy_EX/Chapter3/generate_picture_example.py3.3
Trading_Strategy_EX/Chapter3/smtp.py3.4
Trading_Strategy_EX/Chapter3/smtp2.py
Trading_Strategy_EX/Chapter3/AES_Encryption/
3.5
Trading_Strategy_EX/Chapter3/is_open.py
Trading_Strategy_EX/Chapter3/deal_holiday.py
3.6
Trading/1_buy_follow_corp.py3.7
Trading/2_buy_with_devidend.py
Trading/2_2_buy_with_dividend_price.py
3.8
Trading/3_buy_with_price_fall.py3.9
Trading/strategy_research.py4.1
Trading/backtest_research.py4.2
Trading/tech1_ma_strategy.py4.3
Trading/tech2_highest.py4.4
Trading/tech3_macd_ma.py4.5
Trading Strategy_EX/Chapter3/holidaySchedule.csv下載下來的2021股市休市表
Trading Strategy_EX\Chapter3\AES_Encryption
Trading\AES_Encryption
帳密加解密工具包
Trading\holiday.xlsx經處理過後的判斷是否開盤用
Trading\stock_list.xlsx股票列表(請自行運行程式更新)
247 |
248 | 249 | 250 | 251 | ## 勘誤表 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 |
訂正日期對照章節頁數對照檔案錯誤原因修正內容勘誤發現
264 | 265 | 266 | 267 | ## 購買 268 | 博客來: https://www.books.com.tw/products/0010901963?loc=M_0039_001 269 | 270 | momo: https://m.momoshop.com.tw/goods.momo?i_code=9261467 271 | 272 | 天瓏: https://www.tenlong.com.tw/products/9789860776294 273 | 274 | 誠品: https://www.eslite.com/product/1001313432682066432001 275 | 276 |
277 | 278 | 279 | --------------------------------------------------------------------------------