FIX: 1. 港股美股都显示人民币符号 2. 美股搜索报错兼容 ADD: 支持一键复制分析结果
This commit is contained in:
@@ -5,9 +5,9 @@
|
|||||||
|
|
||||||
## 功能变更
|
## 功能变更
|
||||||
1. 增加html页面,支持浏览器在线使用。
|
1. 增加html页面,支持浏览器在线使用。
|
||||||
2. 支持港股,增加A股港股切换
|
2. 增加港股、美股支持。
|
||||||
3. 完善Dockerfile、GitHub Actions 支持docker一键部署使用
|
3. 完善Dockerfile、GitHub Actions 支持docker一键部署使用。
|
||||||
4. 。。。
|
4. 支持x86_64 和 ARM64架构镜像
|
||||||
|
|
||||||
## docker一键部署
|
## docker一键部署
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -31,12 +31,6 @@ class StockAnalyzer:
|
|||||||
'atr_period': 14
|
'atr_period': 14
|
||||||
}
|
}
|
||||||
|
|
||||||
# 添加市场类型枚举
|
|
||||||
self.MARKET_TYPES = {
|
|
||||||
'A': 'A股',
|
|
||||||
'HK': '港股',
|
|
||||||
'CRYPTO': '加密货币'
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_stock_data(self, stock_code, market_type='A', start_date=None, end_date=None, ):
|
def get_stock_data(self, stock_code, market_type='A', start_date=None, end_date=None, ):
|
||||||
"""获取股票数据"""
|
"""获取股票数据"""
|
||||||
@@ -69,10 +63,10 @@ class StockAnalyzer:
|
|||||||
end_date=end_date,
|
end_date=end_date,
|
||||||
adjust="qfq"
|
adjust="qfq"
|
||||||
)
|
)
|
||||||
elif market_type == 'CRYPTO':
|
# elif market_type == 'CRYPTO':
|
||||||
df = ak.crypto_js_spot(
|
# df = ak.crypto_js_spot(
|
||||||
symbol=stock_code
|
# symbol=stock_code
|
||||||
)
|
# )
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"不支持的市场类型: {market_type}")
|
raise ValueError(f"不支持的市场类型: {market_type}")
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,9 @@
|
|||||||
<div id="searchResults"
|
<div id="searchResults"
|
||||||
class="absolute z-10 w-full mt-1 bg-white border rounded-md shadow-lg hidden">
|
class="absolute z-10 w-full mt-1 bg-white border rounded-md shadow-lg hidden">
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 添加错误提示 -->
|
||||||
|
<div id="searchError" class="hidden absolute z-10 w-full mt-1 p-3 bg-red-50 text-red-600 rounded-md border border-red-200">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -55,11 +58,14 @@
|
|||||||
async function searchUsStocks(keyword) {
|
async function searchUsStocks(keyword) {
|
||||||
if (!keyword) {
|
if (!keyword) {
|
||||||
document.getElementById('searchResults').classList.add('hidden');
|
document.getElementById('searchResults').classList.add('hidden');
|
||||||
|
document.getElementById('searchError').classList.add('hidden');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示 loading
|
// 显示 loading
|
||||||
document.getElementById('searchLoading').classList.remove('hidden');
|
document.getElementById('searchLoading').classList.remove('hidden');
|
||||||
|
// 隐藏之前的错误信息
|
||||||
|
document.getElementById('searchError').classList.add('hidden');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/search_us_stocks?keyword=${encodeURIComponent(keyword)}`);
|
const response = await fetch(`/search_us_stocks?keyword=${encodeURIComponent(keyword)}`);
|
||||||
@@ -72,6 +78,12 @@
|
|||||||
displaySearchResults(data.results);
|
displaySearchResults(data.results);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('搜索出错:', error);
|
console.error('搜索出错:', error);
|
||||||
|
// 显示错误信息
|
||||||
|
const errorDiv = document.getElementById('searchError');
|
||||||
|
errorDiv.textContent = `搜索出错: ${error.message}`;
|
||||||
|
errorDiv.classList.remove('hidden');
|
||||||
|
// 隐藏搜索结果
|
||||||
|
document.getElementById('searchResults').classList.add('hidden');
|
||||||
} finally {
|
} finally {
|
||||||
// 隐藏 loading
|
// 隐藏 loading
|
||||||
document.getElementById('searchLoading').classList.add('hidden');
|
document.getElementById('searchLoading').classList.add('hidden');
|
||||||
@@ -189,10 +201,64 @@
|
|||||||
|
|
||||||
<!-- 结果展示 -->
|
<!-- 结果展示 -->
|
||||||
<div id="results" class="mt-8">
|
<div id="results" class="mt-8">
|
||||||
<h2 class="text-2xl font-bold mb-6 text-gray-800">分析结果</h2>
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-800">分析结果</h2>
|
||||||
|
<button onclick="copyAnalysisResults()"
|
||||||
|
class="flex items-center text-blue-600 hover:text-blue-700">
|
||||||
|
<svg class="w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/>
|
||||||
|
</svg>
|
||||||
|
复制分析结果
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div id="resultContent" class="space-y-8"></div>
|
<div id="resultContent" class="space-y-8"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function copyAnalysisResults() {
|
||||||
|
const resultContent = document.getElementById('resultContent');
|
||||||
|
if (!resultContent.textContent.trim()) {
|
||||||
|
alert('暂无分析结果可复制');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取需要复制的文本
|
||||||
|
let copyText = '';
|
||||||
|
const results = resultContent.querySelectorAll('.bg-white');
|
||||||
|
|
||||||
|
results.forEach(result => {
|
||||||
|
// 获取股票代码
|
||||||
|
const stockCode = result.querySelector('h3').textContent.trim();
|
||||||
|
copyText += `股票代码:${stockCode}\n`;
|
||||||
|
|
||||||
|
// 获取主要指标
|
||||||
|
const indicators = result.querySelectorAll('.flex.justify-between');
|
||||||
|
indicators.forEach(indicator => {
|
||||||
|
const label = indicator.querySelector('.text-gray-600').textContent;
|
||||||
|
const value = indicator.querySelector('.font-medium').textContent;
|
||||||
|
copyText += `${label}:${value}\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取 AI 分析内容
|
||||||
|
const aiAnalysis = result.querySelector('.prose').textContent;
|
||||||
|
copyText += `\nAI分析:\n${aiAnalysis}\n`;
|
||||||
|
|
||||||
|
copyText += '\n----------------------------------------\n\n';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 复制到剪贴板
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
textarea.value = copyText;
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
|
||||||
|
// 显示提示
|
||||||
|
alert('分析结果已复制到剪贴板');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<script>
|
<script>
|
||||||
let isAnalyzing = false;
|
let isAnalyzing = false;
|
||||||
|
|
||||||
@@ -262,6 +328,19 @@
|
|||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
results.forEach(result => {
|
results.forEach(result => {
|
||||||
|
// 根据市场类型设置货币符号
|
||||||
|
const currencySymbol = (() => {
|
||||||
|
switch(document.getElementById('marketType').value) {
|
||||||
|
case 'US':
|
||||||
|
return '$';
|
||||||
|
case 'HK':
|
||||||
|
return 'HK$';
|
||||||
|
case 'A':
|
||||||
|
default:
|
||||||
|
return '¥';
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
html += `
|
html += `
|
||||||
<div class="bg-white rounded-lg shadow-lg overflow-hidden">
|
<div class="bg-white rounded-lg shadow-lg overflow-hidden">
|
||||||
<!-- 头部信息 -->
|
<!-- 头部信息 -->
|
||||||
@@ -281,7 +360,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between items-center p-3 bg-gray-50 rounded">
|
<div class="flex justify-between items-center p-3 bg-gray-50 rounded">
|
||||||
<span class="text-gray-600">当前价格</span>
|
<span class="text-gray-600">当前价格</span>
|
||||||
<span class="font-medium">¥${result.price.toFixed(2)}</span>
|
<span class="font-medium">${currencySymbol}${result.price.toFixed(2)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between items-center p-3 bg-gray-50 rounded">
|
<div class="flex justify-between items-center p-3 bg-gray-50 rounded">
|
||||||
<span class="text-gray-600">价格变动</span>
|
<span class="text-gray-600">价格变动</span>
|
||||||
|
|||||||
@@ -40,16 +40,16 @@ class USStockService:
|
|||||||
|
|
||||||
# 模糊匹配搜索
|
# 模糊匹配搜索
|
||||||
mask = df['name'].str.contains(keyword, case=False, na=False)
|
mask = df['name'].str.contains(keyword, case=False, na=False)
|
||||||
results = df[mask].to_dict('records')
|
results = df[mask]
|
||||||
|
|
||||||
# 格式化返回结果
|
# 格式化返回结果并处理 NaN 值
|
||||||
formatted_results = []
|
formatted_results = []
|
||||||
for item in results:
|
for _, row in results.iterrows():
|
||||||
formatted_results.append({
|
formatted_results.append({
|
||||||
'name': item['name'],
|
'name': row['name'] if pd.notna(row['name']) else '',
|
||||||
'symbol': item['symbol'],
|
'symbol': str(row['symbol']) if pd.notna(row['symbol']) else '',
|
||||||
'price': item['price'],
|
'price': float(row['price']) if pd.notna(row['price']) else 0.0,
|
||||||
'market_value': item['market_value']
|
'market_value': float(row['market_value']) if pd.notna(row['market_value']) else 0.0
|
||||||
})
|
})
|
||||||
|
|
||||||
return formatted_results
|
return formatted_results
|
||||||
|
|||||||
Reference in New Issue
Block a user