lfqian commited on
Commit
12ede86
·
1 Parent(s): cbdf812

Update trading fees to 0.06% and add 0.1% slippage fee

Browse files

- Updated trading fee from 0.05% to 0.06% in strategies.js and perf.js
- Added 0.1% slippage: buy at price×1.001, sell at price×0.999
- Added Updates modal in Footer with changelog display

src/components/FooterOpen.vue CHANGED
@@ -17,6 +17,31 @@
17
  </div>
18
  </div>
19
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  </footer>
21
  </template>
22
 
@@ -25,6 +50,7 @@ export default {
25
  name: "PageFooter",
26
  data() {
27
  return {
 
28
  presentedBy: [
29
  { name: "DeepKin", src: new URL("../assets/images/companies_images/deepkin_logo.png", import.meta.url).href },
30
  { name: "The Fin AI", src: new URL("../assets/images/companies_images/logofinai.png", import.meta.url).href },
@@ -39,6 +65,15 @@ export default {
39
  { name: "Université de Montréal", src: new URL("../assets/images/companies_images/montreal.png", import.meta.url).href },
40
  { name: "Georgia Institute of Technology", src: new URL("../assets/images/companies_images/georgia.png", import.meta.url).href },
41
  ],
 
 
 
 
 
 
 
 
 
42
  };
43
  },
44
  };
@@ -108,6 +143,126 @@ export default {
108
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.2));
109
  }
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  /* Responsive */
112
  @media (max-width: 768px) {
113
  .page-footer {
@@ -119,6 +274,12 @@ export default {
119
  width: 60px;
120
  height: 20px;
121
  }
 
 
 
 
 
 
122
  }
123
 
124
  @media (max-width: 480px) {
@@ -134,5 +295,9 @@ export default {
134
  width: 52px;
135
  height: 18px;
136
  }
 
 
 
 
137
  }
138
  </style>
 
17
  </div>
18
  </div>
19
  </div>
20
+
21
+ <!-- Bottom right: copyright and updates -->
22
+ <div class="footer-bottom-right">
23
+ <span class="copyright">© 2025 Agent Market Arena</span>
24
+ <span class="divider">|</span>
25
+ <span class="updates-link" @click="showUpdates = true">Updates</span>
26
+ </div>
27
+
28
+ <!-- Updates Modal -->
29
+ <div v-if="showUpdates" class="modal-overlay" @click.self="showUpdates = false">
30
+ <div class="modal-content">
31
+ <div class="modal-header">
32
+ <span class="modal-title">📋 Major Updates</span>
33
+ <span class="modal-close" @click="showUpdates = false">✕</span>
34
+ </div>
35
+ <div class="modal-body">
36
+ <div v-for="(update, idx) in updates" :key="idx" class="update-item">
37
+ <div class="update-date">{{ update.date }}</div>
38
+ <ul class="update-list">
39
+ <li v-for="(item, i) in update.items" :key="i" v-html="item"></li>
40
+ </ul>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
  </footer>
46
  </template>
47
 
 
50
  name: "PageFooter",
51
  data() {
52
  return {
53
+ showUpdates: false,
54
  presentedBy: [
55
  { name: "DeepKin", src: new URL("../assets/images/companies_images/deepkin_logo.png", import.meta.url).href },
56
  { name: "The Fin AI", src: new URL("../assets/images/companies_images/logofinai.png", import.meta.url).href },
 
65
  { name: "Université de Montréal", src: new URL("../assets/images/companies_images/montreal.png", import.meta.url).href },
66
  { name: "Georgia Institute of Technology", src: new URL("../assets/images/companies_images/georgia.png", import.meta.url).href },
67
  ],
68
+ updates: [
69
+ {
70
+ date: '2025-12-05',
71
+ items: [
72
+ '<strong>Trading Fee:</strong> Updated from 0.05% to <strong>0.06%</strong>',
73
+ '<strong>Slippage Fee:</strong> Added <strong>0.1%</strong> slippage<br/>&nbsp;&nbsp;• Buy orders: price × 1.001<br/>&nbsp;&nbsp;• Sell orders: price × 0.999',
74
+ ]
75
+ },
76
+ ],
77
  };
78
  },
79
  };
 
