增加主力数据
This commit is contained in:
28
app.py
28
app.py
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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)}亿`;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user