Initial commit
This commit is contained in:
1
app/routes/__init__.py
Normal file
1
app/routes/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# 路由包初始化文件
|
||||
BIN
app/routes/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
app/routes/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/routes/__pycache__/calendar.cpython-311.pyc
Normal file
BIN
app/routes/__pycache__/calendar.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/routes/__pycache__/events.cpython-311.pyc
Normal file
BIN
app/routes/__pycache__/events.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/routes/__pycache__/industries.cpython-311.pyc
Normal file
BIN
app/routes/__pycache__/industries.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/routes/__pycache__/limitanalyse.cpython-311.pyc
Normal file
BIN
app/routes/__pycache__/limitanalyse.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/routes/__pycache__/stocks.cpython-311.pyc
Normal file
BIN
app/routes/__pycache__/stocks.cpython-311.pyc
Normal file
Binary file not shown.
121
app/routes/calendar.py
Normal file
121
app/routes/calendar.py
Normal file
@@ -0,0 +1,121 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
|
||||
bp = Blueprint('calendar', __name__, url_prefix='/api/v1/calendar')
|
||||
|
||||
@bp.route('/event-counts', methods=['GET'])
|
||||
def get_event_counts():
|
||||
"""获取事件数量统计"""
|
||||
try:
|
||||
year = request.args.get('year', '2027')
|
||||
month = request.args.get('month', '10')
|
||||
|
||||
# 模拟事件数量数据
|
||||
event_counts = []
|
||||
for day in range(1, 32):
|
||||
count = (day % 7) + 1 # 模拟每天1-7个事件
|
||||
event_counts.append({
|
||||
'date': f'{year}-{month.zfill(2)}-{day:02d}',
|
||||
'count': count
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': event_counts
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting event counts: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/events', methods=['GET'])
|
||||
def get_calendar_events():
|
||||
"""获取日历事件"""
|
||||
try:
|
||||
year = request.args.get('year', '2027')
|
||||
month = request.args.get('month', '10')
|
||||
event_type = request.args.get('type', 'all')
|
||||
|
||||
# 模拟日历事件数据
|
||||
events = []
|
||||
for day in range(1, 32):
|
||||
for i in range((day % 7) + 1):
|
||||
event = {
|
||||
'id': f'{year}{month.zfill(2)}{day:02d}{i}',
|
||||
'title': f'事件{day}-{i+1}',
|
||||
'date': f'{year}-{month.zfill(2)}-{day:02d}',
|
||||
'type': ['政策', '技术', '产业', '公司'][i % 4],
|
||||
'importance': ['高', '中', '低'][i % 3],
|
||||
'status': 'active'
|
||||
}
|
||||
events.append(event)
|
||||
|
||||
# 根据类型过滤
|
||||
if event_type != 'all':
|
||||
events = [e for e in events if e['type'] == event_type]
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': events
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting calendar events: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/events/<int:event_id>', methods=['GET'])
|
||||
def get_calendar_event_detail(event_id):
|
||||
"""获取日历事件详情"""
|
||||
try:
|
||||
# 模拟事件详情
|
||||
event_detail = {
|
||||
'id': event_id,
|
||||
'title': f'事件{event_id}详情',
|
||||
'description': f'这是事件{event_id}的详细描述',
|
||||
'date': '2027-10-15',
|
||||
'type': '政策',
|
||||
'importance': '高',
|
||||
'status': 'active',
|
||||
'related_stocks': [
|
||||
{'code': '000001', 'name': '股票A'},
|
||||
{'code': '000002', 'name': '股票B'}
|
||||
],
|
||||
'keywords': ['政策', '改革', '创新'],
|
||||
'files': [
|
||||
{'name': '报告.pdf', 'url': '/files/report.pdf'},
|
||||
{'name': '数据.xlsx', 'url': '/files/data.xlsx'}
|
||||
]
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': event_detail
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting calendar event detail: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
def get_event_class(count):
|
||||
"""根据事件数量获取CSS类"""
|
||||
if count == 0:
|
||||
return 'no-events'
|
||||
elif count <= 3:
|
||||
return 'few-events'
|
||||
elif count <= 6:
|
||||
return 'medium-events'
|
||||
else:
|
||||
return 'many-events'
|
||||
|
||||
def parse_json_field(field_value):
|
||||
"""解析JSON字段"""
|
||||
if isinstance(field_value, str):
|
||||
try:
|
||||
return json.loads(field_value)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return []
|
||||
elif isinstance(field_value, (list, dict)):
|
||||
return field_value
|
||||
else:
|
||||
return []
|
||||
385
app/routes/events.py
Normal file
385
app/routes/events.py
Normal file
@@ -0,0 +1,385 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from app import db
|
||||
from app.models import Event, RelatedStock, RelatedConcepts, HistoricalEvent, EventTransmissionNode, EventTransmissionEdge, EventSankeyFlow
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
bp = Blueprint('events', __name__, url_prefix='/api/events')
|
||||
|
||||
|
||||
|
||||
@bp.route('/<int:event_id>', methods=['GET'])
|
||||
def get_event_detail(event_id):
|
||||
"""获取事件详情"""
|
||||
try:
|
||||
event = Event.query.get(event_id)
|
||||
if not event:
|
||||
return jsonify({'success': False, 'error': '事件不存在'}), 404
|
||||
|
||||
# 获取相关股票
|
||||
related_stocks = RelatedStock.query.filter_by(event_id=event_id).all()
|
||||
stocks_data = []
|
||||
for stock in related_stocks:
|
||||
stocks_data.append({
|
||||
'id': stock.id,
|
||||
'stock_code': stock.stock_code,
|
||||
'stock_name': stock.stock_name,
|
||||
'sector': stock.sector,
|
||||
'relation_desc': stock.relation_desc,
|
||||
'correlation': stock.correlation,
|
||||
'momentum': stock.momentum,
|
||||
'created_at': stock.created_at.isoformat() if stock.created_at else None
|
||||
})
|
||||
|
||||
# 获取相关概念
|
||||
related_concepts = RelatedConcepts.query.filter_by(event_id=event_id).all()
|
||||
concepts_data = []
|
||||
for concept in related_concepts:
|
||||
concepts_data.append({
|
||||
'id': concept.id,
|
||||
'concept_code': concept.concept_code,
|
||||
'concept': concept.concept,
|
||||
'reason': concept.reason,
|
||||
'image_paths': concept.image_paths_list,
|
||||
'created_at': concept.created_at.isoformat() if concept.created_at else None
|
||||
})
|
||||
|
||||
event_data = {
|
||||
'id': event.id,
|
||||
'title': event.title,
|
||||
'description': event.description,
|
||||
'event_type': event.event_type,
|
||||
'status': event.status,
|
||||
'start_time': event.start_time.isoformat() if event.start_time else None,
|
||||
'end_time': event.end_time.isoformat() if event.end_time else None,
|
||||
'created_at': event.created_at.isoformat() if event.created_at else None,
|
||||
'updated_at': event.updated_at.isoformat() if event.updated_at else None,
|
||||
'hot_score': event.hot_score,
|
||||
'view_count': event.view_count,
|
||||
'trending_score': event.trending_score,
|
||||
'post_count': event.post_count,
|
||||
'follower_count': event.follower_count,
|
||||
'related_industries': event.related_industries,
|
||||
'keywords': event.keywords_list,
|
||||
'files': event.files,
|
||||
'importance': event.importance,
|
||||
'related_avg_chg': event.related_avg_chg,
|
||||
'related_max_chg': event.related_max_chg,
|
||||
'related_week_chg': event.related_week_chg,
|
||||
'invest_score': event.invest_score,
|
||||
'expectation_surprise_score': event.expectation_surprise_score,
|
||||
'related_stocks': stocks_data,
|
||||
'related_concepts': concepts_data
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': event_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting event detail: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/<int:event_id>/stocks', methods=['GET'])
|
||||
def get_related_stocks(event_id):
|
||||
"""获取事件相关股票"""
|
||||
try:
|
||||
stocks = RelatedStock.query.filter_by(event_id=event_id).all()
|
||||
stocks_data = []
|
||||
for stock in stocks:
|
||||
stocks_data.append({
|
||||
'id': stock.id,
|
||||
'stock_code': stock.stock_code,
|
||||
'stock_name': stock.stock_name,
|
||||
'sector': stock.sector,
|
||||
'relation_desc': stock.relation_desc,
|
||||
'correlation': stock.correlation,
|
||||
'momentum': stock.momentum,
|
||||
'created_at': stock.created_at.isoformat() if stock.created_at else None
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': stocks_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting related stocks: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/<int:event_id>/stocks', methods=['POST'])
|
||||
def add_related_stock(event_id):
|
||||
"""添加相关股票"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({'success': False, 'error': '请提供数据'}), 400
|
||||
|
||||
# 检查事件是否存在
|
||||
event = Event.query.get(event_id)
|
||||
if not event:
|
||||
return jsonify({'success': False, 'error': '事件不存在'}), 404
|
||||
|
||||
# 创建新的相关股票记录
|
||||
new_stock = RelatedStock(
|
||||
event_id=event_id,
|
||||
stock_code=data['stock_code'],
|
||||
stock_name=data.get('stock_name', ''),
|
||||
sector=data.get('sector', ''),
|
||||
relation_desc=data['relation_desc'],
|
||||
correlation=data.get('correlation', 0.5),
|
||||
momentum=data.get('momentum', '')
|
||||
)
|
||||
|
||||
db.session.add(new_stock)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '相关股票添加成功',
|
||||
'data': {
|
||||
'id': new_stock.id,
|
||||
'stock_code': new_stock.stock_code,
|
||||
'stock_name': new_stock.stock_name,
|
||||
'sector': new_stock.sector,
|
||||
'relation_desc': new_stock.relation_desc,
|
||||
'correlation': new_stock.correlation,
|
||||
'momentum': new_stock.momentum
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"Error adding related stock: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/stocks/<int:stock_id>', methods=['DELETE'])
|
||||
def delete_related_stock(stock_id):
|
||||
"""删除相关股票"""
|
||||
try:
|
||||
stock = RelatedStock.query.get(stock_id)
|
||||
if not stock:
|
||||
return jsonify({'success': False, 'error': '相关股票不存在'}), 404
|
||||
|
||||
db.session.delete(stock)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '相关股票删除成功'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"Error deleting related stock: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/<int:event_id>/concepts', methods=['GET'])
|
||||
def get_related_concepts(event_id):
|
||||
"""获取事件相关概念"""
|
||||
try:
|
||||
concepts = RelatedConcepts.query.filter_by(event_id=event_id).all()
|
||||
concepts_data = []
|
||||
for concept in concepts:
|
||||
concepts_data.append({
|
||||
'id': concept.id,
|
||||
'concept_code': concept.concept_code,
|
||||
'concept': concept.concept,
|
||||
'reason': concept.reason,
|
||||
'image_paths': concept.image_paths_list,
|
||||
'created_at': concept.created_at.isoformat() if concept.created_at else None
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': concepts_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting related concepts: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/<int:event_id>/historical', methods=['GET'])
|
||||
def get_historical_events(event_id):
|
||||
"""获取历史事件"""
|
||||
try:
|
||||
historical_events = HistoricalEvent.query.filter_by(event_id=event_id).all()
|
||||
events_data = []
|
||||
for event in historical_events:
|
||||
events_data.append({
|
||||
'id': event.id,
|
||||
'title': event.title,
|
||||
'content': event.content,
|
||||
'event_date': event.event_date.isoformat() if event.event_date else None,
|
||||
'relevance': event.relevance,
|
||||
'importance': event.importance,
|
||||
'related_stock': event.related_stock,
|
||||
'created_at': event.created_at.isoformat() if event.created_at else None
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': events_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting historical events: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/<int:event_id>/expectation-score', methods=['GET'])
|
||||
def get_expectation_score(event_id):
|
||||
"""获取超预期得分"""
|
||||
try:
|
||||
event = Event.query.get(event_id)
|
||||
if not event:
|
||||
return jsonify({'success': False, 'error': '事件不存在'}), 404
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'invest_score': event.invest_score,
|
||||
'expectation_surprise_score': event.expectation_surprise_score
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting expectation score: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/<int:event_id>/follow', methods=['POST'])
|
||||
def toggle_event_follow(event_id):
|
||||
"""关注/取消关注事件"""
|
||||
try:
|
||||
# 这里需要用户认证,暂时返回成功
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '关注状态更新成功'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error toggling event follow: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/<int:event_id>/transmission', methods=['GET'])
|
||||
def get_transmission_chain(event_id):
|
||||
"""获取事件传导链"""
|
||||
try:
|
||||
# 获取传导节点
|
||||
nodes = EventTransmissionNode.query.filter_by(event_id=event_id).all()
|
||||
nodes_data = []
|
||||
for node in nodes:
|
||||
nodes_data.append({
|
||||
'id': node.id,
|
||||
'node_type': node.node_type,
|
||||
'node_name': node.node_name,
|
||||
'node_description': node.node_description,
|
||||
'importance_score': node.importance_score,
|
||||
'stock_code': node.stock_code,
|
||||
'is_main_event': node.is_main_event
|
||||
})
|
||||
|
||||
# 获取传导边
|
||||
edges = EventTransmissionEdge.query.filter_by(event_id=event_id).all()
|
||||
edges_data = []
|
||||
for edge in edges:
|
||||
edges_data.append({
|
||||
'id': edge.id,
|
||||
'from_node_id': edge.from_node_id,
|
||||
'to_node_id': edge.to_node_id,
|
||||
'transmission_type': edge.transmission_type,
|
||||
'transmission_mechanism': edge.transmission_mechanism,
|
||||
'direction': edge.direction,
|
||||
'strength': edge.strength,
|
||||
'impact': edge.impact,
|
||||
'is_circular': edge.is_circular
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'nodes': nodes_data,
|
||||
'edges': edges_data
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting transmission chain: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/<int:event_id>/sankey-data')
|
||||
def get_event_sankey_data(event_id):
|
||||
"""获取事件桑基图数据"""
|
||||
try:
|
||||
flows = EventSankeyFlow.query.filter_by(event_id=event_id).all()
|
||||
flows_data = []
|
||||
for flow in flows:
|
||||
flows_data.append({
|
||||
'id': flow.id,
|
||||
'source_node': flow.source_node,
|
||||
'source_type': flow.source_type,
|
||||
'source_level': flow.source_level,
|
||||
'target_node': flow.target_node,
|
||||
'target_type': flow.target_type,
|
||||
'target_level': flow.target_level,
|
||||
'flow_value': float(flow.flow_value),
|
||||
'flow_ratio': float(flow.flow_ratio),
|
||||
'transmission_path': flow.transmission_path,
|
||||
'impact_description': flow.impact_description,
|
||||
'evidence_strength': flow.evidence_strength
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': flows_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting sankey data: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/<int:event_id>/chain-analysis')
|
||||
def get_event_chain_analysis(event_id):
|
||||
"""获取事件链分析"""
|
||||
try:
|
||||
# 这里可以添加更复杂的链分析逻辑
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'event_id': event_id,
|
||||
'analysis': '链分析数据'
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting chain analysis: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/<int:event_id>/chain-node/<int:node_id>', methods=['GET'])
|
||||
def get_chain_node_detail(event_id, node_id):
|
||||
"""获取链节点详情"""
|
||||
try:
|
||||
node = EventTransmissionNode.query.filter_by(
|
||||
event_id=event_id,
|
||||
id=node_id
|
||||
).first()
|
||||
|
||||
if not node:
|
||||
return jsonify({'success': False, 'error': '节点不存在'}), 404
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'id': node.id,
|
||||
'node_type': node.node_type,
|
||||
'node_name': node.node_name,
|
||||
'node_description': node.node_description,
|
||||
'importance_score': node.importance_score,
|
||||
'stock_code': node.stock_code,
|
||||
'is_main_event': node.is_main_event
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting chain node detail: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
511
app/routes/industries.py
Normal file
511
app/routes/industries.py
Normal file
@@ -0,0 +1,511 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
import json
|
||||
|
||||
bp = Blueprint('industries', __name__, url_prefix='/api')
|
||||
|
||||
@bp.route('/classifications', methods=['GET'])
|
||||
def get_classifications():
|
||||
"""获取行业分类"""
|
||||
try:
|
||||
# 模拟行业分类数据
|
||||
classifications = [
|
||||
{
|
||||
'id': 1,
|
||||
'name': '申万一级行业',
|
||||
'description': '申万一级行业分类标准',
|
||||
'levels': [
|
||||
{'id': 1, 'name': '农林牧渔'},
|
||||
{'id': 2, 'name': '采掘'},
|
||||
{'id': 3, 'name': '化工'},
|
||||
{'id': 4, 'name': '钢铁'},
|
||||
{'id': 5, 'name': '有色金属'},
|
||||
{'id': 6, 'name': '建筑材料'},
|
||||
{'id': 7, 'name': '建筑装饰'},
|
||||
{'id': 8, 'name': '电气设备'},
|
||||
{'id': 9, 'name': '国防军工'},
|
||||
{'id': 10, 'name': '汽车'},
|
||||
{'id': 11, 'name': '家用电器'},
|
||||
{'id': 12, 'name': '纺织服装'},
|
||||
{'id': 13, 'name': '轻工制造'},
|
||||
{'id': 14, 'name': '医药生物'},
|
||||
{'id': 15, 'name': '公用事业'},
|
||||
{'id': 16, 'name': '交通运输'},
|
||||
{'id': 17, 'name': '房地产'},
|
||||
{'id': 18, 'name': '商业贸易'},
|
||||
{'id': 19, 'name': '休闲服务'},
|
||||
{'id': 20, 'name': '银行'},
|
||||
{'id': 21, 'name': '非银金融'},
|
||||
{'id': 22, 'name': '综合'},
|
||||
{'id': 23, 'name': '计算机'},
|
||||
{'id': 24, 'name': '传媒'},
|
||||
{'id': 25, 'name': '通信'},
|
||||
{'id': 26, 'name': '电子'},
|
||||
{'id': 27, 'name': '机械设备'},
|
||||
{'id': 28, 'name': '食品饮料'}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': classifications
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting classifications: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/levels', methods=['GET'])
|
||||
def get_industry_levels():
|
||||
"""获取行业层级"""
|
||||
try:
|
||||
classification_id = request.args.get('classification_id', '1')
|
||||
|
||||
# 模拟行业层级数据
|
||||
levels = [
|
||||
{
|
||||
'id': 1,
|
||||
'name': '农林牧渔',
|
||||
'code': '801010',
|
||||
'description': '农业、林业、畜牧业、渔业',
|
||||
'stock_count': 45,
|
||||
'avg_change': 1.2,
|
||||
'total_market_cap': 500000000000,
|
||||
'sub_industries': [
|
||||
{'id': 101, 'name': '种植业', 'stock_count': 20},
|
||||
{'id': 102, 'name': '林业', 'stock_count': 8},
|
||||
{'id': 103, 'name': '畜牧业', 'stock_count': 12},
|
||||
{'id': 104, 'name': '渔业', 'stock_count': 5}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'name': '采掘',
|
||||
'code': '801020',
|
||||
'description': '煤炭、石油、天然气、有色金属矿采选',
|
||||
'stock_count': 38,
|
||||
'avg_change': 0.8,
|
||||
'total_market_cap': 800000000000,
|
||||
'sub_industries': [
|
||||
{'id': 201, 'name': '煤炭开采', 'stock_count': 15},
|
||||
{'id': 202, 'name': '石油开采', 'stock_count': 8},
|
||||
{'id': 203, 'name': '有色金属矿采选', 'stock_count': 15}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 3,
|
||||
'name': '化工',
|
||||
'code': '801030',
|
||||
'description': '化学原料、化学制品、化学纤维',
|
||||
'stock_count': 156,
|
||||
'avg_change': 1.5,
|
||||
'total_market_cap': 1200000000000,
|
||||
'sub_industries': [
|
||||
{'id': 301, 'name': '化学原料', 'stock_count': 45},
|
||||
{'id': 302, 'name': '化学制品', 'stock_count': 78},
|
||||
{'id': 303, 'name': '化学纤维', 'stock_count': 33}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 4,
|
||||
'name': '钢铁',
|
||||
'code': '801040',
|
||||
'description': '钢铁冶炼、钢铁制品',
|
||||
'stock_count': 32,
|
||||
'avg_change': 0.6,
|
||||
'total_market_cap': 600000000000,
|
||||
'sub_industries': [
|
||||
{'id': 401, 'name': '钢铁冶炼', 'stock_count': 18},
|
||||
{'id': 402, 'name': '钢铁制品', 'stock_count': 14}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 5,
|
||||
'name': '有色金属',
|
||||
'code': '801050',
|
||||
'description': '有色金属冶炼、有色金属制品',
|
||||
'stock_count': 67,
|
||||
'avg_change': 1.8,
|
||||
'total_market_cap': 900000000000,
|
||||
'sub_industries': [
|
||||
{'id': 501, 'name': '有色金属冶炼', 'stock_count': 35},
|
||||
{'id': 502, 'name': '有色金属制品', 'stock_count': 32}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 6,
|
||||
'name': '建筑材料',
|
||||
'code': '801060',
|
||||
'description': '水泥、玻璃、陶瓷、其他建材',
|
||||
'stock_count': 89,
|
||||
'avg_change': 1.1,
|
||||
'total_market_cap': 700000000000,
|
||||
'sub_industries': [
|
||||
{'id': 601, 'name': '水泥', 'stock_count': 25},
|
||||
{'id': 602, 'name': '玻璃', 'stock_count': 18},
|
||||
{'id': 603, 'name': '陶瓷', 'stock_count': 12},
|
||||
{'id': 604, 'name': '其他建材', 'stock_count': 34}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 7,
|
||||
'name': '建筑装饰',
|
||||
'code': '801070',
|
||||
'description': '房屋建设、装修装饰、园林工程',
|
||||
'stock_count': 45,
|
||||
'avg_change': 0.9,
|
||||
'total_market_cap': 400000000000,
|
||||
'sub_industries': [
|
||||
{'id': 701, 'name': '房屋建设', 'stock_count': 15},
|
||||
{'id': 702, 'name': '装修装饰', 'stock_count': 20},
|
||||
{'id': 703, 'name': '园林工程', 'stock_count': 10}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 8,
|
||||
'name': '电气设备',
|
||||
'code': '801080',
|
||||
'description': '电机、电气自动化设备、电源设备',
|
||||
'stock_count': 134,
|
||||
'avg_change': 2.1,
|
||||
'total_market_cap': 1500000000000,
|
||||
'sub_industries': [
|
||||
{'id': 801, 'name': '电机', 'stock_count': 25},
|
||||
{'id': 802, 'name': '电气自动化设备', 'stock_count': 45},
|
||||
{'id': 803, 'name': '电源设备', 'stock_count': 64}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 9,
|
||||
'name': '国防军工',
|
||||
'code': '801090',
|
||||
'description': '航天装备、航空装备、地面兵装',
|
||||
'stock_count': 28,
|
||||
'avg_change': 1.6,
|
||||
'total_market_cap': 300000000000,
|
||||
'sub_industries': [
|
||||
{'id': 901, 'name': '航天装备', 'stock_count': 8},
|
||||
{'id': 902, 'name': '航空装备', 'stock_count': 12},
|
||||
{'id': 903, 'name': '地面兵装', 'stock_count': 8}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 10,
|
||||
'name': '汽车',
|
||||
'code': '801100',
|
||||
'description': '汽车整车、汽车零部件',
|
||||
'stock_count': 78,
|
||||
'avg_change': 1.3,
|
||||
'total_market_cap': 1100000000000,
|
||||
'sub_industries': [
|
||||
{'id': 1001, 'name': '汽车整车', 'stock_count': 25},
|
||||
{'id': 1002, 'name': '汽车零部件', 'stock_count': 53}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 11,
|
||||
'name': '家用电器',
|
||||
'code': '801110',
|
||||
'description': '白色家电、小家电、家电零部件',
|
||||
'stock_count': 56,
|
||||
'avg_change': 1.0,
|
||||
'total_market_cap': 800000000000,
|
||||
'sub_industries': [
|
||||
{'id': 1101, 'name': '白色家电', 'stock_count': 20},
|
||||
{'id': 1102, 'name': '小家电', 'stock_count': 18},
|
||||
{'id': 1103, 'name': '家电零部件', 'stock_count': 18}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 12,
|
||||
'name': '纺织服装',
|
||||
'code': '801120',
|
||||
'description': '纺织制造、服装家纺',
|
||||
'stock_count': 67,
|
||||
'avg_change': 0.7,
|
||||
'total_market_cap': 500000000000,
|
||||
'sub_industries': [
|
||||
{'id': 1201, 'name': '纺织制造', 'stock_count': 35},
|
||||
{'id': 1202, 'name': '服装家纺', 'stock_count': 32}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 13,
|
||||
'name': '轻工制造',
|
||||
'code': '801130',
|
||||
'description': '造纸、包装印刷、家用轻工',
|
||||
'stock_count': 89,
|
||||
'avg_change': 0.9,
|
||||
'total_market_cap': 600000000000,
|
||||
'sub_industries': [
|
||||
{'id': 1301, 'name': '造纸', 'stock_count': 25},
|
||||
{'id': 1302, 'name': '包装印刷', 'stock_count': 30},
|
||||
{'id': 1303, 'name': '家用轻工', 'stock_count': 34}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 14,
|
||||
'name': '医药生物',
|
||||
'code': '801140',
|
||||
'description': '化学制药、中药、生物制品、医疗器械',
|
||||
'stock_count': 234,
|
||||
'avg_change': 1.9,
|
||||
'total_market_cap': 2500000000000,
|
||||
'sub_industries': [
|
||||
{'id': 1401, 'name': '化学制药', 'stock_count': 78},
|
||||
{'id': 1402, 'name': '中药', 'stock_count': 45},
|
||||
{'id': 1403, 'name': '生物制品', 'stock_count': 56},
|
||||
{'id': 1404, 'name': '医疗器械', 'stock_count': 55}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 15,
|
||||
'name': '公用事业',
|
||||
'code': '801150',
|
||||
'description': '电力、燃气、水务',
|
||||
'stock_count': 78,
|
||||
'avg_change': 0.5,
|
||||
'total_market_cap': 900000000000,
|
||||
'sub_industries': [
|
||||
{'id': 1501, 'name': '电力', 'stock_count': 45},
|
||||
{'id': 1502, 'name': '燃气', 'stock_count': 18},
|
||||
{'id': 1503, 'name': '水务', 'stock_count': 15}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 16,
|
||||
'name': '交通运输',
|
||||
'code': '801160',
|
||||
'description': '港口、公路、铁路、航空',
|
||||
'stock_count': 67,
|
||||
'avg_change': 0.8,
|
||||
'total_market_cap': 800000000000,
|
||||
'sub_industries': [
|
||||
{'id': 1601, 'name': '港口', 'stock_count': 15},
|
||||
{'id': 1602, 'name': '公路', 'stock_count': 20},
|
||||
{'id': 1603, 'name': '铁路', 'stock_count': 12},
|
||||
{'id': 1604, 'name': '航空', 'stock_count': 20}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 17,
|
||||
'name': '房地产',
|
||||
'code': '801170',
|
||||
'description': '房地产开发、房地产服务',
|
||||
'stock_count': 89,
|
||||
'avg_change': 0.6,
|
||||
'total_market_cap': 1200000000000,
|
||||
'sub_industries': [
|
||||
{'id': 1701, 'name': '房地产开发', 'stock_count': 65},
|
||||
{'id': 1702, 'name': '房地产服务', 'stock_count': 24}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 18,
|
||||
'name': '商业贸易',
|
||||
'code': '801180',
|
||||
'description': '贸易、零售',
|
||||
'stock_count': 78,
|
||||
'avg_change': 0.7,
|
||||
'total_market_cap': 600000000000,
|
||||
'sub_industries': [
|
||||
{'id': 1801, 'name': '贸易', 'stock_count': 35},
|
||||
{'id': 1802, 'name': '零售', 'stock_count': 43}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 19,
|
||||
'name': '休闲服务',
|
||||
'code': '801190',
|
||||
'description': '景点、酒店、旅游综合',
|
||||
'stock_count': 34,
|
||||
'avg_change': 1.2,
|
||||
'total_market_cap': 300000000000,
|
||||
'sub_industries': [
|
||||
{'id': 1901, 'name': '景点', 'stock_count': 12},
|
||||
{'id': 1902, 'name': '酒店', 'stock_count': 15},
|
||||
{'id': 1903, 'name': '旅游综合', 'stock_count': 7}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 20,
|
||||
'name': '银行',
|
||||
'code': '801200',
|
||||
'description': '银行',
|
||||
'stock_count': 28,
|
||||
'avg_change': 0.4,
|
||||
'total_market_cap': 8000000000000,
|
||||
'sub_industries': [
|
||||
{'id': 2001, 'name': '银行', 'stock_count': 28}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 21,
|
||||
'name': '非银金融',
|
||||
'code': '801210',
|
||||
'description': '保险、证券、多元金融',
|
||||
'stock_count': 45,
|
||||
'avg_change': 0.8,
|
||||
'total_market_cap': 2000000000000,
|
||||
'sub_industries': [
|
||||
{'id': 2101, 'name': '保险', 'stock_count': 8},
|
||||
{'id': 2102, 'name': '证券', 'stock_count': 25},
|
||||
{'id': 2103, 'name': '多元金融', 'stock_count': 12}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 22,
|
||||
'name': '综合',
|
||||
'code': '801220',
|
||||
'description': '综合',
|
||||
'stock_count': 23,
|
||||
'avg_change': 0.6,
|
||||
'total_market_cap': 200000000000,
|
||||
'sub_industries': [
|
||||
{'id': 2201, 'name': '综合', 'stock_count': 23}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 23,
|
||||
'name': '计算机',
|
||||
'code': '801230',
|
||||
'description': '计算机设备、计算机应用',
|
||||
'stock_count': 156,
|
||||
'avg_change': 2.3,
|
||||
'total_market_cap': 1800000000000,
|
||||
'sub_industries': [
|
||||
{'id': 2301, 'name': '计算机设备', 'stock_count': 45},
|
||||
{'id': 2302, 'name': '计算机应用', 'stock_count': 111}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 24,
|
||||
'name': '传媒',
|
||||
'code': '801240',
|
||||
'description': '文化传媒、营销传播',
|
||||
'stock_count': 78,
|
||||
'avg_change': 1.4,
|
||||
'total_market_cap': 700000000000,
|
||||
'sub_industries': [
|
||||
{'id': 2401, 'name': '文化传媒', 'stock_count': 45},
|
||||
{'id': 2402, 'name': '营销传播', 'stock_count': 33}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 25,
|
||||
'name': '通信',
|
||||
'code': '801250',
|
||||
'description': '通信设备、通信运营',
|
||||
'stock_count': 45,
|
||||
'avg_change': 1.7,
|
||||
'total_market_cap': 600000000000,
|
||||
'sub_industries': [
|
||||
{'id': 2501, 'name': '通信设备', 'stock_count': 30},
|
||||
{'id': 2502, 'name': '通信运营', 'stock_count': 15}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 26,
|
||||
'name': '电子',
|
||||
'code': '801260',
|
||||
'description': '半导体、电子制造、光学光电子',
|
||||
'stock_count': 178,
|
||||
'avg_change': 2.0,
|
||||
'total_market_cap': 2000000000000,
|
||||
'sub_industries': [
|
||||
{'id': 2601, 'name': '半导体', 'stock_count': 45},
|
||||
{'id': 2602, 'name': '电子制造', 'stock_count': 78},
|
||||
{'id': 2603, 'name': '光学光电子', 'stock_count': 55}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 27,
|
||||
'name': '机械设备',
|
||||
'code': '801270',
|
||||
'description': '通用机械、专用设备、仪器仪表',
|
||||
'stock_count': 234,
|
||||
'avg_change': 1.1,
|
||||
'total_market_cap': 1500000000000,
|
||||
'sub_industries': [
|
||||
{'id': 2701, 'name': '通用机械', 'stock_count': 89},
|
||||
{'id': 2702, 'name': '专用设备', 'stock_count': 98},
|
||||
{'id': 2703, 'name': '仪器仪表', 'stock_count': 47}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 28,
|
||||
'name': '食品饮料',
|
||||
'code': '801280',
|
||||
'description': '食品加工、饮料制造',
|
||||
'stock_count': 67,
|
||||
'avg_change': 1.3,
|
||||
'total_market_cap': 1000000000000,
|
||||
'sub_industries': [
|
||||
{'id': 2801, 'name': '食品加工', 'stock_count': 35},
|
||||
{'id': 2802, 'name': '饮料制造', 'stock_count': 32}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': levels
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting industry levels: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/info', methods=['GET'])
|
||||
def get_industry_info():
|
||||
"""获取行业信息"""
|
||||
try:
|
||||
industry_id = request.args.get('industry_id')
|
||||
|
||||
if not industry_id:
|
||||
return jsonify({'success': False, 'error': '请提供行业ID'}), 400
|
||||
|
||||
# 模拟行业信息
|
||||
industry_info = {
|
||||
'id': industry_id,
|
||||
'name': f'行业{industry_id}',
|
||||
'code': f'801{industry_id.zfill(3)}',
|
||||
'description': f'这是行业{industry_id}的详细描述',
|
||||
'stock_count': 50,
|
||||
'avg_change': 1.5,
|
||||
'total_market_cap': 800000000000,
|
||||
'pe_ratio': 15.6,
|
||||
'pb_ratio': 2.3,
|
||||
'roe': 8.5,
|
||||
'top_stocks': [
|
||||
{'code': '000001', 'name': '龙头股A', 'weight': 0.15},
|
||||
{'code': '000002', 'name': '龙头股B', 'weight': 0.12},
|
||||
{'code': '000003', 'name': '龙头股C', 'weight': 0.10}
|
||||
],
|
||||
'sub_industries': [
|
||||
{'id': 1, 'name': '子行业A', 'stock_count': 20},
|
||||
{'id': 2, 'name': '子行业B', 'stock_count': 18},
|
||||
{'id': 3, 'name': '子行业C', 'stock_count': 12}
|
||||
],
|
||||
'performance': {
|
||||
'daily': 1.5,
|
||||
'weekly': 3.2,
|
||||
'monthly': 8.5,
|
||||
'quarterly': 12.3,
|
||||
'yearly': 25.6
|
||||
},
|
||||
'trend': {
|
||||
'direction': 'up',
|
||||
'strength': 'medium',
|
||||
'duration': '3 months'
|
||||
}
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': industry_info
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting industry info: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
469
app/routes/limitanalyse.py
Normal file
469
app/routes/limitanalyse.py
Normal file
@@ -0,0 +1,469 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
import pandas as pd
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
bp = Blueprint('limitanalyse', __name__, url_prefix='/api/limit-analyse')
|
||||
|
||||
@bp.route('/available-dates', methods=['GET'])
|
||||
def get_available_dates():
|
||||
"""获取可用日期列表"""
|
||||
try:
|
||||
# 模拟可用日期
|
||||
dates = [
|
||||
'2025-07-16',
|
||||
'2025-07-15',
|
||||
'2025-07-14',
|
||||
'2025-07-11',
|
||||
'2025-07-10'
|
||||
]
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': dates
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"Error getting available dates: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
def load_stock_data(datestr):
|
||||
"""加载股票数据"""
|
||||
try:
|
||||
# 模拟股票数据
|
||||
data = []
|
||||
for i in range(100):
|
||||
data.append({
|
||||
'code': f'00000{i:03d}',
|
||||
'name': f'股票{i}',
|
||||
'price': 10.0 + i * 0.1,
|
||||
'change': (i % 10 - 5) * 0.5,
|
||||
'sector': f'板块{i % 5}',
|
||||
'limit_type': '涨停' if i % 10 == 0 else '正常',
|
||||
'volume': 1000000 + i * 50000,
|
||||
'amount': 10000000 + i * 500000
|
||||
})
|
||||
|
||||
return pd.DataFrame(data)
|
||||
except Exception as e:
|
||||
print(f"Error loading stock data: {e}")
|
||||
return pd.DataFrame()
|
||||
|
||||
@bp.route('/data', methods=['GET'])
|
||||
def get_analysis_data():
|
||||
"""获取分析数据"""
|
||||
try:
|
||||
date = request.args.get('date', '2025-07-16')
|
||||
|
||||
# 加载数据
|
||||
df = load_stock_data(date)
|
||||
if df.empty:
|
||||
return jsonify({'success': False, 'error': '数据加载失败'}), 500
|
||||
|
||||
# 统计信息
|
||||
total_stocks = len(df)
|
||||
limit_up_stocks = len(df[df['limit_type'] == '涨停'])
|
||||
limit_down_stocks = len(df[df['limit_type'] == '跌停'])
|
||||
|
||||
# 板块统计
|
||||
sector_stats = df.groupby('sector').agg({
|
||||
'code': 'count',
|
||||
'change': 'mean',
|
||||
'volume': 'sum'
|
||||
}).reset_index()
|
||||
|
||||
sector_data = []
|
||||
for _, row in sector_stats.iterrows():
|
||||
sector_data.append({
|
||||
'sector': row['sector'],
|
||||
'stock_count': int(row['code']),
|
||||
'avg_change': round(row['change'], 2),
|
||||
'total_volume': int(row['volume'])
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'date': date,
|
||||
'total_stocks': total_stocks,
|
||||
'limit_up_stocks': limit_up_stocks,
|
||||
'limit_down_stocks': limit_down_stocks,
|
||||
'sector_stats': sector_data
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting analysis data: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/sector-data', methods=['GET'])
|
||||
def get_sector_data():
|
||||
"""获取板块数据"""
|
||||
try:
|
||||
date = request.args.get('date', '2025-07-16')
|
||||
|
||||
# 加载数据
|
||||
df = load_stock_data(date)
|
||||
if df.empty:
|
||||
return jsonify({'success': False, 'error': '数据加载失败'}), 500
|
||||
|
||||
# 板块统计
|
||||
sector_stats = df.groupby('sector').agg({
|
||||
'code': 'count',
|
||||
'change': 'mean',
|
||||
'volume': 'sum',
|
||||
'amount': 'sum'
|
||||
}).reset_index()
|
||||
|
||||
sector_data = []
|
||||
for _, row in sector_stats.iterrows():
|
||||
sector_data.append({
|
||||
'sector': row['sector'],
|
||||
'stock_count': int(row['code']),
|
||||
'avg_change': round(row['change'], 2),
|
||||
'total_volume': int(row['volume']),
|
||||
'total_amount': int(row['amount'])
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': sector_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting sector data: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/word-cloud', methods=['GET'])
|
||||
def get_word_cloud_data():
|
||||
"""获取词云数据"""
|
||||
try:
|
||||
date = request.args.get('date', '2025-07-16')
|
||||
|
||||
# 模拟词云数据
|
||||
word_data = [
|
||||
{'word': '科技', 'value': 100},
|
||||
{'word': '新能源', 'value': 85},
|
||||
{'word': '医药', 'value': 70},
|
||||
{'word': '消费', 'value': 65},
|
||||
{'word': '金融', 'value': 50},
|
||||
{'word': '地产', 'value': 45},
|
||||
{'word': '制造', 'value': 40},
|
||||
{'word': '农业', 'value': 35},
|
||||
{'word': '传媒', 'value': 30},
|
||||
{'word': '环保', 'value': 25}
|
||||
]
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': word_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting word cloud data: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/chart-data', methods=['GET'])
|
||||
def get_chart_data():
|
||||
"""获取图表数据"""
|
||||
try:
|
||||
date = request.args.get('date', '2025-07-16')
|
||||
|
||||
# 模拟图表数据
|
||||
chart_data = {
|
||||
'limit_up_distribution': [
|
||||
{'sector': '科技', 'count': 15},
|
||||
{'sector': '新能源', 'count': 12},
|
||||
{'sector': '医药', 'count': 10},
|
||||
{'sector': '消费', 'count': 8},
|
||||
{'sector': '金融', 'count': 6}
|
||||
],
|
||||
'sector_performance': [
|
||||
{'sector': '科技', 'change': 2.5},
|
||||
{'sector': '新能源', 'change': 1.8},
|
||||
{'sector': '医药', 'change': 1.2},
|
||||
{'sector': '消费', 'change': 0.8},
|
||||
{'sector': '金融', 'change': 0.5}
|
||||
]
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': chart_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting chart data: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/stock-details', methods=['GET'])
|
||||
def get_stock_details():
|
||||
"""获取股票详情"""
|
||||
try:
|
||||
code = request.args.get('code')
|
||||
date = request.args.get('date', '2025-07-16')
|
||||
|
||||
if not code:
|
||||
return jsonify({'success': False, 'error': '请提供股票代码'}), 400
|
||||
|
||||
# 模拟股票详情
|
||||
stock_detail = {
|
||||
'code': code,
|
||||
'name': f'股票{code}',
|
||||
'price': 15.50,
|
||||
'change': 2.5,
|
||||
'sector': '科技',
|
||||
'volume': 1500000,
|
||||
'amount': 23250000,
|
||||
'limit_type': '涨停',
|
||||
'turnover_rate': 3.2,
|
||||
'market_cap': 15500000000
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': stock_detail
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting stock details: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/sector-analysis', methods=['GET'])
|
||||
def get_sector_analysis():
|
||||
"""获取板块分析"""
|
||||
try:
|
||||
sector = request.args.get('sector')
|
||||
date = request.args.get('date', '2025-07-16')
|
||||
|
||||
if not sector:
|
||||
return jsonify({'success': False, 'error': '请提供板块名称'}), 400
|
||||
|
||||
# 模拟板块分析数据
|
||||
sector_analysis = {
|
||||
'sector': sector,
|
||||
'stock_count': 25,
|
||||
'avg_change': 1.8,
|
||||
'limit_up_count': 8,
|
||||
'limit_down_count': 2,
|
||||
'total_volume': 50000000,
|
||||
'total_amount': 750000000,
|
||||
'top_stocks': [
|
||||
{'code': '000001', 'name': '股票A', 'change': 10.0},
|
||||
{'code': '000002', 'name': '股票B', 'change': 9.5},
|
||||
{'code': '000003', 'name': '股票C', 'change': 8.8}
|
||||
]
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': sector_analysis
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting sector analysis: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/trend-analysis', methods=['GET'])
|
||||
def get_trend_analysis():
|
||||
"""获取趋势分析"""
|
||||
try:
|
||||
date = request.args.get('date', '2025-07-16')
|
||||
|
||||
# 模拟趋势分析数据
|
||||
trend_data = {
|
||||
'limit_up_trend': [
|
||||
{'date': '2025-07-10', 'count': 45},
|
||||
{'date': '2025-07-11', 'count': 52},
|
||||
{'date': '2025-07-14', 'count': 48},
|
||||
{'date': '2025-07-15', 'count': 55},
|
||||
{'date': '2025-07-16', 'count': 51}
|
||||
],
|
||||
'sector_trend': [
|
||||
{'sector': '科技', 'trend': 'up'},
|
||||
{'sector': '新能源', 'trend': 'up'},
|
||||
{'sector': '医药', 'trend': 'stable'},
|
||||
{'sector': '消费', 'trend': 'down'},
|
||||
{'sector': '金融', 'trend': 'stable'}
|
||||
]
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': trend_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting trend analysis: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/heat-map', methods=['GET'])
|
||||
def get_heat_map_data():
|
||||
"""获取热力图数据"""
|
||||
try:
|
||||
date = request.args.get('date', '2025-07-16')
|
||||
|
||||
# 模拟热力图数据
|
||||
heat_map_data = []
|
||||
sectors = ['科技', '新能源', '医药', '消费', '金融', '地产', '制造', '农业']
|
||||
|
||||
for i, sector in enumerate(sectors):
|
||||
for j in range(8):
|
||||
heat_map_data.append({
|
||||
'sector': sector,
|
||||
'metric': f'指标{j+1}',
|
||||
'value': (i + j) % 10 + 1
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': heat_map_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting heat map data: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/correlation-analysis', methods=['GET'])
|
||||
def get_correlation_analysis():
|
||||
"""获取相关性分析"""
|
||||
try:
|
||||
date = request.args.get('date', '2025-07-16')
|
||||
|
||||
# 模拟相关性分析数据
|
||||
correlation_data = {
|
||||
'sector_correlations': [
|
||||
{'sector1': '科技', 'sector2': '新能源', 'correlation': 0.85},
|
||||
{'sector1': '医药', 'sector2': '消费', 'correlation': 0.72},
|
||||
{'sector1': '金融', 'sector2': '地产', 'correlation': 0.68},
|
||||
{'sector1': '科技', 'sector2': '医药', 'correlation': 0.45},
|
||||
{'sector1': '新能源', 'sector2': '制造', 'correlation': 0.78}
|
||||
],
|
||||
'stock_correlations': [
|
||||
{'stock1': '000001', 'stock2': '000002', 'correlation': 0.92},
|
||||
{'stock1': '000003', 'stock2': '000004', 'correlation': 0.88},
|
||||
{'stock1': '000005', 'stock2': '000006', 'correlation': 0.76}
|
||||
]
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': correlation_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting correlation analysis: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/export-data', methods=['POST'])
|
||||
def export_data():
|
||||
"""导出数据"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
date = data.get('date', '2025-07-16')
|
||||
export_type = data.get('type', 'excel')
|
||||
|
||||
# 模拟导出
|
||||
filename = f'limit_analyse_{date}.{export_type}'
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '数据导出成功',
|
||||
'data': {
|
||||
'filename': filename,
|
||||
'download_url': f'/downloads/{filename}'
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error exporting data: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/high-position-stocks', methods=['GET'])
|
||||
def get_high_position_stocks():
|
||||
"""获取高位股统计数据"""
|
||||
try:
|
||||
date = request.args.get('date', datetime.now().strftime('%Y%m%d'))
|
||||
|
||||
# 模拟高位股数据 - 实际使用时需要连接真实的数据库
|
||||
# 根据用户提供的表结构,查询连续涨停天数较多的股票
|
||||
high_position_stocks = [
|
||||
{
|
||||
'stock_code': '000001',
|
||||
'stock_name': '平安银行',
|
||||
'price': 15.68,
|
||||
'increase_rate': 10.02,
|
||||
'limit_up_days': 5,
|
||||
'continuous_limit_up': 3,
|
||||
'industry': '银行',
|
||||
'turnover_rate': 3.45,
|
||||
'market_cap': 32000000000
|
||||
},
|
||||
{
|
||||
'stock_code': '000002',
|
||||
'stock_name': '万科A',
|
||||
'price': 18.92,
|
||||
'increase_rate': 9.98,
|
||||
'limit_up_days': 4,
|
||||
'continuous_limit_up': 2,
|
||||
'industry': '房地产',
|
||||
'turnover_rate': 5.67,
|
||||
'market_cap': 21000000000
|
||||
},
|
||||
{
|
||||
'stock_code': '600036',
|
||||
'stock_name': '招商银行',
|
||||
'price': 42.15,
|
||||
'increase_rate': 8.45,
|
||||
'limit_up_days': 6,
|
||||
'continuous_limit_up': 4,
|
||||
'industry': '银行',
|
||||
'turnover_rate': 2.89,
|
||||
'market_cap': 105000000000
|
||||
},
|
||||
{
|
||||
'stock_code': '000858',
|
||||
'stock_name': '五粮液',
|
||||
'price': 168.50,
|
||||
'increase_rate': 7.23,
|
||||
'limit_up_days': 3,
|
||||
'continuous_limit_up': 2,
|
||||
'industry': '白酒',
|
||||
'turnover_rate': 1.56,
|
||||
'market_cap': 650000000000
|
||||
},
|
||||
{
|
||||
'stock_code': '002415',
|
||||
'stock_name': '海康威视',
|
||||
'price': 35.68,
|
||||
'increase_rate': 6.89,
|
||||
'limit_up_days': 4,
|
||||
'continuous_limit_up': 3,
|
||||
'industry': '安防',
|
||||
'turnover_rate': 4.12,
|
||||
'market_cap': 33000000000
|
||||
}
|
||||
]
|
||||
|
||||
# 统计信息
|
||||
total_count = len(high_position_stocks)
|
||||
avg_continuous_days = sum(stock['continuous_limit_up'] for stock in high_position_stocks) / total_count if total_count > 0 else 0
|
||||
|
||||
# 按连续涨停天数排序
|
||||
high_position_stocks.sort(key=lambda x: x['continuous_limit_up'], reverse=True)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'stocks': high_position_stocks,
|
||||
'statistics': {
|
||||
'total_count': total_count,
|
||||
'avg_continuous_days': round(avg_continuous_days, 2),
|
||||
'max_continuous_days': max([stock['continuous_limit_up'] for stock in high_position_stocks], default=0),
|
||||
'industry_distribution': {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting high position stocks: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
241
app/routes/stocks.py
Normal file
241
app/routes/stocks.py
Normal file
@@ -0,0 +1,241 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from app import db
|
||||
from clickhouse_driver import Client
|
||||
import pandas as pd
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
|
||||
bp = Blueprint('stocks', __name__, url_prefix='/api/stock')
|
||||
|
||||
def get_clickhouse_client():
|
||||
"""获取ClickHouse客户端"""
|
||||
return Client('localhost', port=9000, user='default', password='', database='default')
|
||||
|
||||
@bp.route('/quotes', methods=['GET', 'POST'])
|
||||
def get_stock_quotes():
|
||||
"""获取股票实时报价"""
|
||||
try:
|
||||
if request.method == 'GET':
|
||||
# GET 请求从 URL 参数获取数据
|
||||
codes = request.args.get('codes', '').split(',')
|
||||
event_time_str = request.args.get('event_time')
|
||||
else:
|
||||
# POST 请求从 JSON 获取数据
|
||||
codes = request.json.get('codes', [])
|
||||
event_time_str = request.json.get('event_time')
|
||||
|
||||
if not codes:
|
||||
return jsonify({'success': False, 'error': '请提供股票代码'}), 400
|
||||
|
||||
# 过滤空字符串
|
||||
codes = [code.strip() for code in codes if code.strip()]
|
||||
|
||||
if not codes:
|
||||
return jsonify({'success': False, 'error': '请提供有效的股票代码'}), 400
|
||||
|
||||
# 解析事件时间
|
||||
event_time = None
|
||||
if event_time_str:
|
||||
try:
|
||||
event_time = datetime.fromisoformat(event_time_str.replace('Z', '+00:00'))
|
||||
except ValueError:
|
||||
return jsonify({'success': False, 'error': '事件时间格式错误'}), 400
|
||||
|
||||
# 获取当前时间
|
||||
now = datetime.now(pytz.timezone('Asia/Shanghai'))
|
||||
|
||||
# 如果提供了事件时间,使用事件时间;否则使用当前时间
|
||||
target_time = event_time if event_time else now
|
||||
|
||||
# 获取交易日和交易时间
|
||||
def get_trading_day_and_times(event_datetime):
|
||||
"""获取交易日和交易时间列表"""
|
||||
# 这里简化处理,实际应该查询交易日历
|
||||
trading_day = event_datetime.strftime('%Y-%m-%d')
|
||||
|
||||
# 生成交易时间列表 (9:30-11:30, 13:00-15:00)
|
||||
morning_times = [f"{trading_day} {hour:02d}:{minute:02d}"
|
||||
for hour in range(9, 12)
|
||||
for minute in range(0, 60, 1)
|
||||
if not (hour == 9 and minute < 30) and not (hour == 11 and minute > 30)]
|
||||
|
||||
afternoon_times = [f"{trading_day} {hour:02d}:{minute:02d}"
|
||||
for hour in range(13, 16)
|
||||
for minute in range(0, 60, 1)]
|
||||
|
||||
return trading_day, morning_times + afternoon_times
|
||||
|
||||
trading_day, trading_times = get_trading_day_and_times(target_time)
|
||||
|
||||
# 模拟股票数据
|
||||
results = {}
|
||||
for code in codes:
|
||||
# 这里应该从ClickHouse或其他数据源获取真实数据
|
||||
# 现在使用模拟数据
|
||||
import random
|
||||
base_price = 10.0 + random.random() * 20.0
|
||||
change = (random.random() - 0.5) * 2.0
|
||||
|
||||
results[code] = {
|
||||
'price': round(base_price, 2),
|
||||
'change': round(change, 2),
|
||||
'name': f'股票{code}'
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': results
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting stock quotes: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@bp.route('/<stock_code>/kline')
|
||||
def get_stock_kline(stock_code):
|
||||
"""获取股票K线数据"""
|
||||
try:
|
||||
chart_type = request.args.get('type', 'daily')
|
||||
event_time_str = request.args.get('event_time')
|
||||
|
||||
if not event_time_str:
|
||||
return jsonify({'success': False, 'error': '请提供事件时间'}), 400
|
||||
|
||||
try:
|
||||
event_datetime = datetime.fromisoformat(event_time_str.replace('Z', '+00:00'))
|
||||
except ValueError:
|
||||
return jsonify({'success': False, 'error': '事件时间格式错误'}), 400
|
||||
|
||||
# 获取股票名称(这里简化处理)
|
||||
stock_name = f'股票{stock_code}'
|
||||
|
||||
if chart_type == 'daily':
|
||||
return get_daily_kline(stock_code, event_datetime, stock_name)
|
||||
elif chart_type == 'minute':
|
||||
return get_minute_kline(stock_code, event_datetime, stock_name)
|
||||
elif chart_type == 'timeline':
|
||||
return get_timeline_data(stock_code, event_datetime, stock_name)
|
||||
else:
|
||||
return jsonify({'error': f'Unsupported chart type: {chart_type}'}), 400
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting stock kline: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
def get_daily_kline(stock_code, event_datetime, stock_name):
|
||||
"""获取日K线数据"""
|
||||
try:
|
||||
# 模拟日K线数据
|
||||
data = []
|
||||
base_price = 10.0
|
||||
for i in range(30):
|
||||
date = (event_datetime - timedelta(days=30-i)).strftime('%Y-%m-%d')
|
||||
open_price = base_price + (i * 0.1) + (i % 3 - 1) * 0.5
|
||||
close_price = open_price + (i % 5 - 2) * 0.3
|
||||
high_price = max(open_price, close_price) + 0.2
|
||||
low_price = min(open_price, close_price) - 0.2
|
||||
volume = 1000000 + i * 50000
|
||||
|
||||
data.append({
|
||||
'date': date,
|
||||
'open': round(open_price, 2),
|
||||
'close': round(close_price, 2),
|
||||
'high': round(high_price, 2),
|
||||
'low': round(low_price, 2),
|
||||
'volume': volume
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'code': stock_code,
|
||||
'name': stock_name,
|
||||
'trade_date': event_datetime.strftime('%Y-%m-%d'),
|
||||
'data': data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting daily kline: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
def get_minute_kline(stock_code, event_datetime, stock_name):
|
||||
"""获取分钟K线数据"""
|
||||
try:
|
||||
# 模拟分钟K线数据
|
||||
data = []
|
||||
base_price = 10.0
|
||||
trading_times = []
|
||||
|
||||
# 生成交易时间
|
||||
for hour in range(9, 16):
|
||||
if hour == 12:
|
||||
continue
|
||||
for minute in range(0, 60):
|
||||
if (hour == 9 and minute < 30) or (hour == 11 and minute > 30):
|
||||
continue
|
||||
trading_times.append(f"{hour:02d}:{minute:02d}")
|
||||
|
||||
for i, time in enumerate(trading_times):
|
||||
open_price = base_price + (i * 0.01) + (i % 10 - 5) * 0.02
|
||||
close_price = open_price + (i % 7 - 3) * 0.01
|
||||
high_price = max(open_price, close_price) + 0.01
|
||||
low_price = min(open_price, close_price) - 0.01
|
||||
volume = 50000 + i * 1000
|
||||
|
||||
data.append({
|
||||
'time': time,
|
||||
'open': round(open_price, 2),
|
||||
'close': round(close_price, 2),
|
||||
'high': round(high_price, 2),
|
||||
'low': round(low_price, 2),
|
||||
'volume': volume
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'code': stock_code,
|
||||
'name': stock_name,
|
||||
'trade_date': event_datetime.strftime('%Y-%m-%d'),
|
||||
'data': data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting minute kline: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
def get_timeline_data(stock_code, event_datetime, stock_name):
|
||||
"""获取分时图数据"""
|
||||
try:
|
||||
# 模拟分时图数据
|
||||
data = []
|
||||
base_price = 10.0
|
||||
trading_times = []
|
||||
|
||||
# 生成交易时间
|
||||
for hour in range(9, 16):
|
||||
if hour == 12:
|
||||
continue
|
||||
for minute in range(0, 60):
|
||||
if (hour == 9 and minute < 30) or (hour == 11 and minute > 30):
|
||||
continue
|
||||
trading_times.append(f"{hour:02d}:{minute:02d}")
|
||||
|
||||
for i, time in enumerate(trading_times):
|
||||
price = base_price + (i * 0.01) + (i % 10 - 5) * 0.02
|
||||
avg_price = price + (i % 5 - 2) * 0.01
|
||||
volume = 50000 + i * 1000
|
||||
|
||||
data.append({
|
||||
'time': time,
|
||||
'price': round(price, 2),
|
||||
'avg_price': round(avg_price, 2),
|
||||
'volume': volume
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'code': stock_code,
|
||||
'name': stock_name,
|
||||
'trade_date': event_datetime.strftime('%Y-%m-%d'),
|
||||
'data': data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting timeline data: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
Reference in New Issue
Block a user