143
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.2));
144
  }
145
 
146
+ /* Bottom right section */
147
+ .footer-bottom-right {
148
+ position: absolute;
149
+ bottom: 0.5rem;
150
+ right: 1rem;
151
+ display: flex;
152
+ align-items: center;
153
+ gap: 0.5rem;
154
+ font-size: 0.75rem;
155
+ color: #6b7280;
156
+ }
157
+
158
+ .copyright {
159
+ font-weight: 500;
160
+ }
161
+
162
+ .divider {
163
+ color: #d1d5db;
164
+ }
165
+
166
+ .updates-link {
167
+ cursor: pointer;
168
+ color: #4f46e5;
169
+ font-weight: 600;
170
+ transition: color 0.2s ease;
171
+ }
172
+
173
+ .updates-link:hover {
174
+ color: #3730a3;
175
+ text-decoration: underline;
176
+ }
177
+
178
+ /* Modal styles */
179
+ .modal-overlay {
180
+ position: fixed;
181
+ top: 0;
182
+ left: 0;
183
+ right: 0;
184
+ bottom: 0;
185
+ background: rgba(0, 0, 0, 0.5);
186
+ display: flex;
187
+ align-items: center;
188
+ justify-content: center;
189
+ z-index: 1000;
190
+ }
191
+
192
+ .modal-content {
193
+ background: #fff;
194
+ border-radius: 12px;
195
+ width: 90%;
196
+ max-width: 480px;
197
+ max-height: 80vh;
198
+ overflow: hidden;
199
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
200
+ }
201
+
202
+ .modal-header {
203
+ display: flex;
204
+ justify-content: space-between;
205
+ align-items: center;
206
+ padding: 1rem 1.25rem;
207
+ background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
208
+ color: #fff;
209
+ }
210
+
211
+ .modal-title {
212
+ font-size: 1.1rem;
213
+ font-weight: 700;
214
+ }
215
+
216
+ .modal-close {
217
+ cursor: pointer;
218
+ font-size: 1.25rem;
219
+ opacity: 0.8;
220
+ transition: opacity 0.2s;
221
+ }
222
+
223
+ .modal-close:hover {
224
+ opacity: 1;
225
+ }
226
+
227
+ .modal-body {
228
+ padding: 1.25rem;
229
+ max-height: 60vh;
230
+ overflow-y: auto;
231
+ }
232
+
233
+ .update-item {
234
+ margin-bottom: 1.25rem;
235
+ }
236
+
237
+ .update-item:last-child {
238
+ margin-bottom: 0;
239
+ }
240
+
241
+ .update-date {
242
+ font-size: 0.9rem;
243
+ font-weight: 700;
244
+ color: #4f46e5;
245
+ margin-bottom: 0.5rem;
246
+ padding-bottom: 0.25rem;
247
+ border-bottom: 2px solid #e5e7eb;
248
+ }
249
+
250
+ .update-list {
251
+ margin: 0;
252
+ padding-left: 1.25rem;
253
+ color: #374151;
254
+ font-size: 0.875rem;
255
+ line-height: 1.6;
256
+ }
257
+
258
+ .update-list li {
259
+ margin-bottom: 0.5rem;
260
+ }
261
+
262
+ .update-list li:last-child {
263
+ margin-bottom: 0;
264
+ }
265
+
266
  /* Responsive */
267
  @media (max-width: 768px) {
268
  .page-footer {
 
274
  width: 60px;
275
  height: 20px;
276
  }
277
+
278
+ .footer-bottom-right {
279
+ position: static;
280
+ margin-top: 0.5rem;
281
+ justify-content: center;
282
+ }
283
  }
284
 
285
  @media (max-width: 480px) {
 
295
  width: 52px;
296
  height: 18px;
297
  }
