ADD: 美股支持。 arm64 镜像支持

This commit is contained in:
兰志宏
2025-03-01 23:30:53 +08:00
parent b7248d6803
commit fdb59c0693
4 changed files with 162 additions and 12 deletions

View File

@@ -13,6 +13,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v1 uses: docker/login-action@v1
with: with:
@@ -26,6 +29,7 @@ jobs:
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64
push: true push: true
tags: | tags: |
${{ secrets.DOCKERHUB_USERNAME }}/stock-scanner:latest ${{ secrets.DOCKERHUB_USERNAME }}/stock-scanner:latest

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*.log
. *

View File

@@ -20,15 +20,64 @@
<label for="marketType" class="block text-sm font-medium text-gray-700 mb-2"> <label for="marketType" class="block text-sm font-medium text-gray-700 mb-2">
选择市场类型 选择市场类型
</label> </label>
<select id="marketType" <select id="marketType" onchange="handleMarketTypeChange()"
class="w-full p-2 border rounded bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> class="w-full p-2 border rounded bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="A">A股</option> <option value="A">A股</option>
<option value="HK">港股</option> <option value="HK">港股</option>
<!-- <option value="US">美股</option> --> <option value="US">美股</option>
<!-- <option value="CRYPTO">加密货币</option> -->
</select> </select>
</div> </div>
<!-- 美股搜索框 -->
<div id="usStockSearch" class="mb-4 hidden">
<div class="relative">
<input type="text"
id="searchInput"
class="w-full p-2 border rounded bg-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="输入美股名称搜索(中文和英文都试下)"
oninput="debounceSearch(event)">
<!-- 添加搜索 loading 图标 -->
<div id="searchLoading" class="absolute right-3 top-2.5 hidden">
<svg class="animate-spin h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
<!-- 搜索结果下拉框 -->
<div id="searchResults"
class="absolute z-10 w-full mt-1 bg-white border rounded-md shadow-lg hidden">
</div>
</div>
</div>
<!-- 修改搜索相关函数 -->
<script>
async function searchUsStocks(keyword) {
if (!keyword) {
document.getElementById('searchResults').classList.add('hidden');
return;
}
// 显示 loading
document.getElementById('searchLoading').classList.remove('hidden');
try {
const response = await fetch(`/search_us_stocks?keyword=${encodeURIComponent(keyword)}`);
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || '搜索失败');
}
displaySearchResults(data.results);
} catch (error) {
console.error('搜索出错:', error);
} finally {
// 隐藏 loading
document.getElementById('searchLoading').classList.add('hidden');
}
}
</script>
<div class="mb-4"> <div class="mb-4">
<label for="batchStocks" class="block text-sm font-medium text-gray-700 mb-2"> <label for="batchStocks" class="block text-sm font-medium text-gray-700 mb-2">
输入代码 输入代码
@@ -38,6 +87,94 @@
placeholder="输入代码,支持多个代码(用回车或逗号分隔)"></textarea> placeholder="输入代码,支持多个代码(用回车或逗号分隔)"></textarea>
</div> </div>
<!-- 添加新的 JavaScript 代码 -->
<script>
let searchTimeout;
function handleMarketTypeChange() {
const marketType = document.getElementById('marketType').value;
const searchDiv = document.getElementById('usStockSearch');
searchDiv.classList.toggle('hidden', marketType !== 'US');
// 清空搜索结果
document.getElementById('searchResults').innerHTML = '';
document.getElementById('searchInput').value = '';
}
function debounceSearch(event) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
searchUsStocks(event.target.value);
}, 300);
}
function displaySearchResults(results) {
const resultsDiv = document.getElementById('searchResults');
if (!results || results.length === 0) {
resultsDiv.classList.add('hidden');
return;
}
let html = '<div class="py-1">';
results.forEach(stock => {
html += `
<div class="px-4 py-2 hover:bg-gray-100 cursor-pointer flex justify-between items-center"
onclick="selectStock('${stock.symbol}')">
<div>
<div class="font-medium">${stock.name}</div>
<div class="text-sm text-gray-500">${stock.symbol}</div>
</div>
<div class="text-right">
<div class="font-medium">$${stock.price}</div>
<div class="text-sm text-gray-500">市值: ${formatMarketValue(stock.market_value)}</div>
</div>
</div>
`;
});
html += '</div>';
resultsDiv.innerHTML = html;
resultsDiv.classList.remove('hidden');
}
function formatMarketValue(value) {
if (value >= 1e12) {
return (value / 1e12).toFixed(2) + '万亿';
} else if (value >= 1e8) {
return (value / 1e8).toFixed(2) + '亿';
} else if (value >= 1e4) {
return (value / 1e4).toFixed(2) + '万';
}
return value.toString();
}
function selectStock(symbol) {
const textarea = document.getElementById('batchStocks');
const currentValue = textarea.value.trim();
// 添加到 textarea
textarea.value = currentValue
? currentValue + '\n' + symbol
: symbol;
// 清空并隐藏搜索结果
document.getElementById('searchInput').value = '';
document.getElementById('searchResults').classList.add('hidden');
}
// 点击外部时隐藏搜索结果
document.addEventListener('click', function(event) {
const searchResults = document.getElementById('searchResults');
const searchInput = document.getElementById('searchInput');
if (!searchResults.contains(event.target) &&
!searchInput.contains(event.target)) {
searchResults.classList.add('hidden');
}
});
</script>
<button id="analyzeBtn" onclick="analyzeStocks()" <button id="analyzeBtn" onclick="analyzeStocks()"
class="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 flex items-center justify-center"> class="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 flex items-center justify-center">
<span>开始分析</span> <span>开始分析</span>

View File

@@ -17,16 +17,25 @@ class USStockService:
try: try:
# 获取美股数据 # 获取美股数据
df = ak.stock_us_spot_em() df = ak.stock_us_spot_em()
# 转换列名 # 转换列名
df = df.rename(columns={ df = df.rename(columns={
"序号": "index",
"名称": "name", "名称": "name",
"最新价": "price", "最新价": "price",
"涨跌额": "change", "涨跌额": "price_change",
"代码": "symbol", "涨跌幅": "price_change_percent",
"成交额": "volume", "开盘价": "open",
"换手率": "turnover" "最高价": "high",
"最低价": "low",
"昨收价": "pre_close",
"总市值": "market_value",
"市盈率": "pe_ratio",
"成交量": "volume",
"成交额": "turnover",
"振幅": "amplitude",
"换手率": "turnover_rate",
"代码": "symbol"
}) })
# 模糊匹配搜索 # 模糊匹配搜索
@@ -38,11 +47,9 @@ class USStockService:
for item in results: for item in results:
formatted_results.append({ formatted_results.append({
'name': item['name'], 'name': item['name'],
'symbol': item['symbol'].replace('105.', ''), # 移除前缀 'symbol': item['symbol'],
'price': item['price'], 'price': item['price'],
'change': item['change'], 'market_value': item['market_value']
'volume': item['volume'],
'turnover': item['turnover']
}) })
return formatted_results return formatted_results