-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcode_artifact.html
More file actions
921 lines (813 loc) · 47.1 KB
/
Copy pathcode_artifact.html
File metadata and controls
921 lines (813 loc) · 47.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>암호화폐 평단가 및 레버리지 계산기 (Leveraged Average Price Calculator)</title>
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'Noto Sans KR', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
}
}
}
</script>
<style>
/* 미니멀 라인 아트 스타일 커스텀 테두리 및 그림자 효과 */
.line-border {
border: 2px solid #171717;
}
.line-shadow {
box-shadow: 4px 4px 0px 0px #171717;
}
.line-shadow-sm {
box-shadow: 2px 2px 0px 0px #171717;
}
.line-shadow:active {
transform: translate(2px, 2px);
box-shadow: 2px 2px 0px 0px #171717;
}
.line-shadow-sm:active {
transform: translate(1px, 1px);
box-shadow: 1px 1px 0px 0px #171717;
}
/* 입력 폼 포커스 스타일 */
input:focus {
outline: none;
border-color: #171717;
background-color: #fbfbfb;
}
/* 스크롤바 커스텀 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #171717;
border-radius: 3px;
}
</style>
</head>
<body
class="bg-neutral-50 text-neutral-900 font-sans min-h-screen flex flex-col justify-between selection:bg-neutral-900 selection:text-white">
<!-- 헤더 영역 -->
<header class="border-b-2 border-neutral-900 bg-white sticky top-0 z-10">
<div class="max-w-5xl mx-auto px-4 py-4 flex flex-col sm:flex-row justify-between items-center gap-4">
<div class="flex items-center gap-3">
<!-- 라인 아트 스타일의 로고 SVG -->
<svg class="w-8 h-8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
<path
d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83">
</path>
</svg>
<div>
<h1 class="text-xl font-extrabold tracking-tight">느좋단타용 계산기</h1>
<p class="text-xs text-neutral-500 font-mono">삼순동순 기술</p>
</div>
</div>
<!-- 세계 시계 위젯 패널 -->
<div class="world-clock-container flex items-center gap-4 text-xs font-mono">
<div id="global-date" class="text-neutral-500 font-bold hidden md:block">----.--.-- (---)</div>
<div class="clocks-wrapper flex items-center gap-2 sm:gap-4 flex-wrap sm:flex-nowrap">
<div class="clock-item flex items-center gap-2 bg-neutral-50 px-2 py-1 line-border cursor-help"
title="KRX Trading Hours: 09:00 - 15:30">
<span class="clock-label text-[10px] text-neutral-500 font-bold">🇰🇷 SEL</span>
<span id="seoul-time" class="clock-time font-bold text-neutral-900">--:--</span>
</div>
<div class="clock-item flex items-center gap-2 bg-neutral-50 px-2 py-1 line-border cursor-help"
title="LSE Trading Hours: 08:00 - 16:30">
<span class="clock-label text-[10px] text-neutral-500 font-bold">🇬🇧 LON</span>
<span id="london-time" class="clock-time font-bold text-neutral-900">--:--</span>
</div>
<div class="clock-item flex items-center gap-2 bg-neutral-50 px-2 py-1 line-border cursor-help"
title="NYSE Trading Hours: 09:30 - 16:00">
<span class="clock-label text-[10px] text-neutral-500 font-bold">🇺🇸 NYC</span>
<span id="newyork-time" class="clock-time font-bold text-neutral-900">--:--</span>
</div>
</div>
</div>
</div>
</header>
<!-- 메인 콘텐츠 -->
<main class="max-w-5xl w-full mx-auto px-4 py-8 flex-grow">
<!-- 상단 화폐 단위 설정 및 선택된 화폐 정보 -->
<div
class="mb-8 flex flex-col md:flex-row justify-between items-start md:items-center gap-4 border-b-2 border-dashed border-neutral-300 pb-6">
<div>
<span class="text-xs uppercase font-mono tracking-wider text-neutral-500">현재 자산</span>
<div class="flex items-baseline gap-2">
<h2 id="current-asset-title" class="text-3xl font-black">비트코인 (BTC)</h2>
<span class="text-lg font-mono text-neutral-500">(USD, $)</span>
</div>
</div>
<div class="flex items-center gap-2 flex-wrap sm:flex-nowrap">
<div class="text-xs font-mono text-neutral-400 bg-white p-2 line-border h-[34px] flex items-center">
<span>기본 통화 설정: 미국 달러 (USD)</span>
</div>
<div class="flex items-center gap-2 flex-wrap sm:flex-nowrap">
<div class="flex items-center gap-1 bg-neutral-100 px-2 py-1 line-border">
<span class="text-[10px] font-mono text-neutral-500 uppercase font-bold px-1">Asset</span>
<select id="asset-select" onchange="handleAssetSelectChange()"
class="bg-transparent text-xs font-mono font-bold focus:outline-none uppercase text-center cursor-pointer pr-2">
<option value="BTC" data-name="비트코인">BTC (비트코인)</option>
<option value="ETH" data-name="이더리움">ETH (이더리움)</option>
<option value="XRP" data-name="리플">XRP (리플)</option>
<option value="SOL" data-name="솔라나">SOL (솔라나)</option>
<option value="CUSTOM" data-name="사용자 정의">CUSTOM (직접 입력)</option>
</select>
</div>
<!-- 사용자 정의(CUSTOM) 선택 시 보여주는 수동 입력 필드 -->
<div id="custom-asset-inputs" class="hidden flex items-center gap-2">
<div class="flex items-center gap-1 bg-neutral-100 px-2 py-1 line-border">
<span class="text-[10px] font-mono text-neutral-500 uppercase font-bold">Symbol</span>
<input type="text" id="asset-symbol" value="BTC" oninput="updateAssetSettings()"
class="w-14 bg-transparent text-xs font-mono font-bold focus:outline-none uppercase text-center">
</div>
<div class="flex items-center gap-1 bg-neutral-100 px-2 py-1 line-border">
<span class="text-[10px] font-mono text-neutral-500 uppercase font-bold">Name</span>
<input type="text" id="asset-name" value="비트코인" oninput="updateAssetSettings()"
class="w-20 bg-transparent text-xs font-bold focus:outline-none text-center">
</div>
</div>
</div>
</div>
</div>
<!-- 핵심 요약 대시보드 (평단가 및 레버리지 결과 출력) -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<!-- 평균 진입 가격 카드 -->
<div class="bg-white p-6 line-border line-shadow relative overflow-hidden group">
<div class="absolute right-4 top-4 opacity-5">
<svg class="w-16 h-16" fill="none" stroke="currentColor" stroke-width="1" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path>
</svg>
</div>
<p class="text-xs font-mono uppercase tracking-wider text-neutral-400">평균 진입 가격 (Average Entry Price)
</p>
<p class="text-3xl font-black mt-2 tracking-tight"><span class="currency-unit">$</span> <span
id="summary-avg-price">0</span></p>
<div class="mt-4 flex items-center justify-between text-xs text-neutral-500 font-mono">
<span>최종 계산 완료</span>
<span id="summary-coin-code" class="font-bold">BTC</span>
</div>
</div>
<!-- 실제 투입 증거금 카드 -->
<div class="bg-white p-6 line-border line-shadow relative overflow-hidden">
<div class="absolute right-4 top-4 opacity-5">
<svg class="w-16 h-16" fill="none" stroke="currentColor" stroke-width="1" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z">
</path>
</svg>
</div>
<p class="text-xs font-mono uppercase tracking-wider text-neutral-400">실제 투입 증거금 (Total Margin Required)
</p>
<p class="text-3xl font-black mt-2 tracking-tight"><span class="currency-unit">$</span> <span
id="summary-total-margin">0</span></p>
<div class="mt-4 flex items-center justify-between text-xs text-neutral-500 font-mono">
<span>레버리지 적용 실자산</span>
<span id="summary-total-cost-raw" class="text-[10px] font-bold">0 USD</span>
</div>
</div>
<!-- 총 포지션 가치 카드 -->
<div class="bg-white p-6 line-border line-shadow relative overflow-hidden">
<div class="absolute right-4 top-4 opacity-5">
<svg class="w-16 h-16" fill="none" stroke="currentColor" stroke-width="1" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round"
d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"></path>
</svg>
</div>
<p class="text-xs font-mono uppercase tracking-wider text-neutral-400">총 포지션 가치 (Total Position Value)
</p>
<p class="text-3xl font-black mt-2 tracking-tight"><span class="currency-unit">$</span> <span
id="summary-total-position">0</span></p>
<div class="mt-4 flex items-center justify-between text-xs text-neutral-500 font-mono">
<span>노출 자산 가치</span>
<span id="summary-total-qty" class="text-[10px] font-bold">0 BTC</span>
</div>
</div>
</div>
<!-- 메인 인터랙션 영역 -->
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
<!-- 왼쪽: 매수 이력 목록 입력 (7단) -->
<div class="lg:col-span-7 flex flex-col gap-6">
<div class="bg-white p-6 line-border line-shadow flex-grow">
<div class="flex justify-between items-center mb-6">
<div class="flex items-center gap-2">
<span class="w-3 h-3 bg-neutral-900 rounded-full inline-block"></span>
<h3 class="font-bold text-lg tracking-tight">순서별 레버리지 매수 이력</h3>
</div>
<div class="flex items-center gap-2">
<button onclick="clearAllRows()"
class="text-xs text-neutral-500 hover:text-neutral-900 border-b border-dashed border-neutral-400 hover:border-neutral-900 pb-0.5 transition-all">전체
초기화</button>
</div>
</div>
<!-- 순서 리스트 -->
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b-2 border-neutral-900 text-xs uppercase font-mono text-neutral-500">
<th class="py-2 px-1 w-8 text-center">차순</th>
<th class="py-2 px-2 w-1/4">매수 가격 (<span class="currency-unit">$</span>)</th>
<th class="py-2 px-2 w-1/5">매수 수량</th>
<th class="py-2 px-2 w-1/6 text-center">레버리지</th>
<th class="py-2 px-2 text-right">필요 증거금 (<span class="currency-unit">$</span>)</th>
<th class="py-2 px-2 text-right">포지션 가치 (<span class="currency-unit">$</span>)</th>
<th class="py-2 px-1 w-8 text-center">삭제</th>
</tr>
</thead>
<tbody id="transaction-rows" class="divide-y divide-neutral-200">
<!-- JS를 통해 동적으로 로우가 채워집니다 -->
</tbody>
</table>
</div>
<!-- 로우 추가 버튼 -->
<div class="mt-6 flex flex-col sm:flex-row gap-3">
<button onclick="addNewRow()"
class="flex-1 py-3 bg-white hover:bg-neutral-50 line-border line-shadow font-extrabold text-sm tracking-tight transition-all flex justify-center items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.5"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"></path>
</svg>
매수 이력 추가
</button>
<button id="quick-add-btn" onclick="addNewRowWithValues()"
class="py-3 px-4 bg-neutral-900 hover:bg-neutral-800 text-white line-border line-shadow font-extrabold text-sm tracking-tight transition-all flex justify-center items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z">
</path>
</svg>
간편 추가 (현재가 기준)
</button>
</div>
</div>
<!-- 사용자 퀵 팁 패널 -->
<div class="bg-neutral-100 p-4 border border-neutral-300 rounded-none flex items-start gap-3">
<svg class="w-5 h-5 text-neutral-600 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor"
stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<div class="text-xs text-neutral-600 leading-relaxed">
<p class="font-bold text-neutral-900">레버리지 기본값 설정 안내</p>
<p>새로운 행을 추가할 때 레버리지는 기본적으로 20배(20x)로 설정됩니다. 각 차수별로 자유롭게 레버리지를 다르게 수정하여 시뮬레이션할 수 있으며, 이에 대응하는 유효
레버리지가 실시간 자동 연산되어 청산가에 반영됩니다.</p>
</div>
</div>
</div>
<!-- 오른쪽: 포지션 분석, 수수료, 청산가 및 목표가 수익 시뮬레이터 (5단) -->
<div class="lg:col-span-5 flex flex-col gap-6">
<!-- 포지션 분석 & 청산가 & 수수료 카드 -->
<div class="bg-white p-6 line-border line-shadow flex flex-col">
<div class="mb-4 border-b pb-3 border-neutral-200">
<span class="text-xs font-mono uppercase tracking-wider text-neutral-400">포지션 분석 (Position
Analysis)</span>
<h4 class="font-bold text-base tracking-tight mt-1">실시간 청산가 및 거래 수수료</h4>
</div>
<!-- 롱 / 숏 포지션 토글 버튼 -->
<div class="grid grid-cols-2 gap-2 mb-4 font-mono">
<button id="toggle-long" onclick="changeDirection('LONG')"
class="py-2 text-xs font-bold line-border line-shadow-sm bg-neutral-900 text-white transition-all">LONG
(매수)</button>
<button id="toggle-short" onclick="changeDirection('SHORT')"
class="py-2 text-xs font-bold line-border line-shadow-sm bg-white text-neutral-900 hover:bg-neutral-50 transition-all">SHORT
(매도)</button>
</div>
<div class="space-y-4 font-mono text-sm">
<!-- 유효 레버리지 -->
<div class="flex justify-between items-center py-2 border-b border-dashed border-neutral-200">
<span class="text-neutral-500 text-xs">유효 레버리지 (Effective Leverage)</span>
<span class="font-bold" id="analysis-effective-leverage">20.00x</span>
</div>
<!-- 청산 가격 -->
<div class="flex justify-between items-center py-2 border-b border-dashed border-neutral-200">
<span class="text-neutral-500 text-xs flex flex-col">
<span>예상 청산 가격 (Est. Liq Price)</span>
<span class="text-[9px] text-neutral-400">유지 증거금율 0.4% 가정</span>
</span>
<span class="font-black text-red-600 text-lg" id="analysis-liq-price">$0.00</span>
</div>
<!-- 거래 수수료 -->
<div class="flex justify-between items-center py-2">
<span class="text-neutral-500 text-xs flex flex-col">
<span>예상 거래 수수료 (Total Est. Fees)</span>
<span class="text-[9px] text-neutral-400">지정가 진입+종료 총 0.04% 적용</span>
</span>
<span class="font-bold text-neutral-900" id="analysis-fees">$0.00</span>
</div>
</div>
</div>
<!-- 목표가 수익 계산기 카드 -->
<div class="bg-white p-6 line-border line-shadow flex flex-col">
<div class="mb-4 border-b pb-3 border-neutral-200">
<span class="text-xs font-mono uppercase tracking-wider text-neutral-400">목표가 계산기 (Profit
Calculator)</span>
<h4 class="font-bold text-base tracking-tight mt-1">목표 가격 기준 수익 시뮬레이터</h4>
</div>
<div class="space-y-4">
<!-- 목표가 입력 영역 -->
<div>
<label class="block text-[10px] uppercase font-mono tracking-wider text-neutral-500 mb-1">목표
가격 (Target Price, USD)</label>
<div class="relative">
<span class="absolute left-3 top-2.5 text-xs text-neutral-400 font-mono">$</span>
<input type="number" id="target-price-input" oninput="calculateProfitSimulator()"
class="w-full pl-7 pr-3 py-2 text-sm font-mono font-bold line-border"
placeholder="목표가 입력">
</div>
</div>
<!-- 시뮬레이션 계산 결과 영역 -->
<div class="bg-neutral-50 p-4 line-border border-dashed space-y-3 font-mono text-xs">
<div class="flex justify-between items-center pb-2 border-b border-neutral-200">
<span class="text-neutral-500">포지션 총 수량</span>
<span class="font-bold text-neutral-800" id="sim-total-qty">0 BTC</span>
</div>
<div class="flex justify-between items-center pb-2 border-b border-neutral-200">
<span class="text-neutral-500">예상 실현 수익금 (Gross PNL)</span>
<span class="font-bold text-green-600" id="sim-gross-pnl">+$0.00</span>
</div>
<div class="flex justify-between items-center pb-2 border-b border-neutral-200">
<span class="text-neutral-500">수수료 차감 후 순수익 (Net PNL)</span>
<span class="font-bold text-green-700 text-sm" id="sim-net-pnl">+$0.00</span>
</div>
<div class="flex justify-between items-center">
<span class="text-neutral-500">예상 수익률 (ROI)</span>
<span class="font-black text-green-600 text-sm" id="sim-roi">0.00%</span>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- 푸터 영역 -->
<footer class="border-t-2 border-neutral-900 bg-white mt-12 py-6">
<div
class="max-w-5xl mx-auto px-4 flex flex-col sm:flex-row justify-between items-center gap-4 text-xs text-neutral-500 font-mono">
<div>
<p>© 2026 gitsam. All rights reserved.</p>
</div>
<div class="flex gap-4">
<span class="hover:text-neutral-900 transition-all cursor-pointer">Vanilla JS</span>
<span>•</span>
<span class="hover:text-neutral-900 transition-all cursor-pointer">Binance Real-time Feed</span>
</div>
</div>
</footer>
<!-- 알림 모달 창 -->
<div id="custom-alert"
class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center opacity-0 pointer-events-none transition-opacity duration-200">
<div class="bg-white p-6 line-border line-shadow max-w-sm w-full mx-4">
<div class="flex items-center gap-3 mb-4">
<svg class="w-6 h-6 text-neutral-900" fill="none" stroke="currentColor" stroke-width="2"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z">
</path>
</svg>
<h5 class="font-extrabold text-base" id="alert-title">알림</h5>
</div>
<p class="text-xs text-neutral-600 mb-6 leading-relaxed" id="alert-message">메시지 내용</p>
<button onclick="closeAlert()"
class="w-full py-2 bg-neutral-900 text-white text-xs font-bold line-border line-shadow-sm tracking-wider transition-all">확인</button>
</div>
</div>
<!-- 스크립트 영역 -->
<script>
// 전역 상태 변수 관리
let activeAsset = 'BTC';
let activeAssetName = '비트코인';
let positionDirection = 'LONG'; // LONG or SHORT
// 초기 임시 데이터 세팅 (BTC USD 물타기 예제, 기본 레버리지 20배 설정)
let transactions = [
{ id: 1, price: 92000, qty: 0.1, leverage: 20 },
{ id: 2, price: 88000, qty: 0.15, leverage: 20 },
{ id: 3, price: 82000, qty: 0.25, leverage: 20 }
];
// 윈도우 로드 시 실행
window.onload = function () {
renderRows();
calculateAverage();
updateClocks();
setInterval(updateClocks, 1000);
};
// 헤더 드롭다운 선택 변경 처리
function handleAssetSelectChange() {
const select = document.getElementById('asset-select');
const customInputs = document.getElementById('custom-asset-inputs');
const selectedOption = select.options[select.selectedIndex];
if (select.value === 'CUSTOM') {
customInputs.classList.remove('hidden');
// 사용자 입력을 위한 초기값 세팅
document.getElementById('asset-symbol').value = 'SOL';
document.getElementById('asset-name').value = '솔라나';
updateAssetSettings();
} else {
customInputs.classList.add('hidden');
activeAsset = select.value;
activeAssetName = selectedOption.getAttribute('data-name');
// 데이터셋 예시 변경 (USD 기준 20배 고정 세팅)
if (activeAsset === 'BTC') {
transactions = [
{ id: 1, price: 92000, qty: 0.1, leverage: 20 },
{ id: 2, price: 88000, qty: 0.15, leverage: 20 },
{ id: 3, price: 82000, qty: 0.25, leverage: 20 }
];
} else if (activeAsset === 'ETH') {
transactions = [
{ id: 1, price: 3400, qty: 1.2, leverage: 20 },
{ id: 2, price: 3100, qty: 2.0, leverage: 20 },
{ id: 3, price: 2800, qty: 3.5, leverage: 20 }
];
} else if (activeAsset === 'XRP') {
transactions = [
{ id: 1, price: 1.20, qty: 1500, leverage: 20 },
{ id: 2, price: 1.05, qty: 3000, leverage: 20 },
{ id: 3, price: 0.90, qty: 5000, leverage: 20 }
];
} else if (activeAsset === 'SOL') {
transactions = [
{ id: 1, price: 180, qty: 10, leverage: 20 },
{ id: 2, price: 160, qty: 15, leverage: 20 },
{ id: 3, price: 140, qty: 25, leverage: 20 }
];
}
// UI 바인딩 및 계산 갱신
document.getElementById('current-asset-title').innerText = `${activeAssetName} (${activeAsset})`;
document.getElementById('summary-coin-code').innerText = activeAsset;
renderRows();
calculateAverage();
}
}
// 헤더 인풋 변경 시 동적 갱신 (CUSTOM 모드 전용)
function updateAssetSettings() {
const symbolInput = document.getElementById('asset-symbol').value.trim();
const nameInput = document.getElementById('asset-name').value.trim();
activeAsset = symbolInput ? symbolInput.toUpperCase() : 'BTC';
activeAssetName = nameInput ? nameInput : 'Crypto';
// UI 바인딩 갱신
document.getElementById('current-asset-title').innerText = `${activeAssetName} (${activeAsset})`;
document.getElementById('summary-coin-code').innerText = activeAsset;
calculateAverage();
}
// 포지션 방향 변경 (LONG / SHORT)
function changeDirection(direction) {
positionDirection = direction;
const btnLong = document.getElementById('toggle-long');
const btnShort = document.getElementById('toggle-short');
if (direction === 'LONG') {
btnLong.className = "py-2 text-xs font-bold line-border line-shadow-sm bg-neutral-900 text-white transition-all";
btnShort.className = "py-2 text-xs font-bold line-border line-shadow-sm bg-white text-neutral-900 hover:bg-neutral-50 transition-all";
} else {
btnLong.className = "py-2 text-xs font-bold line-border line-shadow-sm bg-white text-neutral-900 hover:bg-neutral-50 transition-all";
btnShort.className = "py-2 text-xs font-bold line-border line-shadow-sm bg-neutral-900 text-white transition-all";
}
calculateAverage();
}
// 트랜잭션 행 렌더링
function renderRows() {
const tbody = document.getElementById('transaction-rows');
tbody.innerHTML = '';
transactions.forEach((tx, idx) => {
const totalCost = tx.price * tx.qty;
const marginNeeded = totalCost / (tx.leverage || 20);
const tr = document.createElement('tr');
tr.className = "hover:bg-neutral-50 transition-all font-mono text-xs";
tr.innerHTML = `
<td class="py-3 px-1 text-center font-bold text-neutral-400">${idx + 1}</td>
<td class="py-2 px-2">
<input type="number" step="any" min="0" value="${tx.price}"
oninput="updateRowValue(${tx.id}, 'price', this.value)"
class="w-full bg-transparent border-b border-neutral-300 py-1 px-1 focus:border-neutral-900 transition-all font-mono">
</td>
<td class="py-2 px-2">
<input type="number" step="any" min="0" value="${tx.qty}"
oninput="updateRowValue(${tx.id}, 'qty', this.value)"
class="w-full bg-transparent border-b border-neutral-300 py-1 px-1 focus:border-neutral-900 transition-all font-mono">
</td>
<td class="py-2 px-2">
<input type="number" min="1" max="150" value="${tx.leverage || 20}"
oninput="updateRowValue(${tx.id}, 'leverage', this.value)"
class="w-full bg-transparent border-b border-neutral-300 py-1 px-1 focus:border-neutral-900 transition-all font-mono text-center">
</td>
<td class="py-3 px-2 text-right font-bold text-neutral-600" id="row-margin-${tx.id}">
$${formatNumber(marginNeeded)}
</td>
<td class="py-3 px-2 text-right font-bold text-neutral-900" id="row-total-${tx.id}">
$${formatNumber(totalCost)}
</td>
<td class="py-3 px-1 text-center">
<button onclick="removeRow(${tx.id})" class="text-neutral-400 hover:text-red-600 transition-colors p-1">
<svg class="w-4 h-4 mx-auto" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
</svg>
</button>
</td>
`;
tbody.appendChild(tr);
});
if (transactions.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="7" class="py-8 text-center text-xs text-neutral-400 font-mono">
매수 이력이 없습니다. 좌측의 '추가' 버튼을 통해 행을 추가하세요.
</td>
</tr>
`;
}
}
// 특정 행 데이터 업데이트
function updateRowValue(id, field, value) {
const tx = transactions.find(t => t.id === id);
if (tx) {
const numericValue = parseFloat(value) || 0;
tx[field] = numericValue;
const totalCost = tx.price * tx.qty;
const marginNeeded = totalCost / (tx.leverage || 20);
// 단일 행 실시간 금액 갱신
const marginElement = document.getElementById(`row-margin-${id}`);
const totalCostElement = document.getElementById(`row-total-${id}`);
if (marginElement) {
marginElement.innerText = '$' + formatNumber(marginNeeded);
}
if (totalCostElement) {
totalCostElement.innerText = '$' + formatNumber(totalCost);
}
calculateAverage();
}
}
// 새로운 행 추가 (레버리지 기본값 20x 적용)
function addNewRow() {
let defaultPrice = 0;
let defaultQty = 0;
let defaultLeverage = 20;
if (transactions.length > 0) {
defaultPrice = transactions[transactions.length - 1].price;
defaultQty = transactions[transactions.length - 1].qty;
defaultLeverage = transactions[transactions.length - 1].leverage || 20;
}
const nextId = transactions.length > 0 ? Math.max(...transactions.map(t => t.id)) + 1 : 1;
transactions.push({
id: nextId,
price: defaultPrice,
qty: defaultQty,
leverage: defaultLeverage
});
renderRows();
calculateAverage();
}
// 바이낸스 퍼블릭 API를 통한 실시간 가격 조회 함수
async function fetchBinancePrice(symbol) {
// 바이낸스 시장 정보 쿼리를 위해 USDT 페어로 변환
const querySymbol = `${symbol.toUpperCase()}USDT`;
const endpoint = `https://api.binance.com/api/v3/ticker/price?symbol=${querySymbol}`;
try {
// 비동기 fetch 통신 시도 (네트워크 오류 방지를 위한 타임아웃 5초 설정)
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetch(endpoint, { signal: controller.signal });
clearTimeout(timeoutId);
if (!response.ok) throw new Error('가격조회 응답 에러');
const data = await response.json();
return parseFloat(data.price);
} catch (error) {
console.warn('Binance API fetch failed, trying fallback:', error);
return null;
}
}
// 스마트 추가 - 실시간 가격을 API로 로드한 후 20배 기본 레버리지로 추가
async function addNewRowWithValues() {
const quickBtn = document.getElementById('quick-add-btn');
const originalHTML = quickBtn.innerHTML;
// 로딩 상태 피드백 UI 변경
quickBtn.innerHTML = `
<svg class="animate-spin h-4 w-4 text-white inline-block mr-1" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg> 시세 조회중
`;
quickBtn.disabled = true;
const fetchedPrice = await fetchBinancePrice(activeAsset);
// 원래 버튼 상태 복원
quickBtn.innerHTML = originalHTML;
quickBtn.disabled = false;
let finalPrice = fetchedPrice;
if (finalPrice === null) {
// API 실패 시 알림 팝업 전송 후 기존 마지막 등록 데이터 대체
showAlert('실시간 시세 조회 실패', '거래소 서버 지연으로 가격 정보를 가져올 수 없습니다. 임시 표준 가격으로 추가합니다.');
if (transactions.length > 0) {
finalPrice = transactions[transactions.length - 1].price;
} else {
// 비상용 기본 하드코딩값 백업
const defaultPrices = { BTC: 91500, ETH: 3350, XRP: 1.15, SOL: 175 };
finalPrice = defaultPrices[activeAsset] || 1.0;
}
}
// 자산 단위별 기본 매수 권장량 정밀 조정
let defaultQty = 1.0;
if (activeAsset === 'BTC') defaultQty = 0.05;
else if (activeAsset === 'ETH') defaultQty = 0.5;
else if (activeAsset === 'XRP') defaultQty = 1000;
else if (activeAsset === 'SOL') defaultQty = 5;
else if (transactions.length > 0) defaultQty = transactions[transactions.length - 1].qty;
const nextId = transactions.length > 0 ? Math.max(...transactions.map(t => t.id)) + 1 : 1;
transactions.push({
id: nextId,
price: finalPrice,
qty: defaultQty,
leverage: 20 // 레버리지 기본값 20배로 고정
});
renderRows();
calculateAverage();
}
// 특정 행 제거
function removeRow(id) {
transactions = transactions.filter(t => t.id !== id);
renderRows();
calculateAverage();
}
// 전체 초기화
function clearAllRows() {
transactions = [];
renderRows();
calculateAverage();
}
// 숫자 콤마 포맷팅 (소수점 자산 고려)
function formatNumber(num) {
if (num === null || num === undefined || isNaN(num)) return '0';
if (num % 1 !== 0) {
const parts = num.toFixed(4).toString().split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
let decimalStr = parts[1];
while (decimalStr.endsWith('0') && decimalStr.length > 2) {
decimalStr = decimalStr.slice(0, -1);
}
parts[1] = decimalStr;
return parts.join('.');
}
return Math.round(num).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
// 핵심 평단가, 레버리지, 수수료 및 청산가 계산
function calculateAverage() {
let totalPositionValue = 0;
let totalMarginRequired = 0;
let totalQty = 0;
transactions.forEach(tx => {
const positionVal = tx.price * tx.qty;
const lev = tx.leverage || 20;
totalPositionValue += positionVal;
totalMarginRequired += (positionVal / lev);
totalQty += tx.qty;
});
const avgPrice = totalQty > 0 ? (totalPositionValue / totalQty) : 0;
// 유효 레버리지 연산
const effectiveLeverage = totalMarginRequired > 0 ? (totalPositionValue / totalMarginRequired) : 20;
// 지정가 수수료 연산 (총 포지션 가치의 0.04% - 진입 + 종료 수수료 합산)
const transactionFee = totalPositionValue * 0.0004;
// 청산가 연산 (BTC 선물 표준 유지증거금률 MMR 0.4% 적용 가정)
let liquidationPrice = 0;
const mmr = 0.004;
if (avgPrice > 0) {
if (positionDirection === 'LONG') {
liquidationPrice = avgPrice * (1 - (1 / effectiveLeverage) + mmr);
if (liquidationPrice < 0) liquidationPrice = 0;
} else {
liquidationPrice = avgPrice * (1 + (1 / effectiveLeverage) - mmr);
}
}
// UI 메인 카드 갱신
document.getElementById('summary-avg-price').innerText = formatNumber(avgPrice);
document.getElementById('summary-total-margin').innerText = formatNumber(totalMarginRequired);
document.getElementById('summary-total-position').innerText = formatNumber(totalPositionValue);
document.getElementById('summary-total-qty').innerText = `${formatNumber(totalQty)} ${activeAsset}`;
document.getElementById('summary-total-cost-raw').innerText = `${formatNumber(totalMarginRequired)} USD`;
// 우측 포지션 분석 카드 갱신
document.getElementById('analysis-effective-leverage').innerText = `${effectiveLeverage.toFixed(2)}x`;
document.getElementById('analysis-liq-price').innerText = `$${formatNumber(liquidationPrice)}`;
document.getElementById('analysis-fees').innerText = `$${formatNumber(transactionFee)}`;
// 목표가 시뮬레이션 즉시 동기화
calculateProfitSimulator();
}
// 목표가 시뮬레이터 연산
function calculateProfitSimulator() {
let totalPositionValue = 0;
let totalMarginRequired = 0;
let totalQty = 0;
transactions.forEach(tx => {
const positionVal = tx.price * tx.qty;
const lev = tx.leverage || 20;
totalPositionValue += positionVal;
totalMarginRequired += (positionVal / lev);
totalQty += tx.qty;
});
const avgPrice = totalQty > 0 ? (totalPositionValue / totalQty) : 0;
const transactionFee = totalPositionValue * 0.0004;
const targetPriceInput = document.getElementById('target-price-input').value;
const targetPrice = parseFloat(targetPriceInput) || 0;
const simTotalQty = document.getElementById('sim-total-qty');
const simGrossPnl = document.getElementById('sim-gross-pnl');
const simNetPnl = document.getElementById('sim-net-pnl');
const simRoi = document.getElementById('sim-roi');
simTotalQty.innerText = `${formatNumber(totalQty)} ${activeAsset}`;
if (targetPrice <= 0 || totalQty <= 0 || avgPrice <= 0) {
simGrossPnl.innerText = '$0.00';
simNetPnl.innerText = '$0.00';
simRoi.innerText = '0.00%';
simGrossPnl.className = 'font-bold text-neutral-800';
simNetPnl.className = 'font-bold text-neutral-800 text-sm';
simRoi.className = 'font-black text-neutral-800 text-sm';
return;
}
// 실현 미실현 수익 계산 (Gross PNL)
let grossPnl = 0;
if (positionDirection === 'LONG') {
grossPnl = (targetPrice - avgPrice) * totalQty;
} else {
grossPnl = (avgPrice - targetPrice) * totalQty;
}
// 순수익 (수수료 차감 후 Net PNL)
const netPnl = grossPnl - transactionFee;
// 수익률 계산 (ROI) = Gross PNL / 필요 증거금 * 100%
const roi = totalMarginRequired > 0 ? (grossPnl / totalMarginRequired) * 100 : 0;
// 부호 표시 서식 설정
const signGross = grossPnl >= 0 ? '+' : '';
const signNet = netPnl >= 0 ? '+' : '';
const signRoi = roi >= 0 ? '+' : '';
simGrossPnl.innerText = `${signGross}$${formatNumber(grossPnl)}`;
simNetPnl.innerText = `${signNet}$${formatNumber(netPnl)}`;
simRoi.innerText = `${signRoi}${roi.toFixed(2)}%`;
// 수익 상태에 다른 미니멀 컬러 클래스 부여
if (grossPnl >= 0) {
simGrossPnl.className = 'font-bold text-green-600';
simRoi.className = 'font-black text-green-600 text-sm';
} else {
simGrossPnl.className = 'font-bold text-red-600';
simRoi.className = 'font-black text-red-600 text-sm';
}
if (netPnl >= 0) {
simNetPnl.className = 'font-bold text-green-700 text-sm';
} else {
simNetPnl.className = 'font-bold text-red-700 text-sm';
}
}
// 미니멀 알림 모달 제어
function showAlert(title, message) {
document.getElementById('alert-title').innerText = title;
document.getElementById('alert-message').innerText = message;
const modal = document.getElementById('custom-alert');
modal.classList.remove('pointer-events-none', 'opacity-0');
}
// 미니멀 알림 모달 종료
function closeAlert() {
const modal = document.getElementById('custom-alert');
modal.classList.add('pointer-events-none', 'opacity-0');
}
// 세계 시계 업데이트 함수 (clock.html 기능 통합)
function updateClocks() {
const now = new Date();
const zones = [
{ id: 'seoul', zone: 'Asia/Seoul' },
{ id: 'london', zone: 'Europe/London' },
{ id: 'newyork', zone: 'America/New_York' }
];
// 서울 시간 기준으로 글로벌 날짜 업데이트
const dateParts = new Intl.DateTimeFormat('en-GB', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
weekday: 'short',
timeZone: 'Asia/Seoul'
}).formatToParts(now).reduce((acc, part) => ({ ...acc, [part.type]: part.value }), {});
const globalDateEl = document.getElementById('global-date');
if (globalDateEl) {
globalDateEl.textContent =
`${dateParts.year}.${dateParts.month}.${dateParts.day} (${dateParts.weekday})`;
}
zones.forEach(z => {
const timeStr = new Intl.DateTimeFormat('en-GB', {
hour: '2-digit',
minute: '2-digit',
hour12: false,
timeZone: z.zone
}).format(now);
const el = document.getElementById(`${z.id}-time`);
if (el) {
el.textContent = timeStr;
}
});
}
</script>
</body>
</html>