first commit

This commit is contained in:
monoid 2022-05-25 03:16:39 +09:00
commit e28ff3afa4
15 changed files with 3619 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
stock/**/*
.vscode/**/*
__pycache__/**/*
dist/**/*
stock.db

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2022 monoid
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# Stock
주식 데이터 수집 및 선별하는 파이썬 코드입니다.

12
app.py Normal file
View File

@ -0,0 +1,12 @@
from distutils.log import debug
import flask
app = flask.Flask(__name__)
@app.route("/<m>")
def index(m:str):
return flask.send_from_directory("dist", m)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=12001, debug=True)

196
db.py Normal file
View File

@ -0,0 +1,196 @@
import sqlite3
import datetime
import pandas as pd
import krx
import os
import argparse
import enum
import json
import tqdm
CREATE_STOCK_STATEMENT = """
CREATE TABLE "STOCK" (
"Code" TEXT,
"Date" TEXT,
"Close" INTEGER NOT NULL,
"Diff" INTEGER NOT NULL,
"Open" INTEGER NOT NULL,
"High" INTEGER NOT NULL,
"Low" INTEGER NOT NULL,
"Volume" INTEGER NOT NULL,
PRIMARY KEY("Date","Code")
);"""
@enum.unique
class STOCK_INDEX(enum.IntEnum):
CODE = 0
DATE = 1
CLOSE = 2
DIFF = 3
OPEN = 4
HIGH = 5
LOW = 6
VOLUME = 7
CREATE_KRXCorp_STATEMENT = """
CREATE TABLE "KRXCorp" (
"Name" TEXT,
"Code" TEXT,
"Sector" TEXT,
"Product" TEXT,
"ListingDay" TEXT,
"ClosingMonth" TEXT,
"Representative" TEXT,
"Homepage" TEXT,
"AddressArea" TEXT,
"LastUpdate" TEXT,
PRIMARY KEY("Code")
);
"""
CREATE_UNIQUE_Stock_INDEX_Statement = """
CREATE UNIQUE INDEX "STOCK_INDEX" ON "STOCK" (
"Code",
"Date"
)
"""
def create_table(nday:int = 90):
"""
Create table for stock data
:param nday: determine lastest crolling day.
"""
print("initialize table...")
with sqlite3.connect("stock.db") as db:
db.execute(CREATE_STOCK_STATEMENT)
db.execute(CREATE_KRXCorp_STATEMENT)
db.execute(CREATE_UNIQUE_Stock_INDEX_Statement)
code_df: pd.DataFrame = krx.load_from_krx_server()
code_df.to_csv("krx.csv")
print("compelete to download from krx")
code_df = code_df.rename(columns={'회사명': 'name', '종목코드': 'code'})
code_df_size = len(code_df)
last_updated = (datetime.date.today() - datetime.timedelta(nday)).isoformat()
for i,row in code_df.iterrows():
code = row["code"]
print(f"{code_df_size}/{i+1} : code { code }",end="\r")
db.execute("""
INSERT INTO "KRXCorp" (Name,Code,Sector,Product,ListingDay,ClosingMonth,Representative,Homepage,AddressArea,LastUpdate) VALUES (
?,?,?,?,?,?,?,?,?,?)
""",(row["name"],row["code"],row["업종"],row["주요제품"],row["상장일"],row["결산월"],row["대표자명"],row["홈페이지"],row["지역"]
, last_updated))
print("\nComplete!")
db.commit()
def update_krx(nday:int = 90):
print("update krx...")
with sqlite3.connect("stock.db") as db:
if os.path.exists("krx.csv"):
code_df = pd.read_csv("krx.csv", index_col=0, dtype=str)
else:
code_df: pd.DataFrame = krx.load_from_krx_server()
code_df.to_csv("krx.csv")
print("compelete to download from krx")
code_df = code_df.rename(columns={'회사명': 'name', '종목코드': 'code'})
pbar = tqdm.tqdm(code_df.iterrows(), total=len(code_df))
for _,row in pbar:
code:str = row["code"]
name:str = row["name"]
pbar.set_description(f"{ code }")
q = db.execute("SELECT COUNT(*) FROM KRXCorp Where Code = ?",[code])
a = q.fetchone()[0]
lastUpdate = (datetime.date.today() - datetime.timedelta(nday)).isoformat()
if a > 0:
db.execute("""
UPDATE "KRXCorp" Set Name = ?,
Sector = ?,
Product = ?,
ListingDay = ?,
ClosingMonth = ?,
Representative = ?,
Homepage = ?,
AddressArea = ?,
WHERE Code = ?;
""",(row["name"],row["업종"],row["주요제품"],row["상장일"],row["결산월"],row["대표자명"],row["홈페이지"],row["지역"],code
))
else:
db.execute("""
INSERT INTO "KRXCorp" (Name,Code,Sector,Product,ListingDay,ClosingMonth,Representative,Homepage,AddressArea,LastUpdate) VALUES (
?,?,?,?,?,?,?,?,?,?)
""",(row["name"],row["code"],row["업종"],row["주요제품"],row["상장일"],row["결산월"],row["대표자명"],row["홈페이지"],row["지역"],lastUpdate
))
print("\nComplete!")
db.commit()
def get_data_from_krx(db,stock_code):
cursor = db.execute("""SELECT * FROM KRXCorp WHERE code = ?""", [stock_code])
return cursor.fetchone()
class KRXCorp:
__slots__ = ("Name","Code","Sector","Product","ListingDay",
"ClosingMonth","Representative","Homepage","AddressArea","LastUpdate")
def __init__(self,**kwargs):
super().__init__()
self.Name = kwargs["Name"]
self.Code = kwargs["Code"]
self.Sector = kwargs["Sector"]
self.Product = kwargs["Product"]
self.ListingDay = kwargs["ListingDay"]
self.ClosingMonth = kwargs["ClosingMonth"]
self.Representative = kwargs["Representative"]
self.Homepage = kwargs["Homepage"]
self.AddressArea = kwargs["AddressArea"]
self.LastUpdate = kwargs["LastUpdate"]
@classmethod
def from_db(cls,arr):
return cls(Name=arr[0], Code = arr[1],Sector =arr[2],Product =arr[3],
ListingDay =arr[4],ClosingMonth =arr[5],Representative= arr[6],
Homepage = arr[7],AddressArea = arr[8],LastUpdate = arr[9])
def __repr__(self):
return f"{{{self.Name}: {self.Code}}}"
def toDict(self):
return {
"Name": self.Name,
"Code": self.Code,
"Sector": self.Sector,
"Product": self.Product,
"ListingDay": self.ListingDay,
"ClosingMonth": self.ClosingMonth,
"Representative": self.Representative,
"Homepage": self.Homepage,
"AddressArea": self.AddressArea,
"LastUpdate": self.LastUpdate
}
def GetAllKRXCorp(db):
return [KRXCorp.from_db(c) for c in db.execute("""SELECT * From KRXCorp""")]
def GetAllStockCode(db):
return [c[0] for c in db.execute("""SELECT Code From KRXCorp""")]
parser = argparse.ArgumentParser(description="Stock DataBase")
parser.add_argument("--create",action="store_true",help="create table")
parser.add_argument("--update",action="store_true",help="update table")
parser.add_argument("--getAll",action="store_true",help="get data from krx")
if __name__ == "__main__":
args = parser.parse_args()
if args.create:
if os.path.exists("stock.db"):
print("db file already exists!")
else:
create_table()
if args.update:
update_krx()
if args.getAll:
with sqlite3.connect("stock.db") as db:
print(GetAllKRXCorp(db))

