feat: 增加登陆注册;修复添加自选bug

develop
Lxy 4 months ago
parent 1b4ffdfc7e
commit 44e48c0e1e

@ -1,6 +1,7 @@
# AI 研判模块 # AI 研判模块
import json import json
import requests import requests
import datetime
from typing import Dict, Optional, List from typing import Dict, Optional, List
from qihuo_analyzer.utils.config_manager import config_manager from qihuo_analyzer.utils.config_manager import config_manager
from qihuo_analyzer.core.models import AnalysisResult from qihuo_analyzer.core.models import AnalysisResult
@ -52,9 +53,65 @@ class DeepseekAgent:
self.api_url = self.current_config['api_url'] self.api_url = self.current_config['api_url']
self.headers = self.current_config['headers'] self.headers = self.current_config['headers']
# 初始化缓存字典
# 缓存结构: {"date_symbol_model": {"timestamp": "2023-07-01 12:00:00", "data": {...}}}
self.cache = {}
def _get_cache_key(self, market_data, model_name):
"""获取缓存键
Args:
market_data: 市场数据包含symbol
model_name: 模型名称
Returns:
str: 缓存键格式为 "日期_品种_模型"
"""
# 获取当前日期(交易日)
today = datetime.datetime.now().strftime('%Y-%m-%d')
# 获取品种代码
symbol = market_data.get('symbol', 'unknown')
# 构建缓存键
cache_key = f"{today}_{symbol}_{model_name}"
return cache_key
def _get_from_cache(self, cache_key):
"""从缓存中获取数据
Args:
cache_key: 缓存键
Returns:
Dict: 缓存的数据如果不存在返回None
"""
if cache_key in self.cache:
return self.cache[cache_key]['data']
return None
def _set_to_cache(self, cache_key, data):
"""将数据设置到缓存中
Args:
cache_key: 缓存键
data: 要缓存的数据
"""
self.cache[cache_key] = {
'timestamp': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'data': data
}
def analyze_market(self, market_data: Dict, technical_indicators: Dict, def analyze_market(self, market_data: Dict, technical_indicators: Dict,
trend_analysis: Dict, risk_metrics: Dict) -> Dict: trend_analysis: Dict, risk_metrics: Dict) -> Dict:
"""分析市场""" """分析市场"""
# 生成缓存键
cache_key = self._get_cache_key(market_data, self.model_name)
# 检查缓存
cached_data = self._get_from_cache(cache_key)
if cached_data:
print(f"[DEBUG] 从缓存中获取分析数据: {cache_key}")
return cached_data
# 构建提示词 # 构建提示词
prompt = self._build_analysis_prompt(market_data, technical_indicators, trend_analysis, risk_metrics) prompt = self._build_analysis_prompt(market_data, technical_indicators, trend_analysis, risk_metrics)
@ -64,10 +121,24 @@ class DeepseekAgent:
# 解析结果 # 解析结果
analysis_result = self._parse_analysis_result(response) analysis_result = self._parse_analysis_result(response)
# 缓存结果
self._set_to_cache(cache_key, analysis_result)
print(f"[DEBUG] 缓存分析数据: {cache_key}")
return analysis_result return analysis_result
def generate_trade_recommendation(self, analysis_result: Dict) -> Dict: def generate_trade_recommendation(self, analysis_result: Dict, market_data: Optional[Dict] = None) -> Dict:
"""生成交易建议""" """生成交易建议"""
# 生成缓存键
if market_data:
cache_key = self._get_cache_key(market_data, f"{self.model_name}_recommendation")
# 检查缓存
cached_data = self._get_from_cache(cache_key)
if cached_data:
print(f"[DEBUG] 从缓存中获取交易建议: {cache_key}")
return cached_data
# 构建提示词 # 构建提示词
prompt = self._build_recommendation_prompt(analysis_result) prompt = self._build_recommendation_prompt(analysis_result)
@ -77,6 +148,11 @@ class DeepseekAgent:
# 解析结果 # 解析结果
recommendation = self._parse_recommendation_result(response) recommendation = self._parse_recommendation_result(response)
# 缓存结果
if market_data:
self._set_to_cache(cache_key, recommendation)
print(f"[DEBUG] 缓存交易建议: {cache_key}")
return recommendation return recommendation
def _build_analysis_prompt(self, market_data: Dict, technical_indicators: Dict, def _build_analysis_prompt(self, market_data: Dict, technical_indicators: Dict,

@ -7,3 +7,6 @@ requests==2.31.0
python-dotenv==1.0.0 python-dotenv==1.0.0
APScheduler==3.10.4 APScheduler==3.10.4
pytest==7.4.4 pytest==7.4.4
Flask==2.0.1
Flask-Login==0.6.2
Werkzeug==2.0.1

@ -1,13 +1,16 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Flask web 应用 # Flask web 应用
from flask import Flask, render_template, jsonify, request from flask import Flask, render_template, jsonify, request, redirect, url_for, flash, session
import sys import sys
import os import os
# 添加项目根目录到 Python 路径 # 添加项目根目录到 Python 路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# 导入认证模块
from auth import login_manager, init_db, register_user, login_user_by_credentials, logout_user, login_required, current_user
from qihuo_analyzer.data.data_fetcher import DataFetcher from qihuo_analyzer.data.data_fetcher import DataFetcher
from qihuo_analyzer.data.data_storage import DataStorage from qihuo_analyzer.data.data_storage import DataStorage
from qihuo_analyzer.modules.trend_filter import TrendFilter from qihuo_analyzer.modules.trend_filter import TrendFilter
@ -20,13 +23,57 @@ from qihuo_analyzer.core.models import AnalysisResult
app = Flask(__name__) app = Flask(__name__)
# 设置Flask-Login
app.secret_key = 'your-secret-key-here' # 实际项目中应该使用环境变量
login_manager.init_app(app)
login_manager.login_view = 'login' # 设置登录页面的路由
# 初始化数据库
init_db()
# 模板上下文处理器 # 模板上下文处理器
@app.context_processor @app.context_processor
def inject_functions(): def inject_functions():
import datetime import datetime
def now(): def now():
return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
return {'now': now} return {'now': now, 'current_user': current_user}
# 登录路由
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
success, message = login_user_by_credentials(username, password)
if success:
flash(message)
return redirect(url_for('index'))
else:
flash(message)
return render_template('login.html')
# 注册路由
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
success, message = register_user(username, password)
if success:
flash(message)
return redirect(url_for('login'))
else:
flash(message)
return render_template('register.html')
# 登出路由
@app.route('/logout')
@login_required
def logout():
logout_user()
flash('已登出')
return redirect(url_for('login'))
# 初始化组件 # 初始化组件
data_fetcher = DataFetcher() data_fetcher = DataFetcher()
@ -68,11 +115,16 @@ def get_hot_symbols(all_symbols_data):
try: try:
# 这里简化处理,实际项目中应该使用真实的成交量、振幅、涨速数据 # 这里简化处理,实际项目中应该使用真实的成交量、振幅、涨速数据
# 假设我们从kline_data中获取这些数据 # 假设我们从kline_data中获取这些数据
# 由于我们没有这些数据,这里使用随机值模拟 # 由于我们没有这些数据,这里使用基于品种代码的哈希值生成值,确保每次计算结果一致
import random import hashlib
symbol_data['volume'] = random.randint(10000, 1000000) # 使用品种代码生成哈希值
symbol_data['amplitude'] = random.uniform(0.1, 5.0) hash_obj = hashlib.md5(symbol_data['symbol'].encode())
symbol_data['speed'] = random.uniform(-2.0, 2.0) hash_int = int(hash_obj.hexdigest(), 16)
# 基于哈希值生成成交量、振幅、涨速
symbol_data['volume'] = 100000 + (hash_int % 900000) # 100000-1000000
symbol_data['amplitude'] = 0.1 + (hash_int % 100) / 25 # 0.1-4.1
symbol_data['speed'] = -2.0 + (hash_int % 100) / 25 # -2.0-2.0
except Exception: except Exception:
pass pass
@ -88,6 +140,7 @@ def get_hot_symbols(all_symbols_data):
return hot_symbols return hot_symbols
@app.route('/') @app.route('/')
@login_required
def index(): def index():
"""首页 - 多品种分析面板""" """首页 - 多品种分析面板"""
# 获取所有品种的分析数据 # 获取所有品种的分析数据
@ -301,6 +354,7 @@ def index():
data_unavailable=False) data_unavailable=False)
@app.route('/symbol/<symbol>') @app.route('/symbol/<symbol>')
@login_required
def symbol_detail(symbol): def symbol_detail(symbol):
"""品种详情页""" """品种详情页"""
try: try:
@ -451,7 +505,7 @@ def symbol_detail(symbol):
# AI 分析 - 使用指定的模型 # AI 分析 - 使用指定的模型
ai_agent = DeepseekAgent(model_name=model_name) ai_agent = DeepseekAgent(model_name=model_name)
ai_analysis = ai_agent.analyze_market(market_data, technical_indicators, trend_data, risk_metrics) ai_analysis = ai_agent.analyze_market(market_data, technical_indicators, trend_data, risk_metrics)
recommendation = ai_agent.generate_trade_recommendation(ai_analysis) recommendation = ai_agent.generate_trade_recommendation(ai_analysis, market_data)
# 构建模板数据 # 构建模板数据
# 获取中文名称 # 获取中文名称
@ -544,6 +598,7 @@ def symbol_detail(symbol):
return render_template('error.html', message=f"分析失败: {str(e)}") return render_template('error.html', message=f"分析失败: {str(e)}")
@app.route('/api/analysis/<symbol>') @app.route('/api/analysis/<symbol>')
@login_required
def api_analysis(symbol): def api_analysis(symbol):
"""API: 获取品种分析数据""" """API: 获取品种分析数据"""
try: try:
@ -598,6 +653,7 @@ def api_analysis(symbol):
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@app.route('/api/card/<symbol>') @app.route('/api/card/<symbol>')
@login_required
def api_card(symbol): def api_card(symbol):
"""API: 获取分析卡片数据""" """API: 获取分析卡片数据"""
try: try:
@ -695,7 +751,7 @@ def api_card(symbol):
} }
ai_analysis = deepseek_agent.analyze_market(market_data, technical_indicators, trend_data, risk_metrics) ai_analysis = deepseek_agent.analyze_market(market_data, technical_indicators, trend_data, risk_metrics)
recommendation = deepseek_agent.generate_trade_recommendation(ai_analysis) recommendation = deepseek_agent.generate_trade_recommendation(ai_analysis, market_data)
# 构建卡片数据 # 构建卡片数据
# 获取中文名称 # 获取中文名称
@ -727,6 +783,7 @@ def api_card(symbol):
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@app.route('/card/<symbol>') @app.route('/card/<symbol>')
@login_required
def card(symbol): def card(symbol):
"""分析卡片页面""" """分析卡片页面"""
try: try:
@ -802,6 +859,7 @@ def _get_kline_data_for_chart(kline_data):
return [] return []
@app.route('/api/selected/add/<symbol>', methods=['POST']) @app.route('/api/selected/add/<symbol>', methods=['POST'])
@login_required
def add_selected_symbol(symbol): def add_selected_symbol(symbol):
"""添加自选品种""" """添加自选品种"""
global selected_symbols global selected_symbols
@ -810,6 +868,7 @@ def add_selected_symbol(symbol):
return jsonify({'status': 'success', 'selected_symbols': selected_symbols}) return jsonify({'status': 'success', 'selected_symbols': selected_symbols})
@app.route('/api/selected/remove/<symbol>', methods=['POST']) @app.route('/api/selected/remove/<symbol>', methods=['POST'])
@login_required
def remove_selected_symbol(symbol): def remove_selected_symbol(symbol):
"""删除自选品种""" """删除自选品种"""
global selected_symbols global selected_symbols
@ -818,6 +877,7 @@ def remove_selected_symbol(symbol):
return jsonify({'status': 'success', 'selected_symbols': selected_symbols}) return jsonify({'status': 'success', 'selected_symbols': selected_symbols})
@app.route('/api/selected/list') @app.route('/api/selected/list')
@login_required
def get_selected_symbols(): def get_selected_symbols():
"""获取自选品种列表""" """获取自选品种列表"""
return jsonify({'selected_symbols': selected_symbols}) return jsonify({'selected_symbols': selected_symbols})

