Initial commit
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
"""
|
||||
麦蕊智数API服务模块
|
||||
API文档: https://api.mairuiapi.com
|
||||
Licence: AEB5CE22-155A-4535-AE01-610920EB2751
|
||||
"""
|
||||
|
||||
import requests
|
||||
from requests.adapters import HTTPAdapter
|
||||
from urllib3.util.retry import Retry
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# API配置
|
||||
LICENCE = "5352ED2F-94E5-4E96-8B7F-B57BA75284E3"
|
||||
BASE_URL = "https://api.mairuiapi.com"
|
||||
|
||||
# 缓存配置
|
||||
_cache = {
|
||||
'realtime_all': {'data': None, 'timestamp': None, 'ttl': 60}, # 全市场实时数据缓存60秒
|
||||
}
|
||||
|
||||
# 全局连接池 Session(TCP连接复用,大幅减少连接建立开销)
|
||||
_session = None
|
||||
|
||||
def _get_session():
|
||||
"""获取全局复用的 requests.Session(带连接池和自动重试)"""
|
||||
global _session
|
||||
if _session is None:
|
||||
_session = requests.Session()
|
||||
retry_strategy = Retry(
|
||||
total=2, # 最多重试2次
|
||||
backoff_factor=0.3, # 重试间隔: 0.3s, 0.6s
|
||||
status_forcelist=[429, 500, 502, 503, 504],
|
||||
)
|
||||
adapter = HTTPAdapter(
|
||||
max_retries=retry_strategy,
|
||||
pool_connections=20, # 连接池大小
|
||||
pool_maxsize=20, # 最大连接数
|
||||
)
|
||||
_session.mount("https://", adapter)
|
||||
_session.mount("http://", adapter)
|
||||
return _session
|
||||
|
||||
|
||||
def _request(url, timeout=10):
|
||||
"""发送API请求(复用连接池)"""
|
||||
try:
|
||||
session = _get_session()
|
||||
resp = session.get(url, timeout=timeout)
|
||||
if resp.status_code == 200:
|
||||
return resp.json()
|
||||
else:
|
||||
print(f"API请求失败: {url}, status={resp.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"API请求异常: {url}, error={e}")
|
||||
return None
|
||||
|
||||
|
||||
# ========== 实时交易数据 ==========
|
||||
|
||||
def get_realtime_price(stock_code):
|
||||
"""
|
||||
获取单只股票实时交易数据(券商数据源)
|
||||
API: https://api.mairuiapi.com/hsrl/ssjy/{stock_code}/{licence}
|
||||
"""
|
||||
url = f"{BASE_URL}/hsrl/ssjy/{stock_code}/{LICENCE}"
|
||||
data = _request(url)
|
||||
|
||||
if data:
|
||||
return {
|
||||
'success': True,
|
||||
'data': {
|
||||
'code': stock_code,
|
||||
'price': float(data.get('p', 0)),
|
||||
'change': float(data.get('pc', 0)),
|
||||
'open': float(data.get('o', 0)),
|
||||
'high': float(data.get('h', 0)),
|
||||
'low': float(data.get('l', 0)),
|
||||
'volume': float(data.get('v', 0)),
|
||||
'amount': float(data.get('cje', 0)),
|
||||
'pe': float(data.get('pe', 0)) if data.get('pe') else None,
|
||||
'pb': float(data.get('sjl', 0)) if data.get('sjl') else None,
|
||||
'turnover': float(data.get('hs', 0)),
|
||||
'total_market_cap': float(data.get('sz', 0)),
|
||||
'circulating_market_cap': float(data.get('lt', 0)),
|
||||
'update_time': data.get('t', ''),
|
||||
}
|
||||
}
|
||||
return {'success': False, 'error': '获取失败'}
|
||||
|
||||
|
||||
def get_realtime_prices_batch(stock_codes):
|
||||
"""
|
||||
批量获取实时交易数据(最多20只)
|
||||
API: https://api.mairuiapi.com/hsrl/ssjy_more/{licence}?stock_codes=xxx,xxx
|
||||
"""
|
||||
if not stock_codes:
|
||||
return {}
|
||||
|
||||
# 每次最多20只
|
||||
codes_str = ','.join(stock_codes[:20])
|
||||
url = f"{BASE_URL}/hsrl/ssjy_more/{LICENCE}?stock_codes={codes_str}"
|
||||
data = _request(url)
|
||||
|
||||
results = {}
|
||||
if data and isinstance(data, list):
|
||||
for i, item in enumerate(data):
|
||||
if i < len(stock_codes):
|
||||
code = stock_codes[i]
|
||||
results[code] = {
|
||||
'code': code,
|
||||
'price': float(item.get('p', 0)),
|
||||
'change': float(item.get('pc', 0)),
|
||||
'pe': float(item.get('pe', 0)) if item.get('pe') else None,
|
||||
'pb': float(item.get('pb_ratio', 0)) if item.get('pb_ratio') else None,
|
||||
}
|
||||
return results
|
||||
|
||||
|
||||
# ========== K线数据 ==========
|
||||
|
||||
def get_kline(stock_code, period='d', days=30, adjust='f'):
|
||||
"""
|
||||
获取K线数据
|
||||
API: https://api.mairuiapi.com/hsstock/history/{code}.{market}/{period}/{adjust}/{licence}
|
||||
|
||||
参数:
|
||||
- period: 5/15/30/60/d/w/m/y (分钟/日/周/月/年)
|
||||
- adjust: n(不复权)/f(前复权)/b(后复权)
|
||||
"""
|
||||
# 确定市场
|
||||
if stock_code.startswith(('0', '3')):
|
||||
market = 'SZ'
|
||||
elif stock_code.startswith(('8', '9')):
|
||||
market = 'BJ'
|
||||
else:
|
||||
market = 'SH'
|
||||
|
||||
# 计算日期范围
|
||||
end_date = datetime.now().strftime('%Y%m%d')
|
||||
start_date = (datetime.now() - timedelta(days=days)).strftime('%Y%m%d')
|
||||
|
||||
url = f"{BASE_URL}/hsstock/history/{stock_code}.{market}/{period}/{adjust}/{LICENCE}?st={start_date}&et={end_date}"
|
||||
data = _request(url)
|
||||
|
||||
if data and isinstance(data, list):
|
||||
kline_data = []
|
||||
for item in data:
|
||||
# 处理日期格式,去掉时间部分
|
||||
date_str = item.get('t', '')
|
||||
if date_str and ' ' in date_str:
|
||||
date_str = date_str.split(' ')[0] # 只保留日期部分
|
||||
kline_data.append({
|
||||
'date': date_str,
|
||||
'open': float(item.get('o', 0)),
|
||||
'high': float(item.get('h', 0)),
|
||||
'low': float(item.get('l', 0)),
|
||||
'close': float(item.get('c', 0)),
|
||||
'volume': float(item.get('v', 0)),
|
||||
'amount': float(item.get('a', 0)),
|
||||
})
|
||||
return {'success': True, 'data': kline_data}
|
||||
|
||||
return {'success': True, 'data': []}
|
||||
|
||||
|
||||
# ========== 公司信息 ==========
|
||||
|
||||
def get_company_info(stock_code):
|
||||
"""
|
||||
获取公司简介
|
||||
API: https://api.mairuiapi.com/hscp/gsjj/{stock_code}/{licence}
|
||||
"""
|
||||
url = f"{BASE_URL}/hscp/gsjj/{stock_code}/{LICENCE}"
|
||||
data = _request(url)
|
||||
|
||||
if data:
|
||||
return {
|
||||
'success': True,
|
||||
'data': {
|
||||
'name': data.get('name', ''),
|
||||
'industry': data.get('idea', '').split(',')[0] if data.get('idea') else '',
|
||||
'list_date': data.get('ldate', ''),
|
||||
'issue_price': data.get('sprice', ''),
|
||||
'description': data.get('desc', ''),
|
||||
'business_scope': data.get('bscope', ''),
|
||||
}
|
||||
}
|
||||
return {'success': False, 'error': '获取失败'}
|
||||
|
||||
|
||||
# ========== 财务指标 ==========
|
||||
|
||||
def get_financial_indicators(stock_code):
|
||||
"""
|
||||
获取财务指标
|
||||
API: https://api.mairuiapi.com/hscp/cwzb/{stock_code}/{licence}
|
||||
"""
|
||||
url = f"{BASE_URL}/hscp/cwzb/{stock_code}/{LICENCE}"
|
||||
data = _request(url)
|
||||
|
||||
if data and isinstance(data, list) and len(data) > 0:
|
||||
latest = data[0] # 最新一期
|
||||
return {
|
||||
'success': True,
|
||||
'data': {
|
||||
'report_date': latest.get('date', ''),
|
||||
'eps': _parse_float(latest.get('tbmg')), # 摊薄每股收益
|
||||
'bps': _parse_float(latest.get('mgjz')), # 每股净资产
|
||||
'roe': _parse_float(latest.get('jzsy')), # 净资产收益率
|
||||
'gross_margin': _parse_float(latest.get('xsml')), # 销售毛利率
|
||||
'net_margin': _parse_float(latest.get('xsjl')), # 销售净利率
|
||||
'revenue_yoy': _parse_float(latest.get('zysr')), # 主营业务收入增长率
|
||||
'profit_yoy': _parse_float(latest.get('jlzz')), # 净利润增长率
|
||||
'debt_ratio': _parse_float(latest.get('zcfzl')), # 资产负债率
|
||||
'current_ratio': _parse_float(latest.get('ldbl')), # 流动比率
|
||||
}
|
||||
}
|
||||
return {'success': False, 'error': '获取失败'}
|
||||
|
||||
|
||||
def _parse_float(value):
|
||||
"""解析浮点数"""
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
return float(value)
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
# ========== 资金流向 ==========
|
||||
|
||||
def get_fund_flow(stock_code, days=3):
|
||||
"""
|
||||
获取资金流向数据
|
||||
API: https://api.mairuiapi.com/hsstock/history/transaction/{stock_code}/{licence}?lt={days}
|
||||
"""
|
||||
url = f"{BASE_URL}/hsstock/history/transaction/{stock_code}/{LICENCE}?lt={days}"
|
||||
data = _request(url)
|
||||
|
||||
if data and isinstance(data, list):
|
||||
flow_data = []
|
||||
for item in data:
|
||||
# 计算主力净流入 = 主买大单+主买特大单 - 主卖大单-主卖特大单
|
||||
main_buy = float(item.get('zmbddcje', 0)) + float(item.get('zmbtdcje', 0))
|
||||
main_sell = float(item.get('zmsddcje', 0)) + float(item.get('zmstdcje', 0))
|
||||
main_net = main_buy - main_sell
|
||||
|
||||
flow_data.append({
|
||||
'date': datetime.fromtimestamp(item.get('t', 0)).strftime('%Y-%m-%d') if item.get('t') else '',
|
||||
'main_net_inflow': main_net,
|
||||
'super_buy': float(item.get('zmbtdcje', 0)),
|
||||
'super_sell': float(item.get('zmstdcje', 0)),
|
||||
'big_buy': float(item.get('zmbddcje', 0)),
|
||||
'big_sell': float(item.get('zmsddcje', 0)),
|
||||
})
|
||||
return {'success': True, 'data': flow_data}
|
||||
|
||||
return {'success': True, 'data': []}
|
||||
|
||||
|
||||
# ========== 股票列表 ==========
|
||||
|
||||
def get_stock_list():
|
||||
"""
|
||||
获取股票列表
|
||||
API: https://api.mairuiapi.com/hslt/list/{licence}
|
||||
"""
|
||||
url = f"{BASE_URL}/hslt/list/{LICENCE}"
|
||||
data = _request(url, timeout=30)
|
||||
|
||||
if data and isinstance(data, list):
|
||||
return {
|
||||
'success': True,
|
||||
'data': [{'code': item.get('dm'), 'name': item.get('mc'), 'market': item.get('jys')} for item in data]
|
||||
}
|
||||
return {'success': False, 'error': '获取失败'}
|
||||
|
||||
|
||||
# ========== 涨停股池 ==========
|
||||
|
||||
def get_limit_up_stocks(date=None):
|
||||
"""
|
||||
获取涨停股池
|
||||
API: https://api.mairuiapi.com/hslt/ztgc/{date}/{licence}
|
||||
"""
|
||||
if date is None:
|
||||
date = datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
url = f"{BASE_URL}/hslt/ztgc/{date}/{LICENCE}"
|
||||
data = _request(url)
|
||||
|
||||
if data and isinstance(data, list):
|
||||
return {
|
||||
'success': True,
|
||||
'data': [{
|
||||
'code': item.get('dm'),
|
||||
'name': item.get('mc'),
|
||||
'price': float(item.get('p', 0)),
|
||||
'change': float(item.get('zf', 0)),
|
||||
'amount': float(item.get('cje', 0)),
|
||||
'limit_count': int(item.get('lbc', 0)),
|
||||
'first_limit_time': item.get('fbt', ''),
|
||||
'industry': item.get('hy', ''),
|
||||
} for item in data]
|
||||
}
|
||||
return {'success': True, 'data': []}
|
||||
Reference in New Issue
Block a user