Add files via upload
1. 增加了港股支持 2. 补全Dockerfile 3. 支持HTML在线访问
This commit is contained in:
27
Dockerfile
Normal file
27
Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 使用 Python 3.9 作为基础镜像
|
||||||
|
FROM python:3.10-slim
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 安装系统依赖
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
libgl1-mesa-glx \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 复制项目文件
|
||||||
|
COPY . /app/
|
||||||
|
|
||||||
|
# 安装 Python 依赖
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
RUN pip install akshare --upgrade -i https://pypi.org/simple
|
||||||
|
|
||||||
|
# 设置环境变量
|
||||||
|
ENV PYTHONPATH=/app
|
||||||
|
|
||||||
|
# 暴露端口(如果需要)
|
||||||
|
EXPOSE 8888
|
||||||
|
|
||||||
|
# 启动命令
|
||||||
|
CMD ["python", "web_server.py"]
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# 基础科学计算和数据处理库
|
# 基础科学计算和数据处理库
|
||||||
numpy==2.0.0
|
numpy==2.1.2
|
||||||
pandas==2.2.2
|
pandas==2.2.2
|
||||||
scipy==1.15.1
|
scipy==1.15.1
|
||||||
|
|
||||||
@@ -7,9 +7,6 @@ scipy==1.15.1
|
|||||||
akshare==1.15.87
|
akshare==1.15.87
|
||||||
tqdm==4.67.1
|
tqdm==4.67.1
|
||||||
|
|
||||||
# GUI库
|
|
||||||
PyQt6==6.8.1
|
|
||||||
markdown2==2.5.3
|
|
||||||
|
|
||||||
# 网络和API请求
|
# 网络和API请求
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class StockAnalyzer:
|
|||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
# 设置 Gemini API
|
# 设置 Gemini API
|
||||||
self.gemini_api_url = "https://api.xxx.xxx"
|
self.gemini_api_url = os.getenv('GEMINI_API_URL')
|
||||||
self.gemini_api_key = os.getenv('GEMINI_API_KEY')
|
self.gemini_api_key = os.getenv('GEMINI_API_KEY')
|
||||||
|
|
||||||
# 配置参数
|
# 配置参数
|
||||||
@@ -30,8 +30,15 @@ class StockAnalyzer:
|
|||||||
'volume_ma_period': 20,
|
'volume_ma_period': 20,
|
||||||
'atr_period': 14
|
'atr_period': 14
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 添加市场类型枚举
|
||||||
|
self.MARKET_TYPES = {
|
||||||
|
'A': 'A股',
|
||||||
|
'HK': '港股',
|
||||||
|
'CRYPTO': '加密货币'
|
||||||
|
}
|
||||||
|
|
||||||
def get_stock_data(self, stock_code, start_date=None, end_date=None):
|
def get_stock_data(self, stock_code, market_type='A', start_date=None, end_date=None, ):
|
||||||
"""获取股票数据"""
|
"""获取股票数据"""
|
||||||
import akshare as ak
|
import akshare as ak
|
||||||
|
|
||||||
@@ -41,11 +48,26 @@ class StockAnalyzer:
|
|||||||
end_date = datetime.now().strftime('%Y%m%d')
|
end_date = datetime.now().strftime('%Y%m%d')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 使用 akshare 获取股票数据
|
# 根据市场类型获取数据
|
||||||
df = ak.stock_zh_a_hist(symbol=stock_code,
|
if market_type == 'A':
|
||||||
start_date=start_date,
|
df = ak.stock_zh_a_hist(
|
||||||
end_date=end_date,
|
symbol=stock_code,
|
||||||
adjust="qfq")
|
start_date=start_date,
|
||||||
|
end_date=end_date,
|
||||||
|
adjust="qfq"
|
||||||
|
)
|
||||||
|
# A股数据列名映射
|
||||||
|
elif market_type == 'HK':
|
||||||
|
df = ak.stock_hk_daily(
|
||||||
|
symbol=stock_code,
|
||||||
|
adjust="qfq"
|
||||||
|
)
|
||||||
|
elif market_type == 'CRYPTO':
|
||||||
|
df = ak.crypto_js_spot(
|
||||||
|
symbol=stock_code
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"不支持的市场类型: {market_type}")
|
||||||
|
|
||||||
# 重命名列名以匹配分析需求
|
# 重命名列名以匹配分析需求
|
||||||
df = df.rename(columns={
|
df = df.rename(columns={
|
||||||
@@ -225,7 +247,7 @@ class StockAnalyzer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"model": "gemini-1.5-flash",
|
"model": os.getenv('GEMINI_API_MODEL'),
|
||||||
"messages": [{"role": "user", "content": prompt}]
|
"messages": [{"role": "user", "content": prompt}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,9 +255,12 @@ class StockAnalyzer:
|
|||||||
f"{self.gemini_api_url}/v1/chat/completions",
|
f"{self.gemini_api_url}/v1/chat/completions",
|
||||||
headers=headers,
|
headers=headers,
|
||||||
json=data,
|
json=data,
|
||||||
timeout=10
|
timeout=30
|
||||||
)
|
)
|
||||||
|
print(headers)
|
||||||
|
print(data)
|
||||||
|
print(response.json())
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return response.json()['choices'][0]['message']['content']
|
return response.json()['choices'][0]['message']['content']
|
||||||
else:
|
else:
|
||||||
@@ -258,11 +283,11 @@ class StockAnalyzer:
|
|||||||
else:
|
else:
|
||||||
return '强烈建议卖出'
|
return '强烈建议卖出'
|
||||||
|
|
||||||
def analyze_stock(self, stock_code):
|
def analyze_stock(self, stock_code, market_type='A'):
|
||||||
"""分析单个股票"""
|
"""分析单个股票"""
|
||||||
try:
|
try:
|
||||||
# 获取股票数据
|
# 获取股票数据
|
||||||
df = self.get_stock_data(stock_code)
|
df = self.get_stock_data(stock_code, market_type)
|
||||||
|
|
||||||
# 计算技术指标
|
# 计算技术指标
|
||||||
df = self.calculate_indicators(df)
|
df = self.calculate_indicators(df)
|
||||||
@@ -295,13 +320,13 @@ class StockAnalyzer:
|
|||||||
self.logger.error(f"分析股票时出错: {str(e)}")
|
self.logger.error(f"分析股票时出错: {str(e)}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def scan_market(self, stock_list, min_score=60):
|
def scan_market(self, stock_list, min_score=60, market_type='A'):
|
||||||
"""扫描市场,寻找符合条件的股票"""
|
"""扫描市场,寻找符合条件的股票"""
|
||||||
recommendations = []
|
recommendations = []
|
||||||
|
|
||||||
for stock_code in stock_list:
|
for stock_code in stock_list:
|
||||||
try:
|
try:
|
||||||
report = self.analyze_stock(stock_code)
|
report = self.analyze_stock(stock_code, market_type)
|
||||||
if report['score'] >= min_score:
|
if report['score'] >= min_score:
|
||||||
recommendations.append(report)
|
recommendations.append(report)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
46
web_server.py
Normal file
46
web_server.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
from flask import Flask, render_template, request, jsonify
|
||||||
|
from stock_analyzer import StockAnalyzer
|
||||||
|
import threading
|
||||||
|
import logging
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
analyzer = StockAnalyzer()
|
||||||
|
|
||||||
|
# 配置日志
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
handler = RotatingFileHandler('flask_app.log', maxBytes=10000000, backupCount=5)
|
||||||
|
handler.setFormatter(logging.Formatter(
|
||||||
|
'[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
|
||||||
|
))
|
||||||
|
app.logger.addHandler(handler)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return render_template('index.html')
|
||||||
|
|
||||||
|
@app.route('/analyze', methods=['POST'])
|
||||||
|
def analyze():
|
||||||
|
try:
|
||||||
|
data = request.json
|
||||||
|
stock_codes = data.get('stock_codes', [])
|
||||||
|
market_type = data.get('market_type', 'A')
|
||||||
|
|
||||||
|
if not stock_codes:
|
||||||
|
return jsonify({'error': '请输入代码'}), 400
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for stock_code in stock_codes:
|
||||||
|
result = analyzer.analyze_stock(stock_code.strip(), market_type)
|
||||||
|
results.append(result)
|
||||||
|
|
||||||
|
return jsonify({'results': results})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# 将 host 设置为 '0.0.0.0' 使其支持所有网络接口访问
|
||||||
|
app.run(host='0.0.0.0', port=8888, debug=True)
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user