This commit is contained in:
parent
c0f25aaafd
commit
3145d08c30
|
|
@ -132,7 +132,9 @@ class PriceService:
|
|||
yf_ticker,
|
||||
start=start_date.strftime("%Y-%m-%d"),
|
||||
end=end_date.strftime("%Y-%m-%d"),
|
||||
multi_level_index=False,
|
||||
progress=False,
|
||||
auto_adjust=False,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
|
|
@ -145,7 +147,9 @@ class PriceService:
|
|||
alt_ticker,
|
||||
start=start_date.strftime("%Y-%m-%d"),
|
||||
end=end_date.strftime("%Y-%m-%d"),
|
||||
multi_level_index=False,
|
||||
progress=False,
|
||||
auto_adjust=False,
|
||||
timeout=30
|
||||
)
|
||||
if not data.empty:
|
||||
|
|
@ -157,7 +161,9 @@ class PriceService:
|
|||
alt_ticker,
|
||||
start=start_date.strftime("%Y-%m-%d"),
|
||||
end=end_date.strftime("%Y-%m-%d"),
|
||||
multi_level_index=False,
|
||||
progress=False,
|
||||
auto_adjust=False,
|
||||
timeout=30
|
||||
)
|
||||
if not data.empty:
|
||||
|
|
|
|||
|
|
@ -1210,7 +1210,7 @@
|
|||
|
||||
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
|
||||
"ts-api-utils": ["ts-api-utils@2.2.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-L6f5oQRAoLU1RwXz0Ab9mxsE7LtxeVB6AIR1lpkZMsOyg/JXeaxBaXa/FVCBZyNr9S9I4wkHrlZTklX+im+WMw=="],
|
||||
|
||||
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,9 @@ import {
|
|||
CartesianGrid,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
Rectangle,
|
||||
} from "recharts";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
import type { PriceData, PriceStats } from "@/lib/types";
|
||||
|
||||
|
|
@ -98,7 +97,7 @@ export function PriceChart({ priceData, priceStats, ticker }: PriceChartProps) {
|
|||
const priceValues = heikinAshiData.flatMap(d => [d.HA_High, d.HA_Low]);
|
||||
const minPrice = Math.min(...priceValues);
|
||||
const maxPrice = Math.max(...priceValues);
|
||||
const priceRange = maxPrice - minPrice;
|
||||
const _priceRange = maxPrice - minPrice;
|
||||
|
||||
// Localized labels
|
||||
const labels = {
|
||||
|
|
@ -111,10 +110,12 @@ export function PriceChart({ priceData, priceStats, ticker }: PriceChartProps) {
|
|||
lineChart: t.results.priceSection.lineChart,
|
||||
candlestick: t.results.priceSection.candlestick,
|
||||
volume: t.results.volumeChart,
|
||||
adjClosePrice: locale === 'en' ? 'Adj Close' : '調整後收盤價',
|
||||
closePrice: locale === 'en' ? 'Close' : '收盤價',
|
||||
date: locale === 'en' ? 'Date' : '日期',
|
||||
open: locale === 'en' ? 'Open' : '開',
|
||||
close: locale === 'en' ? 'Close' : '收',
|
||||
adjClose: locale === 'en' ? 'Adj Close' : '調整收',
|
||||
high: locale === 'en' ? 'High' : '高',
|
||||
low: locale === 'en' ? 'Low' : '低',
|
||||
up: locale === 'en' ? '↑ Up' : '↑ 上漲',
|
||||
|
|
@ -178,18 +179,38 @@ export function PriceChart({ priceData, priceStats, ticker }: PriceChartProps) {
|
|||
tickFormatter={(value) => `$${value.toFixed(0)}`}
|
||||
/>
|
||||
<Tooltip
|
||||
formatter={(value: number | undefined) => [
|
||||
value !== undefined ? `$${formatNumber(value)}` : '-',
|
||||
labels.closePrice
|
||||
]}
|
||||
labelFormatter={(label) => `${labels.date}: ${label}`}
|
||||
content={({ active, payload }) => {
|
||||
if (active && payload && payload.length) {
|
||||
const data = payload[0].payload as PriceData;
|
||||
const adjClose = data["Adj Close"];
|
||||
const close = data.Close;
|
||||
const hasAdjClose = adjClose !== undefined && adjClose !== null;
|
||||
|
||||
return (
|
||||
<div className="bg-background border border-border p-3 rounded-lg shadow-lg">
|
||||
<p className="text-sm font-semibold mb-2">{labels.date}: {data.Date}</p>
|
||||
<div className="space-y-1 text-sm">
|
||||
{hasAdjClose && (
|
||||
<p className="text-blue-600 font-medium">
|
||||
{labels.adjClosePrice}: ${formatNumber(adjClose)}
|
||||
</p>
|
||||
)}
|
||||
<p className={hasAdjClose ? "text-gray-500 text-xs" : "text-blue-600"}>
|
||||
{labels.closePrice}: ${formatNumber(close)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey={(data: PriceData) => getCloseValue(data)}
|
||||
stroke="#93c5fd"
|
||||
strokeWidth={2}
|
||||
name={labels.closePrice}
|
||||
name={labels.adjClosePrice}
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
|
|
@ -212,7 +233,9 @@ export function PriceChart({ priceData, priceStats, ticker }: PriceChartProps) {
|
|||
const data = payload[0].payload as HeikinAshiData;
|
||||
const isUp = data.HA_Close > data.HA_Open;
|
||||
const isDown = data.HA_Close < data.HA_Open;
|
||||
const isNeutral = data.HA_Close === data.HA_Open;
|
||||
const adjClose = data["Adj Close"];
|
||||
const close = data.Close;
|
||||
const hasAdjClose = adjClose !== undefined && adjClose !== null;
|
||||
|
||||
// Trend color coding for the direction indicator
|
||||
const trendColor = isUp ? 'text-green-600' : isDown ? 'text-red-600' : 'text-gray-600';
|
||||
|
|
@ -221,7 +244,25 @@ export function PriceChart({ priceData, priceStats, ticker }: PriceChartProps) {
|
|||
return (
|
||||
<div className="bg-background border border-border p-3 rounded-lg shadow-lg">
|
||||
<p className="text-sm font-semibold mb-2">{labels.date}: {data.Date}</p>
|
||||
|
||||
{/* 原始價格 - Adj Close 為主 */}
|
||||
<div className="mb-2 pb-2 border-b border-border">
|
||||
<p className="text-xs text-muted-foreground mb-1">
|
||||
{locale === 'en' ? 'Actual Prices' : '實際價格'}
|
||||
</p>
|
||||
{hasAdjClose && (
|
||||
<p className="text-blue-600 font-medium text-sm">
|
||||
{labels.adjClosePrice}: ${formatNumber(adjClose)}
|
||||
</p>
|
||||
)}
|
||||
<p className={hasAdjClose ? "text-gray-500 text-xs" : "text-blue-600 text-sm"}>
|
||||
{labels.closePrice}: ${formatNumber(close)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Heikin Ashi 值 */}
|
||||
<div className="space-y-1 text-sm">
|
||||
<p className="text-xs text-muted-foreground mb-1">Heikin Ashi</p>
|
||||
<p className="text-purple-600">
|
||||
{labels.open}: ${formatNumber(data.HA_Open)}
|
||||
</p>
|
||||
|
|
@ -303,7 +344,7 @@ const HeikinAshiCandlestickShape: React.FC<HeikinAshiCandlestickShapeProps> = (p
|
|||
const { HA_Open, HA_Close, HA_High, HA_Low } = payload;
|
||||
const isUp = HA_Close > HA_Open;
|
||||
const isDown = HA_Close < HA_Open;
|
||||
const isNeutral = HA_Close === HA_Open;
|
||||
const _isNeutral = HA_Close === HA_Open;
|
||||
|
||||
// Color coding: green for up, red for down, gray for neutral
|
||||
let fillColor: string;
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class StockstatsUtils:
|
|||
end=end_date_str,
|
||||
multi_level_index=False,
|
||||
progress=False,
|
||||
auto_adjust=True,
|
||||
auto_adjust=False,
|
||||
)
|
||||
data_yf = data_yf.reset_index()
|
||||
data_yf.to_csv(data_file, index=False)
|
||||
|
|
|
|||
|
|
@ -32,12 +32,17 @@ def get_YFin_data_online(
|
|||
datetime.strptime(start_date, "%Y-%m-%d")
|
||||
datetime.strptime(end_date, "%Y-%m-%d")
|
||||
|
||||
# 建立股票代碼物件
|
||||
ticker = yf.Ticker(symbol.upper())
|
||||
|
||||
# 獲取指定日期範圍的歷史數據(添加 timeout)
|
||||
# 使用 yf.download() 獲取指定日期範圍的歷史數據
|
||||
try:
|
||||
data = ticker.history(start=start_date, end=end_date, timeout=30)
|
||||
data = yf.download(
|
||||
symbol.upper(),
|
||||
start=start_date,
|
||||
end=end_date,
|
||||
multi_level_index=False,
|
||||
progress=False,
|
||||
auto_adjust=False,
|
||||
timeout=30
|
||||
)
|
||||
except Exception as e:
|
||||
raise Exception(f"從 Yahoo Finance 獲取 {symbol} 數據失敗: {e}")
|
||||
|
||||
|
|
@ -297,7 +302,7 @@ def _get_stock_stats_bulk(
|
|||
end=end_date_str,
|
||||
multi_level_index=False,
|
||||
progress=False,
|
||||
auto_adjust=True,
|
||||
auto_adjust=False,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -37,14 +37,24 @@ class YFinanceUtils:
|
|||
) -> pl.DataFrame:
|
||||
"""檢索指定股票代碼的股價數據"""
|
||||
from datetime import datetime, timedelta
|
||||
ticker = symbol
|
||||
ticker = symbol # 這裡 symbol 已被裝飾器轉換為 yf.Ticker 對象
|
||||
ticker_symbol = ticker.ticker # 獲取股票代碼字串
|
||||
# 將結束日期加一天,使數據範圍包含結束日期
|
||||
end_date_dt = datetime.strptime(end_date, "%Y-%m-%d") + timedelta(days=1)
|
||||
end_date = end_date_dt.strftime("%Y-%m-%d")
|
||||
stock_data = ticker.history(start=start_date, end=end_date)
|
||||
# 使用 yf.download() 統一獲取數據
|
||||
stock_data = yf.download(
|
||||
ticker_symbol,
|
||||
start=start_date,
|
||||
end=end_date,
|
||||
multi_level_index=False,
|
||||
progress=False,
|
||||
auto_adjust=False,
|
||||
timeout=30
|
||||
)
|
||||
# 轉換為 polars DataFrame
|
||||
stock_data_pl = pl.from_pandas(stock_data.reset_index())
|
||||
# save_output(stock_data_pl, f"{ticker.ticker} 的股票數據", save_path)
|
||||
# save_output(stock_data_pl, f"{ticker_symbol} 的股票數據", save_path)
|
||||
return stock_data_pl
|
||||
|
||||
def get_stock_info(
|
||||
|
|
|
|||
Loading…
Reference in New Issue