227
gen.py Normal file
View File

@ -0,0 +1,227 @@
import argparse
import json
import os
import sqlite3
from typing import Dict, List
from render import *
import db as database
from jinja2 import Environment, PackageLoader, select_autoescape
import pandas as pd
import tqdm
class DataStore:
def __init__(self) -> None:
self.db = sqlite3.connect("stock.db")
self.pricesCache: Dict[str,] = {}
def getAllKRXCorp(self) -> List[database.KRXCorp]:
return database.GetAllKRXCorp(self.db)
def getStockPrice(self,code,length) -> pd.DataFrame:
if code in self.pricesCache and len(self.pricesCache[code]) >= length:
return self.pricesCache[code]
else:
s = GetStockPriceFrom(self.db,code,length)
s = pd.DataFrame(s,
columns=[s for s in database.STOCK_INDEX.__members__.keys()])
s.set_index("DATE", inplace=True)
self.pricesCache[code] = s
return self.pricesCache[code]
def clearCache(self) -> None:
self.pricesCache = {}
def __del__(self) -> None:
self.db.close()
class OutputCollectorElement:
def __init__(self, name: str, description: str) -> None:
self.name = name
self.description = description
self.corpListByDate:Dict[str,database.KRXCorp] = {}
def __str__(self) -> str:
return f"OutputCollectorElement:{self.name}"
def addCorp(self, date, corp):
self.corpListByDate.setdefault(date, []).append(corp)
def toDict(self) -> Dict:
return {
"name": self.name,
"description": self.description,
"corpListByDate": {k:[d.toDict() for d in v]
for k,v in self.corpListByDate.items()}
}
class OutputCollector:
def __init__(self) -> None:
self.data: Dict[str,OutputCollectorElement] = {}
def addResult(self, key, help = ""):
"""
add output category to collect
"""
self.data[key] = OutputCollectorElement(key, help)
def collect(self, key, corp, date):
self.data[key].addCorp(date, corp)
def isVolumeNTimes(stock: pd.DataFrame, mul: float, nday:int, order=1) -> bool:
return stock.iloc[nday]['VOLUME'] > stock.iloc[nday+order]['VOLUME'] * mul
def isVolumeMulPriceGreaterThan(stock: pd.DataFrame, threshold: int, nday: int) -> bool:
return stock.iloc[nday]['VOLUME'] * stock.iloc[nday]['CLOSE'] > threshold
def isMACDCrossSignal(signal: pd.Series, macd: pd.Series, nday: int, order=1) -> bool:
return (signal.iloc[nday] < macd.iloc[nday] and
signal.iloc[nday+order] > macd.iloc[nday+order])
def isRelativeDiffLessThan(a:pd.Series,b:pd.Series, threshold: float,nday:int) -> bool:
return (a.iloc[nday] - b.iloc[nday]) / b.iloc[nday] < threshold
def isDiffGreaterThan(a:pd.Series,b:pd.Series, nday:int) -> bool:
"""a is bigger than b"""
return (a.iloc[nday] > b.iloc[nday])
def prepareCollector(collector: OutputCollector) -> None:
collector.addResult("cross 2", """\
5일선과 20일선이 서로 만나는 시점 상대 오차가 1% 이하이고
5일선과 60일선이 서로 만나는 시점을 찾습니다.
""")
collector.addResult("cross 3", """\
cross 2 조건에서 더해서 거래량이 이전 날짜보다 3 증가하고
100000 이상인 시점을 찾습니다.
""")
collector.addResult("cross 4", """\
20일선과 60일선이 서로 만나는 시점 상대 오차가 1% 이하이고
거래량이 1000000 이상인 시점을 찾습니다.
""")
collector.addResult("d20d5", """\
5일선이 20선보다 시점을 찾습니다.
""")
collector.addResult("d20d5VolumeX5", """\
d20d5의 조건에서 더해서 거래량이 이전 날짜보다 5 증가한 시점을 찾습니다.
""")
collector.addResult("DiffDistance", """\
5일선과 20일선이 서로 만나는 시점 상대 오차가 3% 이하이고
5일선과 60일선이 서로 만나고 거래량이 이전 날짜보다 3 증가한
시점을 찾습니다.
""")
collector.addResult("volume", """\
거래량이 이전 날짜보다 3 증가한 시점을 찾습니다.
""")
collector.addResult("volume5", """\
거래량과 가격의 곱이 50,000,000,000 이상인 시점을 찾습니다.
""")
collector.addResult("volumeX5", """\
거래량이 이전 날짜보다 5 증가한 시점을 찾습니다.
""")
collector.addResult("macd", """\
signal과 macd가 서로 교차한 시점을 찾습니다. signal이 아래로 떨어지고
macd가 위로 올라가는 시점을 찾습니다.
""")
def collect(data: DataStore, collector: OutputCollector, corp: database.KRXCorp
, nday: int) -> None:
stock = data.getStockPrice(corp.Code,70)
if len(stock) < 70:
return
d5 = stock["CLOSE"].loc[::-1].rolling(window=5
).mean().dropna().loc[::-1]
d20 = stock["CLOSE"].loc[::-1].rolling(window=20
).mean().dropna().loc[::-1]
d60 = stock["CLOSE"].loc[::-1].rolling(window=60
).mean().dropna().loc[::-1]
if (isRelativeDiffLessThan(d5, d20, 0.01, nday) and
isRelativeDiffLessThan(d5, d60, 0.01, nday)):
collector.collect("cross 2", corp, stock.index[nday])
if (isVolumeNTimes(stock, 3, 0) and
isVolumeMulPriceGreaterThan(stock, 100000, nday)):
collector.collect("cross 3", corp, stock.index[nday])
if (isRelativeDiffLessThan(d20, d60, 0.01, nday) and
isVolumeMulPriceGreaterThan(stock, 1000000, nday)):
collector.collect("cross 4", corp, stock.index[nday])
if (isDiffGreaterThan(d5, d20, nday)):
collector.collect("d20d5", corp, stock.index[nday])
if (isVolumeNTimes(stock, 5, nday)):
collector.collect("d20d5VolumeX5", corp, stock.index[nday])
if (isRelativeDiffLessThan(d5, d20, 0.03, nday) and
isRelativeDiffLessThan(d5, d60, 0.03, nday) and
isVolumeNTimes(stock, 3, nday)):
collector.collect("DiffDistance", corp, stock.index[nday])
if (isVolumeNTimes(stock, 3, nday)):
collector.collect("volume", corp, stock.index[nday])
if (isVolumeMulPriceGreaterThan(stock, 50000000, nday)):
collector.collect("volume5", corp, stock.index[nday])
if (isVolumeNTimes(stock, 5, nday)):
collector.collect("volumeX5", corp, stock.index[nday])
ewm12 = stock["CLOSE"].loc[::-1].ewm(span=12).mean().loc[::-1]
ewm26 = stock["CLOSE"].loc[::-1].ewm(span=26).mean().loc[::-1]
macd = (ewm12 - ewm26)
signal = macd.ewm(span=9).mean()
if (isMACDCrossSignal(macd, signal, nday)):
collector.collect("macd", corp, stock.index[nday])
parser = argparse.ArgumentParser(description="주식 검색 정보를 출력합니다.")
parser.add_argument("--format", "-f", choices=["json", "html"], default="html",
help="출력 포맷을 지정합니다. 기본값은 html입니다.")
parser.add_argument("--dir", "-d", default=".", help="출력할 폴더를 지정합니다.")
parser.add_argument("--corp", "-c", help="주식 코드를 지정합니다. 지정하지 않으면 모든 주식을 검색합니다.")
parser.add_argument("--printStdout", action="store_true", help="출력한 결과를 표준 출력으로 출력합니다.")
parser.add_argument("--version", "-v", action="version", version="%(prog)s 1.0")
parser.add_argument("--verbose", "-V", action="store_true", help="출력할 내용을 자세히 표시합니다.")
if __name__ == "__main__":
args = parser.parse_args()
dataStore = DataStore()
krx_corps = dataStore.getAllKRXCorp()
if args.corp:
krx_corps = [corp for corp in krx_corps if corp.Code == args.corp]
env = Environment(
loader=PackageLoader('render', 'templates'),
autoescape=select_autoescape(['html', 'xml'])
)
collector = OutputCollector()
prepareCollector(collector)
for corp in tqdm.tqdm(krx_corps):
for nday in range(0, 5):
collect(dataStore, collector, corp, nday)
dataStore.clearCache()
for k,v in collector.data.items():
if args.format == "json":
data = json.dumps(v.toDict(), indent=4, ensure_ascii=False)
if args.printStdout:
print(k)
print(data)
else:
with open(os.path.join(args.dir, k + ".json", encoding="UTF-8"), "w") as f:
f.write(data)
else:
template = env.get_template("Lists.html")
days = v.corpListByDate.keys()
days = list(days)
days.sort(reverse=True)
days = days[:5]
html = template.render(collected=v, title=k, days=days)
if args.printStdout:
print(html)
else:
with open(os.path.join(args.dir, k + ".html"), "w", encoding="UTF-8") as f:
f.write(html)

