import asyncio
import json
import os
import re
import time
import aiohttp
import requests
import pandas as pd
class Stock(object):
def __init__(self, refresh_stock_pool: bool = False):
"""
初始化
:param refresh_stock_pool: 是否刷新股票池
"""
self.headers = {
'Authority': 'stock.xueqiu.com',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Origin': 'https://xueqiu.com',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Pragma': 'no-cache',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-User': '?1',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
}
# cookie可以写到redis中
self.cookies = dict()
# 初始化cookie
self.set_cookie()
self.stock_file = "./symbol_name.csv"
if refresh_stock_pool or not os.path.exists(self.stock_file):
self.get_all_stock_to_csv()
def stp_t(self, timestamp: int) -> str:
"""
时间戳转时间
:param timestamp:
:return:
"""
# localtime /seconds
timeArray = time.localtime(timestamp / 1000)
otherStyleTime = time.strftime("%Y-%m-%d", timeArray)
return otherStyleTime
def set_cookie(self):
def cookie1():
response = requests.get('https://xueqiu.com/', headers=self.headers)
return response.cookies.get("acw_tc")
def cooke2():
ck1 = cookie1()
cookies = {
'acw_tc': ck1,
}
params = {
'md5__1038': 'QqGxcDnDyiitnD05o4+r=DRWxBWx9Al=Gn7oD',
}
response = requests.get('https://xueqiu.com/', params=params, cookies=cookies, headers=self.headers)
print(response.cookies)
return response
ck = cooke2()
self.cookies.update({'xq_a_token': ck.cookies.get("xq_a_token")})
self.cookies.update({'u': ck.cookies.get("u")})
self.cookies.update({'xq_r_token': ck.cookies.get("xq_r_token")})
print(ck.cookies)
def get_stock_detail(self, code: str):
"""
获取股票详情
:param code: SZ300911 股票代码
:return:
"""
params = {
'symbol': code,
'extend': 'detail',
}
response = requests.get('https://stock.xueqiu.com/v5/stock/quote.json', params=params, cookies=self.cookies,
headers=self.headers)
print("code", response.status_code)
if response.status_code != 200:
self.set_cookie()
response = requests.get('https://stock.xueqiu.com/v5/stock/quote.json', params=params, cookies=self.cookies,
headers=self.headers)
return response.text
async def get_theme(self, code: str) -> str:
"""
:param code: 600322.SH
:return:
"""
headers = {
'Accept': '*/*',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Origin': 'https://emweb.securities.eastmoney.com',
'Pragma': 'no-cache',
'Referer': 'https://emweb.securities.eastmoney.com/',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-site',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
}
response = requests.get(
'https://datacenter.eastmoney.com/securities/api/data/get?type=RPT_F10_CORETHEME_BOARDTYPE&sty=SECUCODE,SECURITY_CODE,SECURITY_NAME_ABBR,BOARD_CODE,BOARD_NAME,IS_PRECISE,BOARD_RANK,BOARD_TYPE&filter=(SECUCODE="%s")' % code,
headers=headers,
)
result = json.loads(response.text)
try:
res = ""
data = result.get("result").get("data")
for d in data:
res += d.get("BOARD_NAME") + ","
return res
except Exception as e:
print(e)
return ""
def get_hs_rank_percent(self, page: str, size: str, order: str, orderby: str) -> list:
"""
:param page: 页数
:param size: 个数
:param order: 排序的方式 desc asc
:param orderby: 排序的字段
:return:
"""
params = {
'page': page,
'size': size,
'order': order,
'orderby': orderby,
'order_by': orderby,
'market': 'CN',
'type': 'sh_sz',
}
response = requests.get('https://stock.xueqiu.com/v5/stock/screener/quote/list.json', params=params,
cookies=self.cookies, headers=self.headers)
if response.status_code != 200:
self.set_cookie()
response = requests.get('https://stock.xueqiu.com/v5/stock/screener/quote/list.json', params=params,
cookies=self.cookies, headers=self.headers)
all = []
try:
data = json.loads(response.text).get("data").get("list")
for d in data:
all.append(
{"name": d.get("name"), "code": d.get("symbol"), "现价": d.get("current"), "涨幅": d.get("percent"),
"市值/亿": float(d.get("market_capital")) / 10 ** 8, "成交额": d.get("amount"),
"成交量/股": d.get("volume"),
"流通股/股": d.get("float_shares"),
"成交量/流通量": d.get("volume") / d.get("float_shares") if d.get(
"float_shares") != 0 else "新股暂无流通量",
"换手率": d.get("turnover_rate"), "振幅": d.get("amplitude"),
"北向流入": d.get("north_net_inflow"),
"年初至今涨幅": d.get("current_year_percent"),
"上市至今涨幅": d.get("total_percent"),
})
except Exception as e:
print("", e)
return all
async def get_section_stock(self, symbol: str, begin: str, period: str, count: str, indicator: str):
"""
获取数据
:param symbol: SZ300813
:param begin: 1669853231808
:param period: period=day || week || month || quarter || year || 120m || 60m
:param count: -10 从开始向前的k线数量
:param indicator: 指标 kline,pe,pb,ps,pcf,market_capital,agt,ggt,balance
:return:
"""
url = "https://stock.xueqiu.com/v5/stock/chart/kline.json?symbol=%s&begin=%s&period=%s&type=before&count=%s&indicator=%s"
response = requests.get(url % (symbol, begin, period, count, indicator), cookies=self.cookies,
headers=self.headers)
if response.status_code != 200:
self.set_cookie()
response = requests.get(url % (symbol, begin, period, count, indicator), cookies=self.cookies,
headers=self.headers)
try:
data = json.loads(response.text).get("data")
col = data.get("column")
item = data.get("item")
if not item:
return
df = pd.DataFrame(item, columns=col)
return df
except Exception as e:
print("", e)
return pd.DataFrame
def get_all_stock_to_csv(self, count: int = 5500):
"""
初始化获取所有股票
:param count:获取股票数量
:return:
"""
print("正在写入股票池")
df_list = []
for i in range(int(count / 50)):
page = i + 1
params = {
'page': '%d' % page,
'size': '50',
'order': 'desc',
'order_by': 'percent',
'market': 'CN',
'type': 'sh_sz',
}
response = requests.get(
'https://stock.xueqiu.com/v5/stock/screener/quote/list.json',
params=params,
cookies=self.cookies,
headers=self.headers,
)
data = json.loads(response.text)
datas = data.get("data").get("list")
if datas:
for dt in datas:
sysbol = dt.get("symbol")
name = dt.get("name")
df_list.append([name, sysbol])
else:
break
df = pd.DataFrame(df_list)
df.to_csv(self.stock_file)
print("写入股票池结束")
async def stock_daily_strategy(self, name: str, code: str, data: pd.DataFrame, limit_up: int,
energy_multiple: float, percent: float, amplitude: float,
down: bool = False) -> dict:
"""
数据清洗 可以自定义清洗
:param name: 股票名称
:param code: 股票代码
:param data: 数据
:param limit_up: 数量最少
:param down: 是否跌停
:param energy_multiple: 量能比最少
:param percent: 涨幅限制
:param amplitude: 振幅限制 负数则小于 正数则大于 绝对值
:return:
"""
# 是否爆量
q_rate = data['volume'].max() / data['volume'].min()
# 量能变化
volume_chg = [i for i in data['volume'].values]
# 换手率变化
turnoverrate_chg = [i for i in data['turnoverrate'].values]
# >=9.6个点涨幅
if down:
zt_data = {data['timestamp'][p[0]]: p[1] for p in data['percent'].items() if p[1] <= -5}
else:
zt_data = {data['timestamp'][p[0]]: p[1] for p in data['percent'].items() if p[1] >= 5}
# 涨跌幅变化
percent_chg = [i for i in data['percent'].values]
# 振幅限制
data["amplitude"] = (data["high"] - data["low"]) / data["open"]
if amplitude > 0:
amplitud_expectations = False
percent_expectations = False
for amp in data["amplitude"].items():
# and abs(int(data["percent"][amp[0]][1])) <= amplitude * 100 / 2
if amp[1] > amplitude:
amplitud_expectations = True
if abs(int(data["percent"][amp[0]])) <= amplitude * 100 / 2:
percent_expectations = True
if not (amplitud_expectations and percent_expectations):
return
# 几个交易日的总的涨跌幅度
percent_sum = data['percent'].sum()
if percent > 0:
if percent_sum > percent:
return
if limit_up and energy_multiple:
if limit_up < 0:
if len(zt_data) == 0 and q_rate >= energy_multiple:
return {"name": name, "code": code, "涨跌幅变化": percent_chg, "总的涨跌幅": percent_sum}
else:
if len(zt_data) >= limit_up and q_rate >= energy_multiple:
return {"name": name, "code": code, "涨跌幅变化": percent_chg, "总的涨跌幅": percent_sum}
elif limit_up:
if limit_up < 0:
if len(zt_data) == 0:
return {"name": name, "code": code, "涨跌幅变化": percent_chg, "总的涨跌幅": percent_sum}
else:
if len(zt_data) >= limit_up:
return {"name": name, "code": code, "涨跌幅变化": percent_chg, "总的涨跌幅": percent_sum}
elif energy_multiple:
if q_rate >= energy_multiple:
return {"name": name, "code": code, "涨跌幅变化": percent_chg, "总的涨跌幅": percent_sum}
else:
return {"name": name, "code": code, "涨跌幅变化": percent_chg, "总的涨跌幅": percent_sum}
async def logic_name_symbol(self, name: str, symbol: str, trading_day: int, limit_up: int, energy_multiple: float,
percent: float, amplitude: float, down: bool = False):
"""
:param trading_day: 几个交易日
:param limit_up: 涨停数量最少
:param energy_multiple: 量能比最少
:param down:
:return:
"""
res_pd = await self.get_section_stock(symbol=symbol, begin=str(int(time.time() * 1000)), period='day',
count='-' + str(trading_day),
indicator='kline,pe,pb,ps,pcf,market_capital,agt,ggt,balance')
res_pd["timestamp"] = res_pd["timestamp"].map(lambda timestamp: self.stp_t(timestamp))
res = await self.stock_daily_strategy(name, symbol, res_pd, limit_up, energy_multiple, percent, amplitude, down)
if res:
# 获取核心题材
code = symbol[2:] + "." + symbol[:2]
theme = await self.get_theme(code)
if "创业板综" not in theme:
print(res)
print(theme)
async def run():
xueqiu = Stock()
read_ = pd.read_csv(xueqiu.stock_file, 'r')
tasks = []
df = pd.DataFrame(read_)
for data in df.values:
data_list = data[0].split(",")
name = data_list[1]
symbol = data_list[2]
task = asyncio.create_task(
xueqiu.logic_name_symbol(name=name, symbol=symbol, trading_day=5, limit_up=1, energy_multiple=0, percent=0,
amplitude=0, down=False))
tasks.append(task)
tasks = []
await asyncio.gather(*tasks)
if __name__ == '__main__':
asyncio.run(run())
XQ-stock
于 2024-12-09 14:14:09 首次发布