Skip to content

Commit 82e2ea2

Browse files
committed
Update commodity-crisis: added new assets, mobile UI, 120% liquidation logic, and analytics
1 parent 3814ed7 commit 82e2ea2

3 files changed

Lines changed: 165 additions & 25 deletions

File tree

commodity-crisis/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<title>Commodity Crisis - Futures Simulator</title>
7+
<!-- Baidu Analytics -->
8+
<script>
9+
var _hmt = _hmt || [];
10+
(function() {
11+
var hm = document.createElement("script");
12+
hm.src = "https://hm.baidu.com/hm.js?301a591b476305494eb47a7821a42915";
13+
var s = document.getElementsByTagName("script")[0];
14+
s.parentNode.insertBefore(hm, s);
15+
})();
16+
</script>
17+
<!-- Busuanzi -->
18+
<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
719
</head>
820
<body>
921
<div id="root"></div>

commodity-crisis/src/App.tsx

Lines changed: 91 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import React, { useState, useEffect, useRef, useMemo } from 'react';
22
import { 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

77
interface NewsEvent {
88
id: number;
99
text: string;
1010
impact: Record<AssetType, number>;
1111
duration: number;
12+
type: 'NORMAL' | 'INVERSE' | 'NONE'; // 新增新闻类型
1213
}
1314

1415
interface 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

4249
const 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>

commodity-crisis/src/index.css

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,73 @@ body {
2020
padding: 10px;
2121
display: grid;
2222
grid-template-columns: 3fr 1fr;
23-
grid-template-rows: auto 1fr auto;
23+
grid-template-rows: auto 1fr;
2424
gap: 15px;
2525
height: 100vh;
2626
box-sizing: border-box;
2727
}
2828

29+
/* --- Mobile Responsive --- */
30+
@media (max-width: 768px) {
31+
.app-container {
32+
grid-template-columns: 1fr;
33+
height: auto;
34+
overflow-y: auto;
35+
padding: 5px;
36+
gap: 10px;
37+
}
38+
39+
.header {
40+
flex-direction: column;
41+
align-items: flex-start;
42+
gap: 10px;
43+
padding: 10px 15px;
44+
grid-column: 1;
45+
}
46+
47+
.header div[style*="gap: 30px"] {
48+
gap: 15px !important;
49+
font-size: 14px !important;
50+
width: 100%;
51+
justify-content: space-between;
52+
}
53+
54+
.chart-container {
55+
min-height: 300px;
56+
}
57+
58+
.asset-selector {
59+
overflow-x: auto;
60+
padding-bottom: 5px;
61+
-webkit-overflow-scrolling: touch;
62+
white-space: nowrap;
63+
display: flex;
64+
scrollbar-width: none; /* Firefox */
65+
}
66+
67+
.asset-selector::-webkit-scrollbar {
68+
display: none; /* Chrome/Safari */
69+
}
70+
71+
.asset-tab {
72+
flex-shrink: 0;
73+
padding: 6px 12px;
74+
font-size: 13px;
75+
}
76+
77+
.news-panel {
78+
height: 150px;
79+
}
80+
81+
.sidebar {
82+
order: -1; /* 让交易面板在手机端靠前 */
83+
}
84+
85+
.liquidation-overlay h1 {
86+
font-size: 32px !important;
87+
}
88+
}
89+
2990
.header {
3091
grid-column: 1 / span 2;
3192
display: flex;

0 commit comments

Comments
 (0)