Binary file not shown.

@ -318,9 +318,27 @@
<div class="container"> <div class="container">
<!-- 头部 --> <!-- 头部 -->
<div class="header"> <div class="header">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<h1>AI期货分析系统</h1> <h1>AI期货分析系统</h1>
<p>基于DeepSeek大模型和量化分析算法的智能决策平台</p> <p>基于DeepSeek大模型和量化分析算法的智能决策平台</p>
</div> </div>
<div style="display: flex; align-items: center; gap: 16px;">
{% if current_user.is_authenticated %}
<div style="color: white; font-size: 14px;">
欢迎, {{ current_user.username }}
</div>
<a href="/logout" style="padding: 8px 16px; background: rgba(255, 255, 255, 0.2); color: white; text-decoration: none; border-radius: 6px; font-size: 14px;">
登出
</a>
{% else %}
<a href="/login" style="padding: 8px 16px; background: rgba(255, 255, 255, 0.2); color: white; text-decoration: none; border-radius: 6px; font-size: 14px;">
登录
</a>
{% endif %}
</div>
</div>
</div>
<!-- 主题切换 --> <!-- 主题切换 -->
<div style="text-align: right; margin-bottom: 20px;"> <div style="text-align: right; margin-bottom: 20px;">

Loading…
Cancel
Save