ADD: 美股支持。 arm64 镜像支持
This commit is contained in:
4
.github/workflows/docker-image.yml
vendored
4
.github/workflows/docker-image.yml
vendored
@@ -13,6 +13,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
@@ -26,6 +29,7 @@ jobs:
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/stock-scanner:latest
|
||||
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.log
|
||||
. *
|
||||
@@ -20,15 +20,64 @@
|
||||
<label for="marketType" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
选择市场类型
|
||||
</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">
|
||||
<option value="A">A股</option>
|
||||
<option value="HK">港股</option>
|
||||
<!-- <option value="US">美股</option> -->
|
||||
<!-- <option value="CRYPTO">加密货币</option> -->
|
||||
<option value="US">美股</option>
|
||||
</select>
|
||||
</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">
|
||||
<label for="batchStocks" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
输入代码
|
||||
@@ -38,6 +87,94 @@
|
||||
placeholder="输入代码,支持多个代码(用回车或逗号分隔)"></textarea>
|
||||
</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()"
|
||||
class="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 flex items-center justify-center">
|
||||
<span>开始分析</span>
|
||||
|
||||
@@ -18,15 +18,24 @@ class USStockService:
|
||||
# 获取美股数据
|
||||
df = ak.stock_us_spot_em()
|
||||
|
||||
|
||||
# 转换列名
|
||||
df = df.rename(columns={
|
||||
"序号": "index",
|
||||
"名称": "name",
|
||||
"最新价": "price",
|
||||
"涨跌额": "change",
|
||||
"代码": "symbol",
|
||||
"成交额": "volume",
|
||||
"换手率": "turnover"
|
||||
"涨跌额": "price_change",
|
||||
"涨跌幅": "price_change_percent",
|
||||
"开盘价": "open",
|
||||
"最高价": "high",
|
||||
"最低价": "low",
|
||||
"昨收价": "pre_close",
|
||||
"总市值": "market_value",
|
||||
"市盈率": "pe_ratio",
|
||||
"成交量": "volume",
|
||||
"成交额": "turnover",
|
||||
"振幅": "amplitude",
|
||||
"换手率": "turnover_rate",
|
||||
"代码": "symbol"
|
||||
})
|
||||
|
||||
# 模糊匹配搜索
|
||||
@@ -38,11 +47,9 @@ class USStockService:
|
||||
for item in results:
|
||||
formatted_results.append({
|
||||
'name': item['name'],
|
||||
'symbol': item['symbol'].replace('105.', ''), # 移除前缀
|
||||
'symbol': item['symbol'],
|
||||
'price': item['price'],
|
||||
'change': item['change'],
|
||||
'volume': item['volume'],
|
||||
'turnover': item['turnover']
|
||||
'market_value': item['market_value']
|
||||
})
|
||||
|
||||
return formatted_results
|
||||
|
||||
Reference in New Issue
Block a user