2500
krx.csv Normal file

File diff suppressed because it is too large Load Diff

18
krx.py Normal file
View File

@ -0,0 +1,18 @@
import pandas as pd
def load_from_krx_server():
code_df = pd.read_html('http://kind.krx.co.kr/corpgeneral/corpList.do?method=download&searchType=13', header=0)[0]
code_df.종목코드 = code_df.종목코드.map('{:06d}'.format)
return code_df
def load_stock_codes():
code_df = pd.read_csv('krx.csv',index_col=0)
code_df.종목코드 = code_df.종목코드.map('{:06d}'.format)
code_df = code_df[['회사명', '종목코드']]
code_df = code_df.rename(columns={'회사명': 'name', '종목코드': 'code'})
stock_codes = code_df["code"]
return stock_codes

54
print_json.py Normal file
View File

@ -0,0 +1,54 @@
import sqlite3
from render import *
import db as database
import json
import argparse
import time
import datetime
parser = argparse.ArgumentParser(description="print corp data to json")
parser.add_argument("--krx", action="store_true" , default=False,
help="print krx corp data")
def printKRXCorpJson(corp):
print("{", end="")
i = 0
for attr in corp.__slots__:
if i > 0:
print(",",end="")
print("\"",attr,"\":\"",corp.__getattribute__(attr),"\"",sep="",end="")
i += 1
print("}",end="")
if __name__ == "__main__":
args = parser.parse_args()
db = sqlite3.connect("stock.db")
if args.krx:
print("[")
corp_list = database.GetAllKRXCorp(db)
j = 0
for corp in corp_list:
if j > 0:
print(",")
printKRXCorpJson(corp)
j += 1
#print(json.dumps(corp_list,ensure_ascii=False,indent=2))
print("]")
else:
stock_list = GetAllStockPrice(db,60)
i = 0
print("[")
for stock in stock_list:
if i > 0:
print(",")
s = [*map(str,stock)][2:]
code = f'"{stock[0]}"'
date = str(datetime.datetime.fromisoformat(stock[1]).timestamp())
print("[",code,",",date,",".join(s),"]",sep="",end="")
i += 1
print("]")
db.close()