298
+
299
+ .footer-bottom-right {
300
+ font-size: 0.7rem;
301
+ }
302
  }
303
  </style>
src/lib/perf.js CHANGED
@@ -6,23 +6,29 @@ function sanitizeRows(rows) {
6
  return list
7
  }
8
 
9
- export function computeBuyHoldEquity(rows, initial = 100000, fee = 0.0005) {
 
 
10
  const data = sanitizeRows(rows)
11
  if (data.length === 0) return []
12
- const firstPrice = Number(data[0].price)
13
  const equity = []
14
  // open with fee
15
  let capital = initial * (1 - fee)
16
  for (let i = 0; i < data.length; i++) {
17
  const price = Number(data[i].price)
18
  let value = firstPrice > 0 ? capital * (price / firstPrice) : capital
19
- if (i === data.length - 1) value = value * (1 - fee) // closing fee
 
 
 
 
20
  equity.push(Math.max(0, value))
21
  }
22
  return equity
23
  }
24
 
25
- export function computeStrategyEquity(rows, initial = 100000, fee = 0.0005, strategy = 'long_short', tradingMode = 'normal') {
26
  const data = sanitizeRows(rows)
27
  if (data.length === 0) return []
28
  let capital = initial
@@ -38,49 +44,69 @@ export function computeStrategyEquity(rows, initial = 100000, fee = 0.0005, stra
38
  const action = String(data[i].recommended_action || 'HOLD').toUpperCase()
39
  if (tradingMode === 'normal') {
40
  if (action === 'BUY') {
41
- if (position === 'FLAT') { position = 'LONG'; entryPrice = price; capital *= (1 - fee); dailyCapital = capital }
 
 
42
  else if (position === 'SHORT') {
43
- const ret = entryPrice ? (entryPrice - price) / entryPrice : 0
 
44
  capital *= (1 + ret) * (1 - fee)
45
- position = 'LONG'; entryPrice = price; capital *= (1 - fee); dailyCapital = capital
46
  }
47
  } else if (action === 'SELL') {
48
  if (position === 'LONG') {
49
- const ret = entryPrice ? (price - entryPrice) / entryPrice : 0
 
50
  capital *= (1 + ret) * (1 - fee); position = 'FLAT'; entryPrice = 0; dailyCapital = capital
51
  } else if (position === 'FLAT' && strategy === 'long_short') {
52
- position = 'SHORT'; entryPrice = price; capital *= (1 - fee); dailyCapital = capital
53
  }
54
  }
55
  // HOLD keeps position
56
  } else { // aggressive: HOLD forces flat, BUY/SELL switch directly
57
  if (action === 'HOLD') {
58
  if (position === 'LONG') {
59
- const ret = entryPrice ? (price - entryPrice) / entryPrice : 0
 
60
  capital *= (1 + ret) * (1 - fee); position = 'FLAT'; entryPrice = 0; dailyCapital = capital
61
  } else if (position === 'SHORT') {
62
- const ret = entryPrice ? (entryPrice - price) / entryPrice : 0
 
63
  capital *= (1 + ret) * (1 - fee); position = 'FLAT'; entryPrice = 0; dailyCapital = capital
64
  }
65
  } else if (action === 'BUY') {
66
  if (position === 'SHORT') {
67
- const ret = entryPrice ? (entryPrice - price) / entryPrice : 0
 
68
  capital *= (1 + ret) * (1 - fee); position = 'FLAT'; entryPrice = 0; dailyCapital = capital
69
  }
70
- if (position === 'FLAT') { position = 'LONG'; entryPrice = price; capital *= (1 - fee); dailyCapital = capital }
 
 
71
  } else if (action === 'SELL') {
72
  if (position === 'LONG') {
73
- const ret = entryPrice ? (price - entryPrice) / entryPrice : 0
 
74
  capital *= (1 + ret) * (1 - fee); position = 'FLAT'; entryPrice = 0; dailyCapital = capital
75
  }
76
- if (position === 'FLAT' && strategy === 'long_short') { position = 'SHORT'; entryPrice = price; capital *= (1 - fee); dailyCapital = capital }
 
 
77
  }
78
  }
79
  series.push(dailyCapital)
80
  if (i === data.length - 1 && position !== 'FLAT') {
81
- // force close last day
82
- if (position === 'LONG') { const ret = entryPrice ? (data[i].price - entryPrice) / entryPrice : 0; capital *= (1 + ret) * (1 - fee) }
83
- else if (position === 'SHORT') { const ret = entryPrice ? (entryPrice - data[i].price) / entryPrice : 0; capital *= (1 + ret) * (1 - fee) }
 
 
 
 
 
 
 
 
84
  series[series.length - 1] = capital
85
  position = 'FLAT'; entryPrice = 0
86
  }
 
6
  return list
7
  }
8
 
9
+ const SLIPPAGE = 0.001 // 0.1% slippage
10
+
11
+ export function computeBuyHoldEquity(rows, initial = 100000, fee = 0.0006) {
12
  const data = sanitizeRows(rows)
13
  if (data.length === 0) return []
14
+ const firstPrice = Number(data[0].price) * (1 + SLIPPAGE) // buy at higher price
15
  const equity = []
16
  // open with fee
17
  let capital = initial * (1 - fee)
18
  for (let i = 0; i < data.length; i++) {
19
  const price = Number(data[i].price)
20
  let value = firstPrice > 0 ? capital * (price / firstPrice) : capital
21
+ if (i === data.length - 1) {
22
+ // sell at lower price + closing fee
23
+ const sellPrice = price * (1 - SLIPPAGE)
24
+ value = firstPrice > 0 ? capital * (sellPrice / firstPrice) * (1 - fee) : capital
25
+ }
26
  equity.push(Math.max(0, value))
27
  }
28
  return equity
29
  }
30
 
31
+ export function computeStrategyEquity(rows, initial = 100000, fee = 0.0006, strategy = 'long_short', tradingMode = 'normal') {
32
  const data = sanitizeRows(rows)
33
  if (data.length === 0) return []
34
  let capital = initial
 
44
  const action = String(data[i].recommended_action || 'HOLD').toUpperCase()
45
  if (tradingMode === 'normal') {
46
  if (action === 'BUY') {
47
+ if (position === 'FLAT') {
48
+ position = 'LONG'; entryPrice = price * (1 + SLIPPAGE); capital *= (1 - fee); dailyCapital = capital
49
+ }
50
  else if (position === 'SHORT') {
51
+ const buyPrice = price * (1 + SLIPPAGE) // buy to close short at higher price
52
+ const ret = entryPrice ? (entryPrice - buyPrice) / entryPrice : 0
53
  capital *= (1 + ret) * (1 - fee)
54
+ position = 'LONG'; entryPrice = price * (1 + SLIPPAGE); capital *= (1 - fee); dailyCapital = capital
55
  }
56
  } else if (action === 'SELL') {
57
  if (position === 'LONG') {
58
+ const sellPrice = price * (1 - SLIPPAGE) // sell at lower price
59
+ const ret = entryPrice ? (sellPrice - entryPrice) / entryPrice : 0
60
  capital *= (1 + ret) * (1 - fee); position = 'FLAT'; entryPrice = 0; dailyCapital = capital
61
  } else if (position === 'FLAT' && strategy === 'long_short') {
62
+ position = 'SHORT'; entryPrice = price * (1 - SLIPPAGE); capital *= (1 - fee); dailyCapital = capital
63
  }
64
  }
65
  // HOLD keeps position
66
  } else { // aggressive: HOLD forces flat, BUY/SELL switch directly
67
  if (action === 'HOLD') {
68
  if (position === 'LONG') {
69
+ const sellPrice = price * (1 - SLIPPAGE)
70
+ const ret = entryPrice ? (sellPrice - entryPrice) / entryPrice : 0
71
  capital *= (1 + ret) * (1 - fee); position = 'FLAT'; entryPrice = 0; dailyCapital = capital
72
  } else if (position === 'SHORT') {
73
+ const buyPrice = price * (1 + SLIPPAGE)
74
+ const ret = entryPrice ? (entryPrice - buyPrice) / entryPrice : 0
75
  capital *= (1 + ret) * (1 - fee); position = 'FLAT'; entryPrice = 0; dailyCapital = capital
76
  }
77
  } else if (action === 'BUY') {
78
  if (position === 'SHORT') {
79
+ const buyPrice = price * (1 + SLIPPAGE)
80
+ const ret = entryPrice ? (entryPrice - buyPrice) / entryPrice : 0
81
  capital *= (1 + ret) * (1 - fee); position = 'FLAT'; entryPrice = 0; dailyCapital = capital
82
  }
83
+ if (position === 'FLAT') {
84
+ position = 'LONG'; entryPrice = price * (1 + SLIPPAGE); capital *= (1 - fee); dailyCapital = capital
85
+ }
86
  } else if (action === 'SELL') {
87
  if (position === 'LONG') {
88
+ const sellPrice = price * (1 - SLIPPAGE)
89
+ const ret = entryPrice ? (sellPrice - entryPrice) / entryPrice : 0
90
  capital *= (1 + ret) * (1 - fee); position = 'FLAT'; entryPrice = 0; dailyCapital = capital
91
  }
92
+ if (position === 'FLAT' && strategy === 'long_short') {
93
+ position = 'SHORT'; entryPrice = price * (1 - SLIPPAGE); capital *= (1 - fee); dailyCapital = capital
94
+ }
95
  }
96
  }
97
  series.push(dailyCapital)
98
  if (i === data.length - 1 && position !== 'FLAT') {
99
+ // force close last day with slippage
100
+ if (position === 'LONG') {
101
+ const sellPrice = data[i].price * (1 - SLIPPAGE)
102
+ const ret = entryPrice ? (sellPrice - entryPrice) / entryPrice : 0
103
+ capital *= (1 + ret) * (1 - fee)
104
+ }
105
+ else if (position === 'SHORT') {
106
+ const buyPrice = data[i].price * (1 + SLIPPAGE)
107
+ const ret = entryPrice ? (entryPrice - buyPrice) / entryPrice : 0
108
+ capital *= (1 + ret) * (1 - fee)
109
+ }
110
  series[series.length - 1] = capital
111
  position = 'FLAT'; entryPrice = 0
112
  }
src/lib/strategies.js CHANGED
@@ -3,10 +3,10 @@
3
  // -------------------- public config --------------------
4
 
5
  export const STRATEGIES = [
6
- { id: 'long_only', label: 'Aggressive Long Only', strategy: 'long_only', tradingMode: 'aggressive', fee: 0.0005 },
7
- { id: 'conservative_long', label: 'Conservative Long', strategy: 'long_only', tradingMode: 'conservative', fee: 0.0002 },
8
- { id: 'long_short', label: 'Long/Short', strategy: 'long_short', tradingMode: 'aggressive', fee: 0.0007 },
9
- { id: 'neutral', label: 'Market Neutral', strategy: 'market_neutral', tradingMode: 'neutral', fee: 0.0005 },
10
  ];
11
 
12
  // Simple pleasant palette (baseline uses its own dashed gray)
 
3
  // -------------------- public config --------------------
4
 
5
  export const STRATEGIES = [
6
+ { id: 'long_only', label: 'Aggressive Long Only', strategy: 'long_only', tradingMode: 'aggressive', fee: 0.0006 },
7
+ { id: 'conservative_long', label: 'Conservative Long', strategy: 'long_only', tradingMode: 'conservative', fee: 0.0006 },
8
+ { id: 'long_short', label: 'Long/Short', strategy: 'long_short', tradingMode: 'aggressive', fee: 0.0006 },
9
+ { id: 'neutral', label: 'Market Neutral', strategy: 'market_neutral', tradingMode: 'neutral', fee: 0.0006 },
10
  ];
11
 
12
  // Simple pleasant palette (baseline uses its own dashed gray)