@@ -2,13 +2,14 @@ import React, { useState, useEffect, useRef, useMemo } from 'react';
22import { TrendingUp , TrendingDown , Newspaper , Wallet , Activity , AlertTriangle } from 'lucide-react' ;
33
44// --- Constants & Types ---
5- type AssetType = 'OIL' | 'GOLD' | 'WHEAT' ;
5+ type AssetType = 'OIL' | 'GOLD' | 'WHEAT' | 'MA' | 'CU' | 'RU' | 'TBOND' ;
66
77interface NewsEvent {
88 id : number ;
99 text : string ;
1010 impact : Record < AssetType , number > ;
1111 duration : number ;
12+ type : 'NORMAL' | 'INVERSE' | 'NONE' ; // 新增新闻类型
1213}
1314
1415interface Position {
@@ -26,17 +27,23 @@ const ASSET_CONFIG: Record<AssetType, { name: string, basePrice: number, vol: nu
2627 OIL : { name : '原油' , basePrice : 75 , vol : 0.02 , icon : '🛢️' } ,
2728 GOLD : { name : '黄金' , basePrice : 2000 , vol : 0.008 , icon : '✨' } ,
2829 WHEAT : { name : '小麦' , basePrice : 600 , vol : 0.012 , icon : '🌾' } ,
30+ MA : { name : '甲醇' , basePrice : 2500 , vol : 0.025 , icon : '🧪' } ,
31+ CU : { name : '沪铜' , basePrice : 70000 , vol : 0.015 , icon : '🧱' } ,
32+ RU : { name : '橡胶' , basePrice : 13000 , vol : 0.022 , icon : '🌲' } ,
33+ TBOND : { name : '国债' , basePrice : 100 , vol : 0.003 , icon : '📜' } ,
2934} ;
3035
31- const NEWS_POOL : Omit < NewsEvent , 'id' > [ ] = [
32- { text : "中东局势升级,原油供应链面临严重威胁。" , impact : { OIL : 0.15 , GOLD : 0.05 , WHEAT : 0 } , duration : 10 } ,
33- { text : "美联储暗示将在更长时间内维持高利率。" , impact : { OIL : - 0.05 , GOLD : - 0.10 , WHEAT : - 0.02 } , duration : 15 } ,
34- { text : "美国中西部地区报告小麦产量创历史新高。" , impact : { OIL : 0 , GOLD : 0 , WHEAT : - 0.12 } , duration : 12 } ,
35- { text : "全球经济衰退担忧加剧,工业需求疲软。" , impact : { OIL : - 0.12 , GOLD : 0.02 , WHEAT : - 0.05 } , duration : 20 } ,
36- { text : "南非发现特大型金矿,黄金供应预期大增。" , impact : { OIL : 0 , GOLD : - 0.08 , WHEAT : 0 } , duration : 10 } ,
37- { text : "电动汽车电池技术取得突破,长期石油依赖度降低。" , impact : { OIL : - 0.08 , GOLD : 0 , WHEAT : 0 } , duration : 25 } ,
38- { text : "南美遭遇严重旱灾,大宗农作物减产严重。" , impact : { OIL : 0 , GOLD : 0 , WHEAT : 0.18 } , duration : 15 } ,
39- { text : "多国央行近期大幅增持黄金储备。" , impact : { OIL : 0 , GOLD : 0.12 , WHEAT : 0 } , duration : 15 } ,
36+ const NEWS_POOL : Omit < NewsEvent , 'id' | 'type' > [ ] = [
37+ { text : "中东局势升级,原油供应链面临严重威胁。" , impact : { OIL : 0.15 , GOLD : 0.05 , WHEAT : 0 , MA : 0 , CU : 0 , RU : 0 , TBOND : - 0.01 } , duration : 10 } ,
38+ { text : "美联储暗示将在更长时间内维持高利率。" , impact : { OIL : - 0.05 , GOLD : - 0.10 , WHEAT : - 0.02 , MA : - 0.04 , CU : - 0.06 , RU : - 0.03 , TBOND : - 0.05 } , duration : 15 } ,
39+ { text : "南美遭遇严重旱灾,大宗农作物减产严重。" , impact : { OIL : 0 , GOLD : 0 , WHEAT : 0.18 , MA : 0 , CU : 0 , RU : 0 , TBOND : 0 } , duration : 15 } ,
40+ { text : "多国央行近期大幅增持黄金储备。" , impact : { OIL : 0 , GOLD : 0.12 , WHEAT : 0 , MA : 0 , CU : 0 , RU : 0 , TBOND : 0.01 } , duration : 15 } ,
41+ { text : "煤炭价格大幅波动,甲醇生产成本支撑增强。" , impact : { OIL : 0 , GOLD : 0 , WHEAT : 0 , MA : 0.12 , CU : 0 , RU : 0 , TBOND : 0 } , duration : 10 } ,
42+ { text : "全球最大铜矿罢工,供应中断忧虑加剧。" , impact : { OIL : 0 , GOLD : 0 , WHEAT : 0 , MA : 0 , CU : 0.18 , RU : 0 , TBOND : 0 } , duration : 15 } ,
43+ { text : "产胶国降雨过多,橡胶收割工作大面积停滞。" , impact : { OIL : 0 , GOLD : 0 , WHEAT : 0 , MA : 0 , CU : 0 , RU : 0.15 , TBOND : 0 } , duration : 12 } ,
44+ { text : "避险情绪升温,资金疯狂涌入长久期国债。" , impact : { OIL : - 0.03 , GOLD : 0.08 , WHEAT : 0 , MA : - 0.02 , CU : - 0.04 , RU : - 0.03 , TBOND : 0.06 } , duration : 15 } ,
45+ { text : "市场传闻:主要经济体计划联合释放原油储备。" , impact : { OIL : - 0.10 , GOLD : - 0.02 , WHEAT : 0 , MA : - 0.03 , CU : - 0.02 , RU : 0 , TBOND : 0.01 } , duration : 10 } ,
46+ { text : "房地产业复苏迹象明显,工业金属需求看涨。" , impact : { OIL : 0.04 , GOLD : - 0.02 , WHEAT : 0 , MA : 0.06 , CU : 0.12 , RU : 0.08 , TBOND : - 0.03 } , duration : 15 } ,
4047] ;
4148
4249const App : React . FC = ( ) => {
@@ -46,20 +53,31 @@ const App: React.FC = () => {
4653 OIL : ASSET_CONFIG . OIL . basePrice ,
4754 GOLD : ASSET_CONFIG . GOLD . basePrice ,
4855 WHEAT : ASSET_CONFIG . WHEAT . basePrice ,
56+ MA : ASSET_CONFIG . MA . basePrice ,
57+ CU : ASSET_CONFIG . CU . basePrice ,
58+ RU : ASSET_CONFIG . RU . basePrice ,
59+ TBOND : ASSET_CONFIG . TBOND . basePrice ,
4960 } ) ;
5061 const [ priceHistory , setPriceHistory ] = useState < Record < AssetType , number [ ] > > ( {
5162 OIL : [ ASSET_CONFIG . OIL . basePrice ] ,
5263 GOLD : [ ASSET_CONFIG . GOLD . basePrice ] ,
5364 WHEAT : [ ASSET_CONFIG . WHEAT . basePrice ] ,
65+ MA : [ ASSET_CONFIG . MA . basePrice ] ,
66+ CU : [ ASSET_CONFIG . CU . basePrice ] ,
67+ RU : [ ASSET_CONFIG . RU . basePrice ] ,
68+ TBOND : [ ASSET_CONFIG . TBOND . basePrice ] ,
5469 } ) ;
5570 const [ activeAsset , setActiveAsset ] = useState < AssetType > ( 'OIL' ) ;
5671 const [ position , setPosition ] = useState < Position | null > ( null ) ;
5772 const [ news , setNews ] = useState < NewsEvent [ ] > ( [ ] ) ;
5873 const [ ticks , setTicks ] = useState ( 0 ) ;
5974 const [ isLiquidated , setIsLiquated ] = useState ( false ) ;
75+ const [ utilizationRate , setUtilizationRate ] = useState ( 0.8 ) ; // 默认80%使用率
6076
6177 // --- Refs ---
62- const activeImpactsRef = useRef < Record < AssetType , number > > ( { OIL : 0 , GOLD : 0 , WHEAT : 0 } ) ;
78+ const activeImpactsRef = useRef < Record < AssetType , number > > ( {
79+ OIL : 0 , GOLD : 0 , WHEAT : 0 , MA : 0 , CU : 0 , RU : 0 , TBOND : 0
80+ } ) ;
6381
6482 // --- Calculations ---
6583 const unrealizedPnL = useMemo ( ( ) => {
@@ -69,7 +87,13 @@ const App: React.FC = () => {
6987 } , [ position , prices ] ) ;
7088
7189 const equity = balance + unrealizedPnL ;
72- const marginUsed = position ? ( position . entryPrice * position . size ) : 0 ;
90+
91+ // 实时资金使用率计算:(开仓占用的保证金 / 当前净资产)
92+ const currentUtilization = useMemo ( ( ) => {
93+ if ( ! position ) return 0 ;
94+ const initialMargin = position . entryPrice * position . size ;
95+ return initialMargin / Math . max ( 0.001 , equity ) ; // 避免除以0
96+ } , [ position , equity ] ) ;
7397
7498 // --- Market Engine ---
7599 useEffect ( ( ) => {
@@ -86,11 +110,11 @@ const App: React.FC = () => {
86110 const randomWalk = ( Math . random ( ) - 0.5 ) * 2 * config . vol ;
87111 const newsImpact = activeImpactsRef . current [ asset ] ;
88112
89- // Apply movement
113+ // 模拟市场多变性:小概率新闻被其他隐性因素抵消或反转
90114 nextPrices [ asset ] = prevPrices [ asset ] * ( 1 + randomWalk + newsImpact ) ;
91115
92- // Slowly decay news impact
93- activeImpactsRef . current [ asset ] *= 0.9 ;
116+ // 缓慢衰减新闻影响
117+ activeImpactsRef . current [ asset ] *= 0.85 ;
94118 } ) ;
95119
96120 return nextPrices ;
@@ -108,30 +132,39 @@ const App: React.FC = () => {
108132 // Spawn News
109133 if ( ticks > 0 && ticks % NEWS_INTERVAL_TICKS === 0 ) {
110134 const randomNews = NEWS_POOL [ Math . floor ( Math . random ( ) * NEWS_POOL . length ) ] ;
111- const newEvent = { ...randomNews , id : Date . now ( ) } ;
135+
136+ // 随机分配新闻质量:NORMAL(正常), INVERSE(反转), NONE(失效)
137+ const rand = Math . random ( ) ;
138+ const type = rand > 0.85 ? 'INVERSE' : ( rand > 0.7 ? 'NONE' : 'NORMAL' ) ;
139+
140+ const newEvent = { ...randomNews , id : Date . now ( ) , type } ;
112141 setNews ( prev => [ newEvent , ...prev ] . slice ( 0 , 5 ) ) ;
113142
114- // Apply immediate impact
143+ // Apply immediate impact based on type
115144 ( Object . keys ( newEvent . impact ) as AssetType [ ] ) . forEach ( asset => {
116- activeImpactsRef . current [ asset ] += newEvent . impact [ asset ] / 5 ; // Spread impact
145+ let factor = 1 ;
146+ if ( type === 'INVERSE' ) factor = - 0.6 ; // 反着来,且程度稍轻
147+ if ( type === 'NONE' ) factor = 0.05 ; // 几乎没反应
148+
149+ activeImpactsRef . current [ asset ] += ( newEvent . impact [ asset ] * factor ) / 4 ;
117150 } ) ;
118151 }
119152
120- // Check Liquidation
121- if ( equity <= 0 ) {
122- setIsLiquated ( true ) ;
153+ // Check Liquidation: 资金使用率超过 120% 爆仓
154+ if ( currentUtilization > 1.2 ) {
155+ setIsLiquidated ( true ) ;
123156 clearInterval ( timer ) ;
124157 }
125158 } , TICK_MS ) ;
126159
127160 return ( ) => clearInterval ( timer ) ;
128- } , [ ticks , prices , isLiquidated , equity ] ) ;
161+ } , [ ticks , prices , isLiquidated , currentUtilization ] ) ;
129162
130163 // --- Trading Actions ---
131164 const openPosition = ( dir : 1 | - 1 ) => {
132165 if ( position ) return ;
133166 const currentPrice = prices [ activeAsset ] ;
134- const size = ( balance * 0.8 ) / currentPrice ; // Use 80% of balance for margin
167+ const size = ( balance * utilizationRate ) / currentPrice ; // 使用自定义使用率
135168 setPosition ( {
136169 asset : activeAsset ,
137170 direction : dir ,
@@ -170,7 +203,15 @@ const App: React.FC = () => {
170203 < header className = "header" >
171204 < div style = { { display : 'flex' , alignItems : 'center' , gap : '15px' } } >
172205 < Activity size = { 32 } color = "#ffd700" />
173- < h1 style = { { margin : 0 , fontSize : '24px' } } > 期货风云</ h1 >
206+ < div >
207+ < h1 style = { { margin : 0 , fontSize : '24px' } } > 期货风云</ h1 >
208+ < div style = { { display : 'flex' , gap : '10px' , marginTop : '4px' } } >
209+ < a href = "/" style = { { color : '#aaa' , fontSize : '12px' , textDecoration : 'none' , border : '1px solid #444' , padding : '2px 8px' , borderRadius : '4px' } } > 返回博客主页</ a >
210+ < span id = "busuanzi_container_page_pv" style = { { color : '#666' , fontSize : '12px' , display : 'none' } } >
211+ 访客: < span id = "busuanzi_value_page_pv" > </ span >
212+ </ span >
213+ </ div >
214+ </ div >
174215 </ div >
175216 < div style = { { display : 'flex' , gap : '30px' , fontSize : '18px' } } >
176217 < div style = { { color : '#888' } } > 净资产: < span style = { { color : equity > 10000 ? '#26a69a' : '#ef5350' } } > ${ equity . toLocaleString ( undefined , { minimumFractionDigits : 2 , maximumFractionDigits : 2 } ) } </ span > </ div >
@@ -227,18 +268,44 @@ const App: React.FC = () => {
227268 </ div >
228269
229270 < div style = { { fontSize : '14px' , color : '#888' , marginBottom : '10px' } } > 杠杆倍数: { LEVERAGE } x</ div >
271+
272+ < div style = { { marginBottom : '15px' } } >
273+ < div style = { { fontSize : '12px' , color : '#888' , marginBottom : '8px' } } > 资金使用率: { ( utilizationRate * 100 ) . toFixed ( 0 ) } %</ div >
274+ < div style = { { display : 'flex' , gap : '4px' , flexWrap : 'wrap' } } >
275+ { [ 0.1 , 0.3 , 0.5 , 0.8 , 1.0 ] . map ( rate => (
276+ < button
277+ key = { rate }
278+ onClick = { ( ) => setUtilizationRate ( rate ) }
279+ style = { {
280+ padding : '4px 6px' ,
281+ fontSize : '11px' ,
282+ background : utilizationRate === rate ? '#444' : '#222' ,
283+ border : utilizationRate === rate ? '1px solid #ffd700' : '1px solid #444' ,
284+ borderRadius : '4px' ,
285+ color : '#fff' ,
286+ cursor : 'pointer' ,
287+ flex : '1 0 30%'
288+ } }
289+ >
290+ { rate * 100 } %
291+ </ button >
292+ ) ) }
293+ </ div >
294+ </ div >
230295
231296 < button
232297 className = "btn btn-long"
233298 onClick = { ( ) => openPosition ( 1 ) }
234299 disabled = { ! ! position }
300+ style = { { width : '100%' , marginBottom : '10px' } }
235301 >
236302 买入 / 做多
237303 </ button >
238304 < button
239305 className = "btn btn-short"
240306 onClick = { ( ) => openPosition ( - 1 ) }
241307 disabled = { ! ! position }
308+ style = { { width : '100%' } }
242309 >
243310 卖出 / 做空
244311 </ button >
0 commit comments