97
render.py Normal file
View File

@ -0,0 +1,97 @@
import re
import datetime
import sqlite3
import csv
import sqlite3
import datetime
from db import GetAllStockCode
def GetMovingAverageAt(db,code: str,day = 5,date = datetime.date.today()):
if isinstance(day,list):
listday = day
day = max(day)
else:
listday = [day]
last = date - datetime.timedelta(days=day)
l = [row for row in db.execute("SELECT Date,Close FROM STOCK WHERE Code = ? AND Date <= ? ORDER BY Date DESC LIMIT ?",
[code,last.isoformat(),day])]
return [sum(map(lambda x: x[1],l[0:ad]))/ad for ad in listday]
def GetStockPriceFrom(db,code: str,n,date = datetime.date.today()):
"""
return (code,date,close,diff,open,high,low,volume)[]
"""
last = date - datetime.timedelta(days=n)
return [row for row in db.execute(f"SELECT * FROM STOCK WHERE Code = ? AND Date <= ? ORDER BY Date DESC LIMIT ?",
[code,date.isoformat(),n])]
def GetAllStockPrice(db,n,date = datetime.date.today()):
"""
return cursor
"""
last = date - datetime.timedelta(days=n)
return db.execute(f"SELECT * FROM STOCK WHERE Date > ? ORDER BY Date DESC",
[last.isoformat()])
#lastest is front
def makeMovingAveragePoint(n,arr,reversed = False):
if len(arr) < n:
raise IndexError
if not reversed:
start = sum(arr[:n])
ret = [start/n]
for i in range(0,len(arr)-n):
nex = start-arr[i]+arr[i+n]
ret.append(nex/n)
start = nex
else:
start = sum(arr[-n:])
ret = [start/n]
for i in range(0,len(arr)-n):
nex = start-arr[-i-1]+arr[-i-1-n]
ret.append(nex/n)
start = nex
return ret
def detectTF(d5,d20)-> bool:
series = [*map(lambda x: x[0] > x[1],zip(d5,d20))]
return series[0] and (not series[1])
if __name__ == "__main__":
print("start")
db = sqlite3.connect("stock.db")
#stock_codes = GetAllStockCode(db)
#
#for stock_code in stock_codes:
# arr = GetStockPriceFrom(db,stock_code,25)
# if len(arr) < 25:
# print(f"stock_code {stock_code} : lack of data")
# continue
# #print([*map(lambda x: x[2],arr)])
# d5 = makeMovingAveragePoint(5,[*map(lambda x: x[2],arr)])
# d20 = makeMovingAveragePoint(20,[*map(lambda x: x[2],arr)])
# higher = detectTF(d5[:5],d20[:5])
# if higher:
# print(f"stock_code {stock_code} : {higher} {d5} {d20}")
#print(GetMovingAverageAt(db,"155660",day=[5,20]))
#arr = GetStockPriceFrom(db,"155660",25)
#arr.sort(key=lambda x:x[2])
#print([*map(lambda x: x[2],arr)])
#print(makeMovingAveragePoint(2,[*map(lambda x: x[2],arr)]))
#print(makeMovingAveragePoint(2,[*map(lambda x: x[2],arr)],reversed=True))
#d5 = makeMovingAveragePoint(5,[*map(lambda x: x[2],arr)])
#d20 = makeMovingAveragePoint(20,[*map(lambda x: x[2],arr)])
#print(d5)
#print(d20)
#print(detectTF(d5,d20))
#print(GetAllStockCode(db))
db.close()

