增加主力数据

This commit is contained in:
2025-12-29 16:17:23 +08:00
parent a9cb60a12b
commit 0e58af9f94
6 changed files with 123 additions and 69 deletions

28
app.py
View File

@@ -9123,11 +9123,10 @@ def get_stock_quote_detail(stock_code):
'week52_high': None, 'week52_high': None,
'week52_low': None, 'week52_low': None,
# 主力动态(预留字段 # 主力动态(数据来源stock_main_capital_flow 表
'main_net_inflow': None, 'net_inflow': None, # 主力净流入量(万元)
'institution_holding': None, 'main_inflow_ratio': None, # 主力净流入量占比(%
'buy_ratio': None, 'net_active_buy_ratio': None, # 净主动买入额占比(%
'sell_ratio': None,
'update_time': None 'update_time': None
} }
@@ -9211,6 +9210,25 @@ def get_stock_quote_detail(stock_code):
result_data['week52_high'] = float(w52.get('week52_high') or 0) result_data['week52_high'] = float(w52.get('week52_high') or 0)
result_data['week52_low'] = float(w52.get('week52_low') or 0) result_data['week52_low'] = float(w52.get('week52_low') or 0)
# 3. 获取主力资金流向数据(取最新交易日)
capital_flow_query = text("""
SELECT
net_inflow,
net_active_buy_ratio,
main_inflow_ratio
FROM stock_main_capital_flow
WHERE code = :stock_code
ORDER BY trade_date DESC
LIMIT 1
""")
capital_flow_result = conn.execute(capital_flow_query, {'stock_code': base_code}).fetchone()
if capital_flow_result:
cf = row_to_dict(capital_flow_result)
result_data['net_inflow'] = float(cf.get('net_inflow') or 0) if cf.get('net_inflow') is not None else None
result_data['main_inflow_ratio'] = float(cf.get('main_inflow_ratio') or 0) if cf.get('main_inflow_ratio') is not None else None
result_data['net_active_buy_ratio'] = float(cf.get('net_active_buy_ratio') or 0) if cf.get('net_active_buy_ratio') is not None else None
return jsonify({ return jsonify({
'success': True, 'success': True,
'data': result_data 'data': result_data

View File

@@ -1,28 +1,25 @@
/** /**
* MainForceInfo - 主力动态原子组件 * MainForceInfo - 主力动态原子组件
* *
* 显示主力资金和机构相关指标 * 显示主力资金流向指标数据来源stock_main_capital_flow 表)
* - 主力净流入(带正负颜色) * - 主力净流入(万元,带正负颜色)
* - 机构持仓比例 * - 主力流入占比(%
* - 买卖比例进度条 * - 净主动买入占比(%,进度条展示)
* *
* 注意:标题由外层 GlassSection 提供 * 注意:标题由外层 GlassSection 提供
*/ */
import React, { memo } from 'react'; import React, { memo } from 'react';
import { Box, VStack, HStack, Text, Progress } from '@chakra-ui/react'; import { Box, VStack, HStack, Text, Progress } from '@chakra-ui/react';
import { formatNetInflow } from './formatters';
import { DEEP_SPACE_THEME as T } from './theme'; import { DEEP_SPACE_THEME as T } from './theme';
export interface MainForceInfoProps { export interface MainForceInfoProps {
/** 主力净流入(亿 */ /** 主力净流入(万元 */
mainNetInflow: number; netInflow: number | null;
/** 机构持仓比例% */ /** 主力净流入量占比% */
institutionHolding: number; mainInflowRatio: number | null;
/** 买入比例% */ /** 净主动买入额占比% */
buyRatio: number; netActiveBuyRatio: number | null;
/** 卖出比例(% */
sellRatio: number;
} }
/** /**
@@ -54,6 +51,32 @@ const MetricRow: React.FC<MetricRowProps> = ({
</HStack> </HStack>
); );
/**
* 格式化主力净流入显示
* 根据数值大小自动选择万/亿单位
*/
const formatNetInflowValue = (value: number | null): string => {
if (value === null || value === undefined) return '--';
const absValue = Math.abs(value);
const sign = value >= 0 ? '+' : '';
if (absValue >= 10000) {
// 超过1亿显示为亿
return `${sign}${(value / 10000).toFixed(2)}亿`;
}
// 否则显示万
return `${sign}${value.toFixed(2)}`;
};
/**
* 格式化百分比显示
*/
const formatPercent = (value: number | null): string => {
if (value === null || value === undefined) return '--';
const sign = value >= 0 ? '+' : '';
return `${sign}${value.toFixed(2)}%`;
};
/** /**
* 主力动态展示组件 * 主力动态展示组件
* *
@@ -61,51 +84,75 @@ const MetricRow: React.FC<MetricRowProps> = ({
* 应由 GlassSection 包装以提供标题 * 应由 GlassSection 包装以提供标题
*/ */
export const MainForceInfo: React.FC<MainForceInfoProps> = memo(({ export const MainForceInfo: React.FC<MainForceInfoProps> = memo(({
mainNetInflow, netInflow,
institutionHolding, mainInflowRatio,
buyRatio, netActiveBuyRatio,
sellRatio,
}) => { }) => {
const inflowColor = mainNetInflow >= 0 ? T.upColor : T.downColor; const inflowColor = (netInflow ?? 0) >= 0 ? T.upColor : T.downColor;
const ratioColor = (mainInflowRatio ?? 0) >= 0 ? T.upColor : T.downColor;
// 净主动买入占比用于进度条,范围可能是 -100 ~ 100需要转换
// 负值表示主动卖出多,正值表示主动买入多
const buyRatioValue = netActiveBuyRatio ?? 0;
// 转换为 0-100 的进度条值50为中点
const progressValue = Math.min(100, Math.max(0, 50 + buyRatioValue / 2));
return ( return (
<VStack align="stretch" spacing={2}> <VStack align="stretch" spacing={2}>
<MetricRow <MetricRow
label="主力净流入" label="主力净流入"
value={formatNetInflow(mainNetInflow)} value={formatNetInflowValue(netInflow)}
valueColor={inflowColor} valueColor={inflowColor}
highlight highlight
/> />
<MetricRow <MetricRow
label="机构持仓" label="流入占比"
value={`${institutionHolding.toFixed(2)}%`} value={formatPercent(mainInflowRatio)}
valueColor={T.purple} valueColor={ratioColor}
highlight highlight
/> />
{/* 买卖比例进度条 */} {/* 净主动买入占比进度条 */}
<Box mt={2}> <Box mt={2}>
<Progress <HStack justify="space-between" mb={1} fontSize="12px">
value={buyRatio} <Text color={T.textMuted}></Text>
size="sm" <Text
sx={{ color={buyRatioValue >= 0 ? T.upColor : T.downColor}
'& > div': { fontWeight="600"
bg: T.upColor, >
boxShadow: T.upGlow, {formatPercent(netActiveBuyRatio)}
},
}}
bg={T.downColor}
borderRadius="full"
h="8px"
/>
<HStack justify="space-between" mt={2} fontSize="13px">
<Text color={T.upColor} fontWeight="600">
{buyRatio}%
</Text>
<Text color={T.downColor} fontWeight="600">
{sellRatio}%
</Text> </Text>
</HStack> </HStack>
<Box position="relative">
<Progress
value={progressValue}
size="sm"
sx={{
'& > div': {
bg: buyRatioValue >= 0 ? T.upColor : T.downColor,
boxShadow: buyRatioValue >= 0 ? T.upGlow : T.downGlow,
transition: 'all 0.3s ease',
},
}}
bg="rgba(255,255,255,0.1)"
borderRadius="full"
h="8px"
/>
{/* 中点标记 */}
<Box
position="absolute"
left="50%"
top="0"
bottom="0"
w="2px"
bg="rgba(255,255,255,0.3)"
transform="translateX(-50%)"
/>
</Box>
<HStack justify="space-between" mt={1} fontSize="11px">
<Text color={T.downColor}></Text>
<Text color={T.upColor}></Text>
</HStack>
</Box> </Box>
</VStack> </VStack>
); );

View File

@@ -19,11 +19,3 @@ export const formatChangePercent = (percent: number): string => {
const sign = percent >= 0 ? '+' : ''; const sign = percent >= 0 ? '+' : '';
return `${sign}${percent.toFixed(2)}%`; return `${sign}${percent.toFixed(2)}%`;
}; };
/**
* 格式化主力净流入显示
*/
export const formatNetInflow = (value: number): string => {
const sign = value >= 0 ? '+' : '';
return `${sign}${value.toFixed(2)}亿`;
};

View File

@@ -42,11 +42,10 @@ const transformQuoteData = (apiData: any, stockCode: string): StockQuoteCardData
week52Low: apiData.week52_low || apiData.week52Low || 0, week52Low: apiData.week52_low || apiData.week52Low || 0,
week52High: apiData.week52_high || apiData.week52High || 0, week52High: apiData.week52_high || apiData.week52High || 0,
// 主力动态 // 主力动态数据来源stock_main_capital_flow 表)
mainNetInflow: apiData.main_net_inflow || apiData.mainNetInflow || 0, netInflow: apiData.net_inflow ?? apiData.netInflow ?? null, // 主力净流入量(万元)
institutionHolding: apiData.institution_holding || apiData.institutionHolding || 0, mainInflowRatio: apiData.main_inflow_ratio ?? apiData.mainInflowRatio ?? null, // 主力净流入量占比(%
buyRatio: apiData.buy_ratio || apiData.buyRatio || 50, netActiveBuyRatio: apiData.net_active_buy_ratio ?? apiData.netActiveBuyRatio ?? null, // 净主动买入额占比(%
sellRatio: apiData.sell_ratio || apiData.sellRatio || 50,
// 更新时间 // 更新时间
updateTime: apiData.update_time || apiData.updateTime || new Date().toLocaleString(), updateTime: apiData.update_time || apiData.updateTime || new Date().toLocaleString(),

View File

@@ -179,10 +179,9 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
{/* 第三列:主力动态 */} {/* 第三列:主力动态 */}
<GlassSection title="主力动态" flex={1}> <GlassSection title="主力动态" flex={1}>
<MainForceInfo <MainForceInfo
mainNetInflow={quoteData.mainNetInflow || 0} netInflow={quoteData.netInflow}
institutionHolding={quoteData.institutionHolding} mainInflowRatio={quoteData.mainInflowRatio}
buyRatio={quoteData.buyRatio} netActiveBuyRatio={quoteData.netActiveBuyRatio}
sellRatio={quoteData.sellRatio}
/> />
</GlassSection> </GlassSection>
</Flex> </Flex>

View File

@@ -33,11 +33,10 @@ export interface StockQuoteCardData {
week52Low: number; // 52周最低 week52Low: number; // 52周最低
week52High: number; // 52周最高 week52High: number; // 52周最高
// 主力动态 // 主力动态数据来源stock_main_capital_flow 表)
mainNetInflow: number; // 主力净流入(亿 netInflow: number | null; // 主力净流入量(万元
institutionHolding: number; // 机构持仓比例(百分比 mainInflowRatio: number | null; // 主力净流入量占比(%
buyRatio: number; // 买入比例(百分比 netActiveBuyRatio: number | null; // 净主动买入额占比(%
sellRatio: number; // 卖出比例(百分比)
// 更新时间 // 更新时间
updateTime: string; // 格式YYYY-MM-DD HH:mm:ss updateTime: string; // 格式YYYY-MM-DD HH:mm:ss