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:
|
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
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 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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user