BIN
requirements.txt Normal file

Binary file not shown.

9
static/index.html Normal file
View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<title></title>
</head>
<body>
<h1>Hello</h1>
</body>
</html>

91
stock_price.py Normal file
View File

@ -0,0 +1,91 @@
import pandas as pd
import bs4
import requests
import re
import multiprocessing as mp
import sqlite3
import datetime
def get_naver_finance_price(code,page=1):
#url = (f'https://finance.naver.com/item/sise_day.nhn?code={code}&page={page}')
url = 'https://finance.naver.com/item/sise_day.nhn'
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"}
# print(url)
html = requests.get(url,params={'code':code,'page':page},headers=headers)
if html.status_code != 200:
raise UserWarning(html.status_code)
return html.text
stock_h = ['날짜','종가','전일비','시가','고가','저가','거래량']
def get_data(soup,date):
nums = soup.select(".tah")
i = 0
ret=[pd.DataFrame(columns=stock_h)]
nums = [*map(lambda x:x.text.replace(',','').strip(),nums)]
while True:
m = nums[i:(i+7)]
if not m:
break
#for ISO 8601
m[0] = m[0].replace(".","-")
#date
if m[0] <= date:
return pd.concat(ret,ignore_index=True),True
ret.append(pd.DataFrame([m],columns=stock_h))
i += 7
return pd.concat(ret,ignore_index=True),False
def get_last_page(soup):
a = soup.select_one('.pgRR a')
if a is None:
index_list = soup.select('td a')
return len(index_list)
href = a.attrs['href']
p = re.compile(r"page=(\d*)")
g = p.search(href)
return g.groups()[0]
def croll_naver_page(code,page,date):
html_text = get_naver_finance_price(code,page)
soup = bs4.BeautifulSoup(html_text,'html.parser')
return get_data(soup,date)
def croll_naver_page_all(code,date) -> pd.DataFrame:
html_text = get_naver_finance_price(code)
#print(html_text)
s = bs4.BeautifulSoup(html_text,'html.parser')
last = int(get_last_page(s))
r = [(code,i) for i in range(1,last+1)]
retdata = []
for c,pagenum in r:
d,is_end = croll_naver_page(c,pagenum,date)
if is_end:
retdata.append(d)
break
retdata.append(d)
if len(retdata) == 0:
return []
return pd.concat(retdata,ignore_index=True)
#with mp.Pool(CPU_COUNT) as pl:
# dl = pl.starmap(croll_naver_page,r)
# return pd.concat(dl,ignore_index=True)
def toSqlPos(x,code):
return (code,x["날짜"],x["종가"],x["전일비"],x["시가"],x["고가"],x["저가"],x["거래량"])
if __name__ == '__main__':
db = sqlite3.connect("stock.db")
today = datetime.date.today()
krx_stock_rows = [(i,code,last_update) for i,(code,last_update) in enumerate(db.execute("""SELECT Code,LastUpdate From KRXCorp"""))]
total = len(krx_stock_rows)
for i,code,last_update in krx_stock_rows:
print(f"{total}/{i}: code {code} : {last_update}")
if last_update == today.isoformat():
continue
d = croll_naver_page_all(code,last_update)
cursor = db.cursor()
if len(d)> 0:
cursor.executemany("INSERT INTO STOCK (Code,Date,Close,Diff,Open,High,Low,Volume) VALUES (?,?,?,?,?,?,?,?)",[toSqlPos(x,code) for i,x in d.iterrows() ])
cursor.execute("""UPDATE KRXCorp Set LastUpdate = ? WHERE Code = ?""",(today.isoformat(),code))
db.commit()

59
templates/Lists.html Normal file
View File

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stock</title>
<style>
body{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(to right, #2b2b2b, #3d1844);
color: #fff;
}
.table_item:nth-child(2n){
background: #a7a7a7;
}
.table_item:nth-child(2n+1){
background: #fff;
}
.table_item:hover{
background: #8d8d8d;
}
.container{
display: grid;
grid-template-rows: 24px auto;
background: #f0f0f0;
color: black;
box-shadow: 0px 0px 5px 0px white;
text-decoration: none;
grid-template-columns: repeat({{ 5 }}, 1fr);
}
.container a:link, a:visited{
text-decoration: none;
color: black;
}
.data_header{
border-bottom: 1px solid #a7a7a7;
}
</style>
</head>
<body>
<div style="margin: auto; max-width: 750px;">
<h1>{{title}} Stock List</h1>
<section class="description">
{{collected.description}}
</section>
<section class="container">
{% for day in days|reverse %}
<div class="data_header">{{ day }}</div>
{% endfor %}
{% for day in days|reverse %}
{% set corplist = collected.corpListByDate[day] %}
<div>{% for item in corplist %}
<div class="table_item"><a href="https://stockplus.com/m/stocks/KOREA-A{{ item.Code }}">{{ item.Name }}({{item.Code}})</a></div>{% endfor %}
</div>
{% endfor %}
</section>
</div>
</body>
</html>

328
test.ipynb Normal file
View File

@ -0,0 +1,328 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import sqlite3\n",
"from typing import Dict\n",
"from render import * \n",
"import db as database\n",
"from jinja2 import Environment, PackageLoader, select_autoescape\n",
"import pandas as pd"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"import importlib"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<module 'db' from 'c:\\\\Users\\\\Monoid\\\\Desktop\\\\stock\\\\db.py'>"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"importlib.reload(database)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"db = sqlite3.connect(\"stock.db\")\n"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"krx = database.GetAllKRXCorp(db)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"krxDf = pd.DataFrame([corp.toDict() for corp in krx])"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"data = GetStockPriceFrom(db,\"155660\", 61)\n"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [],
"source": [
"s = pd.DataFrame(data, columns=[s for s in database.STOCK_INDEX.__members__.keys()])"
]
},
{
"cell_type": "code",
"execution_count": 56,
"metadata": {},
"outputs": [],
"source": [
"s.set_index(\"DATE\", inplace=True)"
]
},
{
"cell_type": "code",
"execution_count": 91,
"metadata": {},
"outputs": [],
"source": [
"stock = s"
]
},
{
"cell_type": "code",
"execution_count": 92,
"metadata": {},
"outputs": [],
"source": [
"d5 = stock[\"CLOSE\"].loc[::-1].rolling(window=5\n",
" ).mean().dropna().loc[::-1]\n",
"d20 = stock[\"CLOSE\"].loc[::-1].rolling(window=20\n",
" ).mean().dropna().loc[::-1]\n",
"d60 = stock[\"CLOSE\"].loc[::-1].rolling(window=60\n",
" ).mean().dropna().loc[::-1]"
]
},
{
"cell_type": "code",
"execution_count": 100,
"metadata": {},
"outputs": [],
"source": [
"ewm12 = stock[\"CLOSE\"].loc[::-1].ewm(span=12).mean().loc[::-1]\n",
"ewm26 = stock[\"CLOSE\"].loc[::-1].ewm(span=26).mean().loc[::-1]\n",
"macd = (ewm12 - ewm26)\n",
"signal = macd.ewm(span=9).mean()"
]
},
{
"cell_type": "code",
"execution_count": 101,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"DATE\n",
"2022-05-20 148.895069\n",
"2022-05-19 152.584580\n",
"2022-05-18 122.762721\n",
"2022-05-17 97.031260\n",
"2022-05-16 50.671176\n",
" ... \n",
"2022-02-28 7.956286\n",
"2022-02-25 1.291958\n",
"2022-02-24 -0.770309\n",
"2022-02-23 4.262821\n",
"2022-02-22 0.000000\n",
"Name: CLOSE, Length: 61, dtype: float64"
]
},
"execution_count": 101,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"macd"
]
},
{
"cell_type": "code",
"execution_count": 105,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 115,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['2022-05-16', '2022-05-15']"
]
},
"execution_count": 115,
"metadata": {},
"output_type": "execute_result"
}
],
"source": []
},
{
"cell_type": "code",
"execution_count": 142,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>a</th>\n",
" <th>b</th>\n",
" <th>c</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1</td>\n",
" <td>2</td>\n",
" <td>3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>4</td>\n",
" <td>5</td>\n",
" <td>6</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" a b c\n",
"0 1 2 3\n",
"1 4 5 6"
]
},
"execution_count": 142,
"metadata": {},
"output_type": "execute_result"
}
],
"source": []
},
{
"cell_type": "code",
"execution_count": 132,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>a</th>\n",
" <th>b</th>\n",
" <th>c</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
"Empty DataFrame\n",
"Columns: [a, b, c]\n",
"Index: []"
]
},
"execution_count": 132,
"metadata": {},
"output_type": "execute_result"
}
],
"source": []
}
],
"metadata": {
"interpreter": {
"hash": "4958a03c5ef93b3c628112f436609f44fba8a7f6eb1fb9f266a15f7204ae796a"
},
"kernelspec": {
"display_name": "Python 3.10.2 ('stock': venv)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.2"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}