Create templates/index.html
Browse files- templates/index.html +1527 -0
templates/index.html
ADDED
|
@@ -0,0 +1,1527 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="ko">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>๐ AI ์ฃผ์ ๋ฆฌ์์น ์ผํฐ v5.5</title>
|
| 7 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 8 |
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
| 9 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
| 10 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
| 11 |
+
<!-- TradingView Widget -->
|
| 12 |
+
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
| 13 |
+
<style>
|
| 14 |
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 15 |
+
body { font-family: 'Segoe UI', sans-serif; background: linear-gradient(135deg, #0a0a1a 0%, #1a1a2e 100%); padding: 20px; color: #e8e8e8; min-height: 100vh; }
|
| 16 |
+
.container { max-width: 1600px; margin: 0 auto; background: rgba(26, 26, 46, 0.95); border-radius: 20px; padding: 40px; box-shadow: 0 20px 60px rgba(0,0,0,0.5); border: 1px solid rgba(0,217,255,0.2); }
|
| 17 |
+
h1 { text-align: center; background: linear-gradient(135deg, #00d9ff, #00ff88); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 10px; font-size: 2.5em; }
|
| 18 |
+
.subtitle { text-align: center; color: #888; margin-bottom: 30px; }
|
| 19 |
+
|
| 20 |
+
.indices-bar { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 15px; margin-bottom: 30px; }
|
| 21 |
+
.index-card { background: linear-gradient(135deg, rgba(0,217,255,0.1), rgba(0,255,136,0.05)); border: 1px solid rgba(0,217,255,0.3); border-radius: 15px; padding: 15px; text-align: center; }
|
| 22 |
+
.index-name { font-size: 0.85em; color: #888; margin-bottom: 5px; }
|
| 23 |
+
.index-value { font-size: 1.4em; font-weight: 700; color: #fff; }
|
| 24 |
+
.index-change { font-size: 0.9em; font-weight: 600; margin-top: 5px; }
|
| 25 |
+
.positive { color: #00ff88; }
|
| 26 |
+
.negative { color: #ff4757; }
|
| 27 |
+
|
| 28 |
+
.tabs { display: flex; gap: 10px; margin-bottom: 30px; border-bottom: 2px solid rgba(255,255,255,0.1); overflow-x: auto; }
|
| 29 |
+
.tab { padding: 15px 30px; background: transparent; border: none; cursor: pointer; font-size: 1em; font-weight: 600; color: #888; border-radius: 10px 10px 0 0; white-space: nowrap; }
|
| 30 |
+
.tab.active { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; }
|
| 31 |
+
.tab:hover:not(.active) { background: rgba(255,255,255,0.1); color: #fff; }
|
| 32 |
+
.tab-content { display: none; animation: fadeIn 0.5s; }
|
| 33 |
+
.tab-content.active { display: block; }
|
| 34 |
+
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
| 35 |
+
|
| 36 |
+
/* ์ผํฐ๋จผํธ ๊ฒ์ด์ง */
|
| 37 |
+
.sentiment-section { background: rgba(255,255,255,0.03); border-radius: 15px; padding: 25px; margin-bottom: 30px; border: 1px solid rgba(255,255,255,0.1); }
|
| 38 |
+
.sentiment-title { color: #00d9ff; font-size: 1.2em; font-weight: 700; margin-bottom: 20px; text-align: center; }
|
| 39 |
+
.gauge-container { display: flex; justify-content: center; align-items: center; flex-direction: column; }
|
| 40 |
+
.gauge-wrapper { position: relative; width: 300px; height: 180px; }
|
| 41 |
+
.gauge-labels { display: flex; justify-content: space-between; width: 100%; margin-top: 10px; font-size: 0.7em; color: #888; }
|
| 42 |
+
.gauge-value { text-align: center; margin-top: 15px; }
|
| 43 |
+
.gauge-value .number { font-size: 2em; font-weight: 700; }
|
| 44 |
+
.gauge-value .label { font-size: 1.1em; margin-top: 5px; }
|
| 45 |
+
|
| 46 |
+
/* ์คํ ํํธ๋งต (finviz ์คํ์ผ) */
|
| 47 |
+
.heatmap-section { margin-bottom: 30px; }
|
| 48 |
+
.heatmap-container { background: rgba(0,0,0,0.3); border-radius: 15px; padding: 20px; border: 1px solid rgba(255,255,255,0.1); }
|
| 49 |
+
.heatmap-title { color: #00d9ff; font-size: 1.1em; font-weight: 700; margin-bottom: 15px; }
|
| 50 |
+
.stock-heatmap { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 4px; }
|
| 51 |
+
.heatmap-cell { padding: 12px 8px; border-radius: 4px; text-align: center; cursor: pointer; transition: all 0.3s; position: relative; min-height: 70px; display: flex; flex-direction: column; justify-content: center; }
|
| 52 |
+
.heatmap-cell:hover { transform: scale(1.05); z-index: 10; box-shadow: 0 5px 20px rgba(0,0,0,0.5); }
|
| 53 |
+
.heatmap-cell .ticker { font-weight: 700; font-size: 0.9em; color: #fff; }
|
| 54 |
+
.heatmap-cell .change { font-size: 0.8em; font-weight: 600; margin-top: 4px; }
|
| 55 |
+
.heatmap-cell .price { font-size: 0.7em; color: rgba(255,255,255,0.7); margin-top: 2px; }
|
| 56 |
+
.heatmap-cell.strong-up { background: linear-gradient(135deg, #00c853, #00e676); }
|
| 57 |
+
.heatmap-cell.up { background: linear-gradient(135deg, #4caf50, #66bb6a); }
|
| 58 |
+
.heatmap-cell.slight-up { background: linear-gradient(135deg, #8bc34a, #9ccc65); }
|
| 59 |
+
.heatmap-cell.neutral { background: linear-gradient(135deg, #607d8b, #78909c); }
|
| 60 |
+
.heatmap-cell.slight-down { background: linear-gradient(135deg, #ff9800, #ffa726); }
|
| 61 |
+
.heatmap-cell.down { background: linear-gradient(135deg, #f44336, #ef5350); }
|
| 62 |
+
.heatmap-cell.strong-down { background: linear-gradient(135deg, #c62828, #d32f2f); }
|
| 63 |
+
|
| 64 |
+
/* ์ด์ ํค์๋ ํ๊ทธ ํด๋ผ์ฐ๋ */
|
| 65 |
+
.keyword-cloud { display: flex; flex-wrap: wrap; gap: 10px; padding: 20px; background: rgba(255,255,255,0.03); border-radius: 15px; margin-bottom: 30px; }
|
| 66 |
+
.keyword-tag { padding: 8px 16px; border-radius: 20px; font-weight: 600; cursor: pointer; transition: all 0.3s; }
|
| 67 |
+
.keyword-tag:hover { transform: scale(1.1); }
|
| 68 |
+
.keyword-tag.size-xl { font-size: 1.3em; padding: 12px 24px; }
|
| 69 |
+
.keyword-tag.size-lg { font-size: 1.1em; padding: 10px 20px; }
|
| 70 |
+
.keyword-tag.size-md { font-size: 0.95em; }
|
| 71 |
+
.keyword-tag.size-sm { font-size: 0.85em; padding: 6px 12px; }
|
| 72 |
+
|
| 73 |
+
/* ํฐ์ปค ํ
์ดํ */
|
| 74 |
+
.ticker-tape-container { margin: -40px -40px 0 -40px; border-radius: 20px 20px 0 0; overflow: hidden; }
|
| 75 |
+
|
| 76 |
+
/* ๋ง์ผ ์ค๋ฒ๋ทฐ ๊ทธ๋ฆฌ๋ */
|
| 77 |
+
.market-overview-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 30px; }
|
| 78 |
+
@media (max-width: 1200px) { .market-overview-grid { grid-template-columns: repeat(2, 1fr); } }
|
| 79 |
+
@media (max-width: 768px) { .market-overview-grid { grid-template-columns: 1fr; } }
|
| 80 |
+
|
| 81 |
+
.market-card { background: rgba(255,255,255,0.03); border-radius: 15px; padding: 20px; border: 1px solid rgba(255,255,255,0.1); display: flex; flex-direction: column; align-items: center; }
|
| 82 |
+
.market-card-title { color: #00d9ff; font-size: 1em; font-weight: 700; margin-bottom: 15px; text-align: center; }
|
| 83 |
+
.chart-container { width: 100%; height: 180px; position: relative; }
|
| 84 |
+
.chart-legend { display: flex; justify-content: center; gap: 15px; margin-top: 10px; font-size: 0.85em; }
|
| 85 |
+
.legend-item { display: flex; align-items: center; gap: 5px; }
|
| 86 |
+
.legend-dot { width: 12px; height: 12px; border-radius: 50%; }
|
| 87 |
+
|
| 88 |
+
/* ์์ ฏ ์น์
*/
|
| 89 |
+
.widget-section { margin-bottom: 30px; }
|
| 90 |
+
.widget-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; }
|
| 91 |
+
@media (max-width: 992px) { .widget-grid { grid-template-columns: 1fr; } }
|
| 92 |
+
.widget-card { background: rgba(0,0,0,0.3); border-radius: 15px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1); min-height: 400px; }
|
| 93 |
+
.widget-card-full { background: rgba(0,0,0,0.3); border-radius: 15px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1); width: 100%; }
|
| 94 |
+
|
| 95 |
+
/* ํํธ๋งต ํญ */
|
| 96 |
+
.heatmap-tabs { display: flex; gap: 8px; }
|
| 97 |
+
.heatmap-tab { padding: 8px 16px; background: rgba(255,255,255,0.1); border: none; border-radius: 20px; color: #888; cursor: pointer; font-size: 0.9em; font-weight: 600; transition: all 0.3s; }
|
| 98 |
+
.heatmap-tab:hover { background: rgba(255,255,255,0.2); color: #fff; }
|
| 99 |
+
.heatmap-tab.active { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; }
|
| 100 |
+
|
| 101 |
+
/* ํ์ด์ง๋ค์ด์
ํญ */
|
| 102 |
+
.pagination-tabs { display: flex; gap: 8px; }
|
| 103 |
+
.page-tab { padding: 8px 16px; background: rgba(255,255,255,0.1); border: none; border-radius: 20px; color: #888; cursor: pointer; font-size: 0.85em; font-weight: 600; transition: all 0.3s; }
|
| 104 |
+
.page-tab:hover { background: rgba(255,255,255,0.2); color: #fff; }
|
| 105 |
+
.page-tab.active { background: linear-gradient(135deg, #9b59b6, #8e44ad); color: #fff; }
|
| 106 |
+
|
| 107 |
+
/* ์คํฌ๋ฆฌ๋ ์๋ธ ํญ */
|
| 108 |
+
.sub-tabs { display: flex; gap: 10px; margin-bottom: 10px; border-bottom: 2px solid rgba(255,255,255,0.1); padding-bottom: 15px; }
|
| 109 |
+
.sub-tab { padding: 12px 30px; background: rgba(255,255,255,0.05); border: 2px solid rgba(255,255,255,0.1); border-radius: 12px; color: #888; cursor: pointer; font-size: 1em; font-weight: 700; transition: all 0.3s; }
|
| 110 |
+
.sub-tab:hover { background: rgba(255,255,255,0.1); color: #fff; border-color: rgba(255,255,255,0.3); }
|
| 111 |
+
.sub-tab.active { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; border-color: transparent; }
|
| 112 |
+
|
| 113 |
+
/* ์คํฌ๋ฆฌ๋ ์๋ธ ์ฝํ
์ธ */
|
| 114 |
+
.screening-sub-content { display: none; }
|
| 115 |
+
.screening-sub-content.active { display: block; }
|
| 116 |
+
|
| 117 |
+
/* ์คํฌ๋ฆฌ๋ ์น์
*/
|
| 118 |
+
.screening-section { background: rgba(255,255,255,0.02); border-radius: 15px; padding: 20px; border: 1px solid rgba(255,255,255,0.1); }
|
| 119 |
+
|
| 120 |
+
/* ๋ด์ค/๋ฆฌ์์น ์นด๋ */
|
| 121 |
+
.news-card { background: rgba(255,255,255,0.03); border-radius: 15px; padding: 25px; margin-bottom: 20px; border-left: 4px solid #00d9ff; }
|
| 122 |
+
.news-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 15px; flex-wrap: wrap; gap: 10px; }
|
| 123 |
+
.news-title { font-size: 1.15em; font-weight: 700; color: #fff; flex: 1; line-height: 1.5; }
|
| 124 |
+
.news-meta { display: flex; gap: 10px; flex-wrap: wrap; }
|
| 125 |
+
.badge { padding: 5px 12px; border-radius: 20px; font-size: 0.8em; font-weight: 600; }
|
| 126 |
+
.ticker-badge { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; }
|
| 127 |
+
.market-badge { background: linear-gradient(135deg, #9b59b6, #8e44ad); color: #fff; }
|
| 128 |
+
.impact-high { background: #ff4757; color: white; }
|
| 129 |
+
.impact-medium { background: #ffa502; color: #1a1a2e; }
|
| 130 |
+
.impact-low { background: #2ed573; color: #1a1a2e; }
|
| 131 |
+
.sentiment-positive { background: rgba(0,255,136,0.2); color: #00ff88; border: 1px solid #00ff88; }
|
| 132 |
+
.sentiment-negative { background: rgba(255,71,87,0.2); color: #ff4757; border: 1px solid #ff4757; }
|
| 133 |
+
.sentiment-neutral { background: rgba(255,165,2,0.2); color: #ffa502; border: 1px solid #ffa502; }
|
| 134 |
+
|
| 135 |
+
.news-summary { color: #bbb; line-height: 1.8; margin-bottom: 15px; font-size: 0.95em; }
|
| 136 |
+
.news-content { color: #999; line-height: 1.7; margin-bottom: 15px; font-size: 0.9em; padding: 15px; background: rgba(0,0,0,0.2); border-radius: 10px; }
|
| 137 |
+
.analysis-box { background: rgba(0,217,255,0.08); border-radius: 10px; padding: 15px; margin-top: 15px; }
|
| 138 |
+
.analysis-label { display: inline-block; background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; padding: 5px 12px; border-radius: 15px; font-size: 0.85em; font-weight: 600; margin-bottom: 10px; }
|
| 139 |
+
.news-link { display: inline-block; background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; padding: 10px 20px; border-radius: 8px; text-decoration: none; font-size: 0.9em; font-weight: 600; margin-top: 10px; transition: all 0.3s; border: none; cursor: pointer; }
|
| 140 |
+
.news-link:hover { transform: scale(1.05); box-shadow: 0 5px 15px rgba(0,217,255,0.3); }
|
| 141 |
+
|
| 142 |
+
.research-card { background: rgba(255,255,255,0.03); border-radius: 15px; padding: 25px; margin-bottom: 20px; border: 1px solid rgba(255,255,255,0.1); border-left: 4px solid #00ff88; }
|
| 143 |
+
.research-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 10px; }
|
| 144 |
+
.research-ticker { font-size: 1.8em; font-weight: 800; background: linear-gradient(135deg, #00d9ff, #00ff88); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
| 145 |
+
.rating-badge { padding: 10px 20px; border-radius: 25px; font-size: 1em; font-weight: 700; }
|
| 146 |
+
.rating-buy { background: #2ed573; color: #1a1a2e; }
|
| 147 |
+
.rating-hold { background: #ffa502; color: #1a1a2e; }
|
| 148 |
+
.rating-sell { background: #ff4757; color: white; }
|
| 149 |
+
|
| 150 |
+
.metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 15px; margin-bottom: 20px; }
|
| 151 |
+
.metric-card { background: rgba(255,255,255,0.05); padding: 15px; border-radius: 10px; text-align: center; }
|
| 152 |
+
.metric-label { color: #888; font-size: 0.85em; margin-bottom: 5px; }
|
| 153 |
+
.metric-value { font-size: 1.2em; font-weight: 700; color: #fff; }
|
| 154 |
+
|
| 155 |
+
/* ์ค์ผ์ค๋ฌ ์ํ ํ์ */
|
| 156 |
+
.scheduler-status {
|
| 157 |
+
display: flex; align-items: center; justify-content: center; gap: 10px;
|
| 158 |
+
background: rgba(155, 89, 182, 0.1); border: 1px solid rgba(155, 89, 182, 0.3);
|
| 159 |
+
border-radius: 25px; padding: 10px 20px; margin: 15px auto; max-width: 500px;
|
| 160 |
+
}
|
| 161 |
+
.scheduler-indicator { font-size: 1.2em; animation: pulse 2s infinite; }
|
| 162 |
+
.scheduler-indicator.active { color: #00ff88; }
|
| 163 |
+
.scheduler-indicator.inactive { color: #ff4757; }
|
| 164 |
+
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
|
| 165 |
+
#schedulerText { color: #bbb; font-size: 0.9em; }
|
| 166 |
+
|
| 167 |
+
.soma-agent { background: rgba(255,255,255,0.02); border-radius: 12px; padding: 20px; margin-bottom: 15px; border-left: 3px solid; }
|
| 168 |
+
.soma-supervisor { border-color: #9b59b6; }
|
| 169 |
+
.soma-researcher { border-color: #3498db; }
|
| 170 |
+
.soma-critic { border-color: #e74c3c; }
|
| 171 |
+
.soma-analyst { border-color: #00ff88; }
|
| 172 |
+
.soma-header { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
|
| 173 |
+
.soma-icon { width: 35px; height: 35px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.1em; }
|
| 174 |
+
.soma-name { font-weight: 700; font-size: 0.95em; }
|
| 175 |
+
.soma-content { color: #bbb; line-height: 1.8; font-size: 0.95em; }
|
| 176 |
+
|
| 177 |
+
.deep-analysis-btn { background: linear-gradient(135deg, #9b59b6, #8e44ad); color: white; border: none; padding: 12px 24px; border-radius: 25px; font-size: 0.9em; font-weight: 700; cursor: pointer; transition: all 0.3s; }
|
| 178 |
+
.deep-analysis-btn:hover { transform: scale(1.05); box-shadow: 0 10px 30px rgba(155, 89, 182, 0.4); }
|
| 179 |
+
|
| 180 |
+
.download-btn { background: linear-gradient(135deg, #27ae60, #2ecc71); color: white; border: none; padding: 12px 24px; border-radius: 25px; font-size: 0.9em; font-weight: 700; cursor: pointer; transition: all 0.3s; text-decoration: none; margin: 5px; display: inline-flex; align-items: center; gap: 8px; }
|
| 181 |
+
.download-btn:hover { transform: scale(1.05); box-shadow: 0 10px 30px rgba(39, 174, 96, 0.4); }
|
| 182 |
+
.download-btn.pdf { background: linear-gradient(135deg, #e74c3c, #c0392b); }
|
| 183 |
+
|
| 184 |
+
/* ์คํฌ๋ฆฌ๋ ํ
์ด๋ธ */
|
| 185 |
+
.screening-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; }
|
| 186 |
+
.stock-card { background: rgba(255,255,255,0.03); border-radius: 15px; padding: 20px; border: 1px solid rgba(255,255,255,0.1); transition: all 0.3s; }
|
| 187 |
+
.stock-card:hover { transform: translateY(-5px); box-shadow: 0 10px 30px rgba(0,217,255,0.2); border-color: #00d9ff; }
|
| 188 |
+
.stock-card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
|
| 189 |
+
.stock-card-ticker { font-size: 1.4em; font-weight: 800; color: #00d9ff; }
|
| 190 |
+
.stock-card-rank { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; padding: 5px 12px; border-radius: 15px; font-weight: 700; font-size: 0.85em; }
|
| 191 |
+
.stock-card-name { color: #888; font-size: 0.9em; margin-bottom: 15px; }
|
| 192 |
+
.stock-card-price { font-size: 1.5em; font-weight: 700; margin-bottom: 5px; }
|
| 193 |
+
.stock-card-change { font-size: 1.1em; font-weight: 600; margin-bottom: 15px; }
|
| 194 |
+
.mini-chart { width: 100%; height: 60px; background: rgba(0,0,0,0.2); border-radius: 8px; margin-bottom: 15px; overflow: hidden; }
|
| 195 |
+
.stock-card-metrics { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; font-size: 0.85em; }
|
| 196 |
+
.stock-card-metric { display: flex; justify-content: space-between; padding: 8px; background: rgba(255,255,255,0.03); border-radius: 5px; }
|
| 197 |
+
.stock-card-metric-label { color: #888; }
|
| 198 |
+
.stock-card-metric-value { font-weight: 600; }
|
| 199 |
+
.view-chart-btn { width: 100%; margin-top: 15px; padding: 10px; background: rgba(0,217,255,0.2); border: 1px solid #00d9ff; color: #00d9ff; border-radius: 8px; cursor: pointer; font-weight: 600; transition: all 0.3s; }
|
| 200 |
+
.view-chart-btn:hover { background: #00d9ff; color: #1a1a2e; }
|
| 201 |
+
|
| 202 |
+
/* ๋ชจ๋ฌ */
|
| 203 |
+
.modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); overflow: auto; }
|
| 204 |
+
.modal.active { display: flex; justify-content: center; align-items: flex-start; padding: 30px 20px; }
|
| 205 |
+
.modal-content { background: #1a1a2e; border-radius: 20px; padding: 30px; max-width: 1200px; width: 100%; border: 1px solid rgba(0,217,255,0.3); max-height: 95vh; overflow-y: auto; }
|
| 206 |
+
.modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; position: sticky; top: -30px; background: #1a1a2e; padding: 10px 0; z-index: 10; }
|
| 207 |
+
.modal-close { background: none; border: none; color: #888; font-size: 2em; cursor: pointer; }
|
| 208 |
+
.modal-close:hover { color: #ff4757; }
|
| 209 |
+
|
| 210 |
+
/* ์ฌ์ธต ๋ถ์ ์น์
*/
|
| 211 |
+
.deep-section { margin-bottom: 20px; padding: 20px; background: rgba(255,255,255,0.03); border-radius: 12px; }
|
| 212 |
+
.deep-section h3 { color: #00d9ff; margin-bottom: 15px; font-size: 1.05em; }
|
| 213 |
+
.deep-section .content { color: #bbb; line-height: 1.8; font-size: 0.95em; }
|
| 214 |
+
.deep-section .content table { width: 100%; border-collapse: collapse; margin: 15px 0; }
|
| 215 |
+
.deep-section .content th { background: rgba(0,217,255,0.2); padding: 10px; text-align: left; color: #00d9ff; }
|
| 216 |
+
.deep-section .content td { padding: 10px; border-bottom: 1px solid rgba(255,255,255,0.1); }
|
| 217 |
+
.deep-section .content ul, .deep-section .content ol { margin: 10px 0 10px 20px; }
|
| 218 |
+
.deep-section .content li { margin-bottom: 8px; }
|
| 219 |
+
.deep-section .content strong { color: #fff; }
|
| 220 |
+
.deep-section .content p { margin-bottom: 10px; }
|
| 221 |
+
|
| 222 |
+
.chart-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; margin: 20px 0; }
|
| 223 |
+
.chart-box { background: rgba(255,255,255,0.03); border-radius: 12px; padding: 15px; }
|
| 224 |
+
.chart-box h4 { color: #00d9ff; margin-bottom: 10px; font-size: 0.9em; text-align: center; }
|
| 225 |
+
.chart-box canvas { max-height: 200px; }
|
| 226 |
+
|
| 227 |
+
.loading { display: none; text-align: center; padding: 40px; }
|
| 228 |
+
.loading.active { display: block; }
|
| 229 |
+
.spinner { border: 4px solid rgba(255,255,255,0.1); border-top: 4px solid #00d9ff; border-radius: 50%; width: 50px; height: 50px; animation: spin 1s linear infinite; margin: 0 auto 20px; }
|
| 230 |
+
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
| 231 |
+
|
| 232 |
+
/* TradingView ์ฐจํธ ๋ชจ๋ฌ */
|
| 233 |
+
.chart-modal-content { max-width: 1000px; }
|
| 234 |
+
#tradingview-chart { width: 100%; height: 500px; border-radius: 10px; overflow: hidden; }
|
| 235 |
+
|
| 236 |
+
.button-group { text-align: center; margin: 40px 0; display: flex; justify-content: center; gap: 15px; flex-wrap: wrap; }
|
| 237 |
+
.refresh-btn { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; border: none; padding: 15px 35px; font-size: 1em; font-weight: 700; border-radius: 50px; cursor: pointer; transition: all 0.3s; }
|
| 238 |
+
.refresh-btn:hover { transform: scale(1.05); box-shadow: 0 10px 30px rgba(0,217,255,0.4); }
|
| 239 |
+
.refresh-btn.secondary { background: transparent; border: 2px solid #00d9ff; color: #00d9ff; }
|
| 240 |
+
|
| 241 |
+
.timestamp { text-align: center; color: #666; margin-top: 30px; font-size: 0.9em; padding: 15px; background: rgba(255,255,255,0.02); border-radius: 10px; }
|
| 242 |
+
.footer { text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,0.1); color: #666; font-size: 0.85em; }
|
| 243 |
+
.empty-state { text-align: center; padding: 60px; color: #888; }
|
| 244 |
+
.empty-state h3 { margin-bottom: 15px; color: #00d9ff; }
|
| 245 |
+
|
| 246 |
+
@media (max-width: 768px) {
|
| 247 |
+
body { padding: 10px; }
|
| 248 |
+
.container { padding: 20px; }
|
| 249 |
+
h1 { font-size: 1.8em; }
|
| 250 |
+
.tab { padding: 12px 15px; font-size: 0.85em; }
|
| 251 |
+
.screening-grid { grid-template-columns: 1fr; }
|
| 252 |
+
.stock-heatmap { grid-template-columns: repeat(3, 1fr); }
|
| 253 |
+
}
|
| 254 |
+
</style>
|
| 255 |
+
</head>
|
| 256 |
+
<body>
|
| 257 |
+
<div class="container">
|
| 258 |
+
<h1>๐ AI ์ฃผ์ ๋ฆฌ์์น ์ผํฐ v4.2</h1>
|
| 259 |
+
<p class="subtitle">
|
| 260 |
+
TOP 30 ์ข
๋ชฉ ์ค์๊ฐ ๋ถ์ | ์คํ ํํธ๋งต | ์ฐจํธ ๋ถ์ | ์ฌ์ธต ๋ถ์ + PDF/DOCX<br>
|
| 261 |
+
<span style="color: #00d9ff;">AGI SOMA ๋ฉํฐ์์ด์ ํธ | ์กฐ์ฌ์โ๋นํ๊ฐโ๊ฐ๋
์ ํ์
์์คํ
</span>
|
| 262 |
+
</p>
|
| 263 |
+
|
| 264 |
+
<!-- ์ง์ -->
|
| 265 |
+
<div class="indices-bar">
|
| 266 |
+
{% for idx in indices %}
|
| 267 |
+
<div class="index-card">
|
| 268 |
+
<div class="index-name">{{ idx.name }}</div>
|
| 269 |
+
<div class="index-value">{{ "{:,.2f}".format(idx.price) }}</div>
|
| 270 |
+
<div class="index-change {{ 'positive' if idx.change >= 0 else 'negative' }}">
|
| 271 |
+
{{ "+" if idx.change >= 0 else "" }}{{ "{:.2f}".format(idx.change) }} ({{ "{:.2f}".format(idx.change_percent) }}%)
|
| 272 |
+
</div>
|
| 273 |
+
</div>
|
| 274 |
+
{% endfor %}
|
| 275 |
+
</div>
|
| 276 |
+
|
| 277 |
+
<!-- ํญ -->
|
| 278 |
+
<div class="tabs">
|
| 279 |
+
<button class="tab active" onclick="switchTab(event, 'market')">๐ ์์ฅ ๋ถ์</button>
|
| 280 |
+
<button class="tab" onclick="switchTab(event, 'breaking')">๐ฅ ์๋ณด ({{ breaking_news|length }})</button>
|
| 281 |
+
<button class="tab" onclick="switchTab(event, 'research')">๐ ๋ฆฌ์์น ({{ research_reports|length }})</button>
|
| 282 |
+
<button class="tab" onclick="switchTab(event, 'screening')">๐ ์คํฌ๋ฆฌ๋ ({{ screening_data|length }})</button>
|
| 283 |
+
</div>
|
| 284 |
+
|
| 285 |
+
<!-- ์์ฅ ๋ถ์ ํญ -->
|
| 286 |
+
<div id="market-content" class="tab-content active">
|
| 287 |
+
|
| 288 |
+
<!-- ํฐ์ปค ํ
์ดํ (์๋จ ํ๋ฅด๋ ์ฃผ๊ฐ) -->
|
| 289 |
+
<div class="ticker-tape-container">
|
| 290 |
+
<div id="ticker-tape-widget"></div>
|
| 291 |
+
</div>
|
| 292 |
+
|
| 293 |
+
<h2 style="color: #00d9ff; margin-bottom: 20px; margin-top: 20px;">๐ ์์ฅ ์ข
ํฉ ๋ถ์</h2>
|
| 294 |
+
|
| 295 |
+
<!-- 3์ด ๊ทธ๋ฆฌ๋: ์ผํฐ๋จผํธ + ์นํฐ ํผํฌ๋จผ์ค + ์์น/ํ๋ฝ ๋น์จ -->
|
| 296 |
+
<div class="market-overview-grid">
|
| 297 |
+
<!-- ์ผํฐ๋จผํธ ๊ฒ์ด์ง -->
|
| 298 |
+
<div class="market-card">
|
| 299 |
+
<div class="market-card-title">๐ ์์ฅ ์ผํฐ๋จผํธ</div>
|
| 300 |
+
<div class="gauge-container">
|
| 301 |
+
<div class="gauge-wrapper" style="width: 200px; height: 120px;">
|
| 302 |
+
<canvas id="sentimentGauge"></canvas>
|
| 303 |
+
</div>
|
| 304 |
+
<div class="gauge-labels" style="font-size: 0.6em;">
|
| 305 |
+
<span>๋งค๋</span>
|
| 306 |
+
<span>์ค๋ฆฝ</span>
|
| 307 |
+
<span>๋งค์</span>
|
| 308 |
+
</div>
|
| 309 |
+
<div class="gauge-value" style="margin-top: 10px;">
|
| 310 |
+
<div class="number" id="sentimentValue" style="font-size: 1.8em;">-</div>
|
| 311 |
+
<div class="label" id="sentimentLabel" style="color: #00d9ff; font-size: 0.95em;">๋ถ์ ์ค...</div>
|
| 312 |
+
</div>
|
| 313 |
+
</div>
|
| 314 |
+
</div>
|
| 315 |
+
|
| 316 |
+
<!-- ์นํฐ๋ณ ํผํฌ๋จผ์ค ๋ฐ ์ฐจํธ -->
|
| 317 |
+
<div class="market-card">
|
| 318 |
+
<div class="market-card-title">๐ ์นํฐ๋ณ ํผํฌ๋จผ์ค</div>
|
| 319 |
+
<div class="chart-container">
|
| 320 |
+
<canvas id="sectorChart"></canvas>
|
| 321 |
+
</div>
|
| 322 |
+
</div>
|
| 323 |
+
|
| 324 |
+
<!-- ์์น/ํ๋ฝ ๋น์จ ๋๋ -->
|
| 325 |
+
<div class="market-card">
|
| 326 |
+
<div class="market-card-title">๐ฉ ์์น/ํ๋ฝ ๋น์จ</div>
|
| 327 |
+
<div class="chart-container">
|
| 328 |
+
<canvas id="upDownChart"></canvas>
|
| 329 |
+
</div>
|
| 330 |
+
<div id="upDownLegend" class="chart-legend"></div>
|
| 331 |
+
</div>
|
| 332 |
+
</div>
|
| 333 |
+
|
| 334 |
+
<!-- ๋ง์ผ ์ค๋ฒ๋ทฐ ์์ ฏ (์ ์ฒด ๋๋น) -->
|
| 335 |
+
<div class="widget-section">
|
| 336 |
+
<h3 style="color: #00d9ff; margin-bottom: 15px;">๐ ๊ธ๋ก๋ฒ ์์ฅ ํํฉ</h3>
|
| 337 |
+
<div class="widget-card-full" id="market-overview-widget" style="height: 450px;"></div>
|
| 338 |
+
</div>
|
| 339 |
+
|
| 340 |
+
<!-- ์ด์ ํค์๋ ํ๊ทธ ํด๋ผ์ฐ๋ -->
|
| 341 |
+
<h3 style="color: #00d9ff; margin-bottom: 15px; margin-top: 30px;">๐ฅ ์์ฅ ์ด์ ํค์๋ (24h)</h3>
|
| 342 |
+
<div class="keyword-cloud" id="keywordCloud"></div>
|
| 343 |
+
|
| 344 |
+
<!-- ์คํ ํํธ๋งต with ํญ -->
|
| 345 |
+
<div class="heatmap-section">
|
| 346 |
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
| 347 |
+
<h3 style="color: #00d9ff; margin: 0;">๐ ์์ฅ ํํธ๋งต</h3>
|
| 348 |
+
<div class="heatmap-tabs">
|
| 349 |
+
<button class="heatmap-tab active" onclick="switchHeatmap('SPX500', this)">S&P 500</button>
|
| 350 |
+
<button class="heatmap-tab" onclick="switchHeatmap('NASDAQ100', this)">NASDAQ 100</button>
|
| 351 |
+
<button class="heatmap-tab" onclick="switchHeatmap('DJI', this)">DOW 30</button>
|
| 352 |
+
</div>
|
| 353 |
+
</div>
|
| 354 |
+
<div id="stock-heatmap-widget" style="height: 500px; border-radius: 15px; overflow: hidden;"></div>
|
| 355 |
+
</div>
|
| 356 |
+
</div>
|
| 357 |
+
|
| 358 |
+
<!-- ์๋ณด ํญ -->
|
| 359 |
+
<div id="breaking-content" class="tab-content">
|
| 360 |
+
<h2 style="color: #00d9ff; margin-bottom: 20px;">๐ฅ 24์๊ฐ ์ด๋ด ์ฃผ์ ๋ด์ค</h2>
|
| 361 |
+
{% if breaking_news %}
|
| 362 |
+
{% for news in breaking_news %}
|
| 363 |
+
<div class="news-card">
|
| 364 |
+
<div class="news-header">
|
| 365 |
+
<div class="news-title">{{ news.title }}</div>
|
| 366 |
+
<div class="news-meta">
|
| 367 |
+
{% if news.news_type == 'market' %}
|
| 368 |
+
<span class="badge market-badge">{{ news.ticker }}</span>
|
| 369 |
+
{% else %}
|
| 370 |
+
<span class="badge ticker-badge">{{ news.ticker }}</span>
|
| 371 |
+
{% endif %}
|
| 372 |
+
<span class="badge impact-{{ news.impact_level }}">{{ news.impact_text }}</span>
|
| 373 |
+
<span class="badge sentiment-{{ news.sentiment }}">{{ news.sentiment_text }}</span>
|
| 374 |
+
</div>
|
| 375 |
+
</div>
|
| 376 |
+
{% if news.title_original and news.title_original != news.title %}
|
| 377 |
+
<div style="color: #666; font-size: 0.85em; margin-bottom: 10px; font-style: italic;">๐ {{ news.title_original }}</div>
|
| 378 |
+
{% endif %}
|
| 379 |
+
<div class="news-summary">{{ news.summary }}</div>
|
| 380 |
+
{% if news.content %}
|
| 381 |
+
<div class="news-content">{{ news.content }}</div>
|
| 382 |
+
{% endif %}
|
| 383 |
+
<div class="analysis-box">
|
| 384 |
+
<span class="analysis-label">๐ก ํฌ์ ํฌ์ธํธ</span>
|
| 385 |
+
<div style="color: #bbb; line-height: 1.7;">{{ news.investment_point }}</div>
|
| 386 |
+
</div>
|
| 387 |
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 15px; flex-wrap: wrap; gap: 10px;">
|
| 388 |
+
<div>
|
| 389 |
+
<span style="color: #666; font-size: 0.85em;">๐
{{ news.published_at }}</span>
|
| 390 |
+
{% if news.source %}
|
| 391 |
+
<span style="color: #00d9ff; font-size: 0.85em; margin-left: 10px;">๐ฐ {{ news.source }}</span>
|
| 392 |
+
{% endif %}
|
| 393 |
+
</div>
|
| 394 |
+
<div>
|
| 395 |
+
{% if news.url %}
|
| 396 |
+
<a href="{{ news.url }}" target="_blank" class="news-link" style="margin-right: 5px;">๐ ์๋ฌธ ๋ณด๊ธฐ</a>
|
| 397 |
+
{% endif %}
|
| 398 |
+
<button class="news-link" onclick="searchNews('{{ news.title|e }}')">๐ ๊ด๋ จ ๊ฒ์</button>
|
| 399 |
+
</div>
|
| 400 |
+
</div>
|
| 401 |
+
</div>
|
| 402 |
+
{% endfor %}
|
| 403 |
+
{% else %}
|
| 404 |
+
<div class="empty-state">
|
| 405 |
+
<h3>๐ญ 24์๊ฐ ์ด๋ด ๋ด์ค๊ฐ ์์ต๋๋ค</h3>
|
| 406 |
+
</div>
|
| 407 |
+
{% endif %}
|
| 408 |
+
</div>
|
| 409 |
+
|
| 410 |
+
<!-- ๋ฆฌ์์น ํญ -->
|
| 411 |
+
<div id="research-content" class="tab-content">
|
| 412 |
+
<h2 style="color: #00d9ff; margin-bottom: 20px;">๐ AGI SOMA ๋ฆฌ์์น + ์ฌ์ธต ๋ถ์</h2>
|
| 413 |
+
{% if research_reports %}
|
| 414 |
+
{% for report in research_reports %}
|
| 415 |
+
<div class="research-card">
|
| 416 |
+
<div class="research-header">
|
| 417 |
+
<div>
|
| 418 |
+
<span class="research-ticker">{{ report.ticker }}</span>
|
| 419 |
+
<span style="color: #888; margin-left: 10px;">{{ report.company_name }}</span>
|
| 420 |
+
</div>
|
| 421 |
+
<span class="rating-badge rating-{{ report.rating_class }}">{{ report.rating }}</span>
|
| 422 |
+
</div>
|
| 423 |
+
<div class="metrics-grid">
|
| 424 |
+
<div class="metric-card">
|
| 425 |
+
<div class="metric-label">ํ์ฌ๊ฐ</div>
|
| 426 |
+
<div class="metric-value">${{ "{:,.2f}".format(report.current_price) }}</div>
|
| 427 |
+
</div>
|
| 428 |
+
<div class="metric-card">
|
| 429 |
+
<div class="metric-label">๋ชฉํ๊ฐ</div>
|
| 430 |
+
<div class="metric-value" style="color: #00ff88;">${{ "{:,.2f}".format(report.target_price) }}</div>
|
| 431 |
+
</div>
|
| 432 |
+
<div class="metric-card">
|
| 433 |
+
<div class="metric-label">์์น์ฌ๋ ฅ</div>
|
| 434 |
+
<div class="metric-value" style="color: #00d9ff;">+{{ report.upside }}%</div>
|
| 435 |
+
</div>
|
| 436 |
+
<div class="metric-card">
|
| 437 |
+
<div class="metric-label">๋ฆฌ์คํฌ</div>
|
| 438 |
+
<div class="metric-value">{{ report.risk_level }}</div>
|
| 439 |
+
</div>
|
| 440 |
+
</div>
|
| 441 |
+
<div class="soma-agent soma-supervisor">
|
| 442 |
+
<div class="soma-header">
|
| 443 |
+
<div class="soma-icon" style="background: #9b59b6;">๐</div>
|
| 444 |
+
<span class="soma-name" style="color: #9b59b6;">๊ฐ๋
์ (Supervisor)</span>
|
| 445 |
+
</div>
|
| 446 |
+
<div class="soma-content">{{ report.supervisor_analysis }}</div>
|
| 447 |
+
</div>
|
| 448 |
+
<div class="soma-agent soma-researcher">
|
| 449 |
+
<div class="soma-header">
|
| 450 |
+
<div class="soma-icon" style="background: #3498db;">๐ฌ</div>
|
| 451 |
+
<span class="soma-name" style="color: #3498db;">์กฐ์ฌ์ (Researcher)</span>
|
| 452 |
+
</div>
|
| 453 |
+
<div class="soma-content">{{ report.researcher_analysis }}</div>
|
| 454 |
+
</div>
|
| 455 |
+
<div style="margin-top: 20px;">
|
| 456 |
+
<button class="deep-analysis-btn" onclick="runDeepAnalysis('{{ report.ticker }}', '{{ report.company_name }}')">๐ฌ ์ฌ์ธต ๋ถ์</button>
|
| 457 |
+
<span id="deep-status-{{ report.ticker }}" style="margin-left: 15px; color: #888;"></span>
|
| 458 |
+
</div>
|
| 459 |
+
</div>
|
| 460 |
+
{% endfor %}
|
| 461 |
+
{% else %}
|
| 462 |
+
<div class="empty-state"><h3>๐ญ ๋ฆฌ์์น ๋ณด๊ณ ์๊ฐ ์์ต๋๋ค</h3></div>
|
| 463 |
+
{% endif %}
|
| 464 |
+
</div>
|
| 465 |
+
|
| 466 |
+
<!-- ์คํฌ๋ฆฌ๋ ํญ -->
|
| 467 |
+
<div id="screening-content" class="tab-content">
|
| 468 |
+
|
| 469 |
+
<!-- ์๋ธ ํญ (DOW 30 / NASDAQ 100) -->
|
| 470 |
+
<div class="sub-tabs">
|
| 471 |
+
<button class="sub-tab active" onclick="switchScreeningTab('dow30', this)">๐๏ธ DOW 30</button>
|
| 472 |
+
<button class="sub-tab" onclick="switchScreeningTab('nasdaq100', this)">๐ป NASDAQ 100</button>
|
| 473 |
+
</div>
|
| 474 |
+
|
| 475 |
+
<!-- DOW 30 ์ฝํ
์ธ -->
|
| 476 |
+
<div id="dow30-content" class="screening-sub-content active">
|
| 477 |
+
<h3 style="color: #00d9ff; margin: 20px 0;">๐๏ธ DOW 30 ์ข
๋ชฉ ์คํฌ๋ฆฌ๋</h3>
|
| 478 |
+
<div class="screening-grid">
|
| 479 |
+
{% set dow30_tickers = ['UNH', 'GS', 'HD', 'CAT', 'CRM', 'MCD', 'V', 'TRV', 'AXP', 'JPM', 'IBM', 'BA', 'MMM', 'JNJ', 'PG', 'MRK', 'NKE', 'DIS', 'KO', 'WMT', 'VZ', 'DOW', 'SHW', 'AAPL', 'MSFT', 'AMZN', 'NVDA', 'HON', 'AMGN', 'CSCO'] %}
|
| 480 |
+
{% for stock in screening_data %}
|
| 481 |
+
{% if stock.ticker in dow30_tickers %}
|
| 482 |
+
<div class="stock-card">
|
| 483 |
+
<div class="stock-card-header">
|
| 484 |
+
<span class="stock-card-ticker">{{ stock.ticker }}</span>
|
| 485 |
+
<span class="stock-card-rank">#{{ stock.rank }}</span>
|
| 486 |
+
</div>
|
| 487 |
+
<div class="stock-card-name">{{ stock.name[:20] }}{% if stock.name|length > 20 %}...{% endif %}</div>
|
| 488 |
+
<div class="stock-card-price">${{ "{:,.2f}".format(stock.price) }}</div>
|
| 489 |
+
<div class="stock-card-change {{ 'positive' if stock.change_percent >= 0 else 'negative' }}">
|
| 490 |
+
{{ "{:+.2f}".format(stock.change_percent) }}%
|
| 491 |
+
</div>
|
| 492 |
+
<!-- TradingView ๋ฏธ๋ ์ฐจํธ -->
|
| 493 |
+
<div class="mini-chart" id="mini-chart-dow-{{ stock.ticker }}"></div>
|
| 494 |
+
<div class="stock-card-metrics">
|
| 495 |
+
<div class="stock-card-metric">
|
| 496 |
+
<span class="stock-card-metric-label">RSI</span>
|
| 497 |
+
<span class="stock-card-metric-value">{{ "{:.1f}".format(stock.rsi) }}</span>
|
| 498 |
+
</div>
|
| 499 |
+
<div class="stock-card-metric">
|
| 500 |
+
<span class="stock-card-metric-label">PER</span>
|
| 501 |
+
<span class="stock-card-metric-value">{{ "{:.1f}".format(stock.pe_ratio) }}</span>
|
| 502 |
+
</div>
|
| 503 |
+
<div class="stock-card-metric">
|
| 504 |
+
<span class="stock-card-metric-label">์์ด</span>
|
| 505 |
+
<span class="stock-card-metric-value">${{ "{:.0f}".format(stock.market_cap / 1e9) }}B</span>
|
| 506 |
+
</div>
|
| 507 |
+
</div>
|
| 508 |
+
<button class="view-chart-btn" onclick="openChartModal('{{ stock.ticker }}', '{{ stock.name }}', '{{ stock.exchange|default('NYSE') }}')">๐ ์ฐจํธ</button>
|
| 509 |
+
</div>
|
| 510 |
+
{% endif %}
|
| 511 |
+
{% endfor %}
|
| 512 |
+
</div>
|
| 513 |
+
</div>
|
| 514 |
+
|
| 515 |
+
<!-- NASDAQ 100 ์ฝํ
์ธ -->
|
| 516 |
+
<div id="nasdaq100-content" class="screening-sub-content">
|
| 517 |
+
<div style="display: flex; justify-content: space-between; align-items: center; margin: 20px 0;">
|
| 518 |
+
<h3 style="color: #00d9ff; margin: 0;">๐ป NASDAQ 100 ์ข
๋ชฉ ์คํฌ๋ฆฌ๋</h3>
|
| 519 |
+
<div class="pagination-tabs">
|
| 520 |
+
<button class="page-tab active" onclick="showNasdaqPage(1, this)">1-35</button>
|
| 521 |
+
<button class="page-tab" onclick="showNasdaqPage(2, this)">36-70</button>
|
| 522 |
+
<button class="page-tab" onclick="showNasdaqPage(3, this)">71-101</button>
|
| 523 |
+
</div>
|
| 524 |
+
</div>
|
| 525 |
+
|
| 526 |
+
<!-- NASDAQ ํ์ด์ง 1: 1-35 -->
|
| 527 |
+
<div class="screening-grid nasdaq-page" id="nasdaq-page-1">
|
| 528 |
+
{% for stock in screening_data[:35] %}
|
| 529 |
+
<div class="stock-card">
|
| 530 |
+
<div class="stock-card-header">
|
| 531 |
+
<span class="stock-card-ticker">{{ stock.ticker }}</span>
|
| 532 |
+
<span class="stock-card-rank">#{{ loop.index }}</span>
|
| 533 |
+
</div>
|
| 534 |
+
<div class="stock-card-name">{{ stock.name[:20] }}{% if stock.name|length > 20 %}...{% endif %}</div>
|
| 535 |
+
<div class="stock-card-price">${{ "{:,.2f}".format(stock.price) }}</div>
|
| 536 |
+
<div class="stock-card-change {{ 'positive' if stock.change_percent >= 0 else 'negative' }}">
|
| 537 |
+
{{ "{:+.2f}".format(stock.change_percent) }}%
|
| 538 |
+
</div>
|
| 539 |
+
<!-- TradingView ๋ฏธ๋ ์ฐจํธ -->
|
| 540 |
+
<div class="mini-chart" id="mini-chart-nq1-{{ stock.ticker }}"></div>
|
| 541 |
+
<div class="stock-card-metrics">
|
| 542 |
+
<div class="stock-card-metric">
|
| 543 |
+
<span class="stock-card-metric-label">RSI</span>
|
| 544 |
+
<span class="stock-card-metric-value">{{ "{:.1f}".format(stock.rsi) }}</span>
|
| 545 |
+
</div>
|
| 546 |
+
<div class="stock-card-metric">
|
| 547 |
+
<span class="stock-card-metric-label">PER</span>
|
| 548 |
+
<span class="stock-card-metric-value">{{ "{:.1f}".format(stock.pe_ratio) }}</span>
|
| 549 |
+
</div>
|
| 550 |
+
<div class="stock-card-metric">
|
| 551 |
+
<span class="stock-card-metric-label">์์ด</span>
|
| 552 |
+
<span class="stock-card-metric-value">${{ "{:.0f}".format(stock.market_cap / 1e9) }}B</span>
|
| 553 |
+
</div>
|
| 554 |
+
</div>
|
| 555 |
+
<button class="view-chart-btn" onclick="openChartModal('{{ stock.ticker }}', '{{ stock.name }}', '{{ stock.exchange|default('NASDAQ') }}')">๐ ์ฐจํธ</button>
|
| 556 |
+
</div>
|
| 557 |
+
{% endfor %}
|
| 558 |
+
</div>
|
| 559 |
+
|
| 560 |
+
<!-- NASDAQ ํ์ด์ง 2: 36-70 -->
|
| 561 |
+
<div class="screening-grid nasdaq-page" id="nasdaq-page-2" style="display: none;">
|
| 562 |
+
{% for stock in screening_data[35:70] %}
|
| 563 |
+
<div class="stock-card">
|
| 564 |
+
<div class="stock-card-header">
|
| 565 |
+
<span class="stock-card-ticker">{{ stock.ticker }}</span>
|
| 566 |
+
<span class="stock-card-rank">#{{ loop.index + 35 }}</span>
|
| 567 |
+
</div>
|
| 568 |
+
<div class="stock-card-name">{{ stock.name[:20] }}{% if stock.name|length > 20 %}...{% endif %}</div>
|
| 569 |
+
<div class="stock-card-price">${{ "{:,.2f}".format(stock.price) }}</div>
|
| 570 |
+
<div class="stock-card-change {{ 'positive' if stock.change_percent >= 0 else 'negative' }}">
|
| 571 |
+
{{ "{:+.2f}".format(stock.change_percent) }}%
|
| 572 |
+
</div>
|
| 573 |
+
<!-- TradingView ๋ฏธ๋ ์ฐจํธ -->
|
| 574 |
+
<div class="mini-chart" id="mini-chart-nq2-{{ stock.ticker }}"></div>
|
| 575 |
+
<div class="stock-card-metrics">
|
| 576 |
+
<div class="stock-card-metric">
|
| 577 |
+
<span class="stock-card-metric-label">RSI</span>
|
| 578 |
+
<span class="stock-card-metric-value">{{ "{:.1f}".format(stock.rsi) }}</span>
|
| 579 |
+
</div>
|
| 580 |
+
<div class="stock-card-metric">
|
| 581 |
+
<span class="stock-card-metric-label">PER</span>
|
| 582 |
+
<span class="stock-card-metric-value">{{ "{:.1f}".format(stock.pe_ratio) }}</span>
|
| 583 |
+
</div>
|
| 584 |
+
<div class="stock-card-metric">
|
| 585 |
+
<span class="stock-card-metric-label">์์ด</span>
|
| 586 |
+
<span class="stock-card-metric-value">${{ "{:.0f}".format(stock.market_cap / 1e9) }}B</span>
|
| 587 |
+
</div>
|
| 588 |
+
</div>
|
| 589 |
+
<button class="view-chart-btn" onclick="openChartModal('{{ stock.ticker }}', '{{ stock.name }}', '{{ stock.exchange|default('NASDAQ') }}')">๐ ์ฐจํธ</button>
|
| 590 |
+
</div>
|
| 591 |
+
{% endfor %}
|
| 592 |
+
</div>
|
| 593 |
+
|
| 594 |
+
<!-- NASDAQ ํ์ด์ง 3: 71-101 -->
|
| 595 |
+
<div class="screening-grid nasdaq-page" id="nasdaq-page-3" style="display: none;">
|
| 596 |
+
{% for stock in screening_data[70:101] %}
|
| 597 |
+
<div class="stock-card">
|
| 598 |
+
<div class="stock-card-header">
|
| 599 |
+
<span class="stock-card-ticker">{{ stock.ticker }}</span>
|
| 600 |
+
<span class="stock-card-rank">#{{ loop.index + 70 }}</span>
|
| 601 |
+
</div>
|
| 602 |
+
<div class="stock-card-name">{{ stock.name[:20] }}{% if stock.name|length > 20 %}...{% endif %}</div>
|
| 603 |
+
<div class="stock-card-price">${{ "{:,.2f}".format(stock.price) }}</div>
|
| 604 |
+
<div class="stock-card-change {{ 'positive' if stock.change_percent >= 0 else 'negative' }}">
|
| 605 |
+
{{ "{:+.2f}".format(stock.change_percent) }}%
|
| 606 |
+
</div>
|
| 607 |
+
<!-- TradingView ๋ฏธ๋ ์ฐจํธ -->
|
| 608 |
+
<div class="mini-chart" id="mini-chart-nq3-{{ stock.ticker }}"></div>
|
| 609 |
+
<div class="stock-card-metrics">
|
| 610 |
+
<div class="stock-card-metric">
|
| 611 |
+
<span class="stock-card-metric-label">RSI</span>
|
| 612 |
+
<span class="stock-card-metric-value">{{ "{:.1f}".format(stock.rsi) }}</span>
|
| 613 |
+
</div>
|
| 614 |
+
<div class="stock-card-metric">
|
| 615 |
+
<span class="stock-card-metric-label">PER</span>
|
| 616 |
+
<span class="stock-card-metric-value">{{ "{:.1f}".format(stock.pe_ratio) }}</span>
|
| 617 |
+
</div>
|
| 618 |
+
<div class="stock-card-metric">
|
| 619 |
+
<span class="stock-card-metric-label">์์ด</span>
|
| 620 |
+
<span class="stock-card-metric-value">${{ "{:.0f}".format(stock.market_cap / 1e9) }}B</span>
|
| 621 |
+
</div>
|
| 622 |
+
</div>
|
| 623 |
+
<button class="view-chart-btn" onclick="openChartModal('{{ stock.ticker }}', '{{ stock.name }}', '{{ stock.exchange|default('NASDAQ') }}')">๐ ์ฐจํธ</button>
|
| 624 |
+
</div>
|
| 625 |
+
{% endfor %}
|
| 626 |
+
</div>
|
| 627 |
+
</div>
|
| 628 |
+
</div>
|
| 629 |
+
|
| 630 |
+
<div class="button-group">
|
| 631 |
+
<button class="refresh-btn" onclick="location.reload()">๐ ์๋ก๊ณ ์นจ</button>
|
| 632 |
+
<button class="refresh-btn secondary" onclick="location.href='/?refresh=true'">๐ฅ ๋ฐ์ดํฐ ๊ฐฑ์ </button>
|
| 633 |
+
<button class="refresh-btn" style="background: linear-gradient(135deg, #9b59b6, #8e44ad);" onclick="triggerAutoRefresh()">โฐ ์ฆ์ ์๋๊ฐฑ์ </button>
|
| 634 |
+
</div>
|
| 635 |
+
|
| 636 |
+
<!-- ์ค์ผ์ค๋ฌ ์ํ ํ์ -->
|
| 637 |
+
<div class="scheduler-status" id="schedulerStatus">
|
| 638 |
+
<span class="scheduler-indicator">โฐ</span>
|
| 639 |
+
<span id="schedulerText">์๋ ๊ฐฑ์ ์ํ ํ์ธ ์ค...</span>
|
| 640 |
+
</div>
|
| 641 |
+
|
| 642 |
+
<div class="timestamp">โฐ ๋ง์ง๋ง ์
๋ฐ์ดํธ: {{ timestamp }}</div>
|
| 643 |
+
<div class="footer">
|
| 644 |
+
<p>๐ AI ์ฃผ์ ๋ฆฌ์์น ์ผํฐ v5.8 | NASDAQ 100 + DOW 30 | ์๋ ๊ฐฑ์ ์์คํ
| Ginigen AI</p>
|
| 645 |
+
<p style="margin-top: 10px;">โ ๏ธ ๋ณธ ์๋น์ค๋ ๏ฟฝ๏ฟฝ์ ์ฐธ๊ณ ์ฉ์ด๋ฉฐ, ํฌ์ ๊ฒฐ์ ์ ๋ณธ์ธ์ ํ๋จ๊ณผ ์ฑ
์ ํ์ ์ด๋ฃจ์ด์ ธ์ผ ํฉ๋๋ค.</p>
|
| 646 |
+
</div>
|
| 647 |
+
</div>
|
| 648 |
+
|
| 649 |
+
<!-- ์ฌ์ธต ๋ถ์ ๋ชจ๋ฌ -->
|
| 650 |
+
<div id="deepAnalysisModal" class="modal">
|
| 651 |
+
<div class="modal-content" id="deepAnalysisContent">
|
| 652 |
+
<div class="modal-header">
|
| 653 |
+
<h2 style="color: #00d9ff;">๐ฌ <span id="modal-ticker"></span> ์ฌ์ธต ๋ถ์</h2>
|
| 654 |
+
<button class="modal-close" onclick="closeModal('deepAnalysisModal')">×</button>
|
| 655 |
+
</div>
|
| 656 |
+
<div id="loading-indicator" class="loading">
|
| 657 |
+
<div class="spinner"></div>
|
| 658 |
+
<p>๐ค SOMA ์์ด์ ํธ๊ฐ ์ฌ์ธต ๋ถ์ ์ค์
๋๋ค...</p>
|
| 659 |
+
</div>
|
| 660 |
+
<div id="deep-analysis-result" style="display: none;">
|
| 661 |
+
<div class="chart-grid">
|
| 662 |
+
<div class="chart-box"><h4>๐ ํฌ์ ์งํ</h4><canvas id="metricsChart"></canvas></div>
|
| 663 |
+
<div class="chart-box"><h4>๐ฏ ๋ชฉํ๊ฐ ๋ถ์</h4><canvas id="priceChart"></canvas></div>
|
| 664 |
+
<div class="chart-box"><h4>โ๏ธ ๋ฆฌ์คํฌ/๊ธฐํ</h4><canvas id="riskChart"></canvas></div>
|
| 665 |
+
<div class="chart-box"><h4>๐ ์ฑ์ฅ ์ ๋ง</h4><canvas id="growthChart"></canvas></div>
|
| 666 |
+
</div>
|
| 667 |
+
<div class="metrics-grid" style="margin-bottom: 25px;">
|
| 668 |
+
<div class="metric-card"><div class="metric-label">ํ์ฌ๊ฐ</div><div class="metric-value" id="result-current-price">-</div></div>
|
| 669 |
+
<div class="metric-card"><div class="metric-label">๋ชฉํ๊ฐ</div><div class="metric-value" style="color: #00ff88;" id="result-target-price">-</div></div>
|
| 670 |
+
<div class="metric-card"><div class="metric-label">ํฌ์์๊ฒฌ</div><div class="metric-value" style="color: #00d9ff;" id="result-rating">-</div></div>
|
| 671 |
+
<div class="metric-card"><div class="metric-label">์์น์ฌ๋ ฅ</div><div class="metric-value" style="color: #2ed573;" id="result-upside">-</div></div>
|
| 672 |
+
</div>
|
| 673 |
+
<div class="deep-section" style="background: rgba(0,217,255,0.1); border: 1px solid rgba(0,217,255,0.3);"><h3>๐ ํต์ฌ ์์ฝ</h3><div class="content" id="result-executive-summary"></div></div>
|
| 674 |
+
<div class="deep-section"><h3>๐ข ํ์ฌ ๊ฐ์</h3><div class="content" id="result-company-overview"></div></div>
|
| 675 |
+
<div class="deep-section"><h3>๐ฐ ์ฌ๋ฌด ๋ถ์</h3><div class="content" id="result-financial-analysis"></div></div>
|
| 676 |
+
<div class="deep-section"><h3>๐ ๊ธฐ์ ์ ๋ถ์</h3><div class="content" id="result-technical-analysis"></div></div>
|
| 677 |
+
<div class="deep-section"><h3>๐ญ ์ฐ์
๋ถ์</h3><div class="content" id="result-industry-analysis"></div></div>
|
| 678 |
+
<div class="deep-section"><h3>โ ๏ธ ๋ฆฌ์คํฌ ํ๊ฐ</h3><div class="content" id="result-risk-assessment"></div></div>
|
| 679 |
+
<div class="deep-section"><h3>๐ก ํฌ์ ๋
ผ๋ฆฌ</h3><div class="content" id="result-investment-thesis"></div></div>
|
| 680 |
+
<div class="deep-section"><h3>๐ฏ ๋ชฉํ ์ฃผ๊ฐ</h3><div class="content" id="result-price-targets"></div></div>
|
| 681 |
+
<div class="deep-section"><h3>๐ ์นดํ๋ฆฌ์คํธ</h3><div class="content" id="result-catalysts"></div></div>
|
| 682 |
+
<div class="deep-section" style="background: rgba(0,255,136,0.1); border: 1px solid rgba(0,255,136,0.3);"><h3>โ
์ต์ข
๊ถ๊ณ </h3><div class="content" id="result-final-recommendation" style="font-weight: 600;"></div></div>
|
| 683 |
+
<div style="text-align: center; margin-top: 30px;">
|
| 684 |
+
<a id="download-docx-btn" class="download-btn" style="display: none;">๐ DOCX</a>
|
| 685 |
+
<button id="download-pdf-btn" class="download-btn pdf" style="display: none;" onclick="downloadAsPDF()">๐ PDF</button>
|
| 686 |
+
</div>
|
| 687 |
+
</div>
|
| 688 |
+
</div>
|
| 689 |
+
</div>
|
| 690 |
+
|
| 691 |
+
<!-- TradingView ์ฐจํธ ๋ชจ๋ฌ -->
|
| 692 |
+
<div id="chartModal" class="modal">
|
| 693 |
+
<div class="modal-content chart-modal-content">
|
| 694 |
+
<div class="modal-header">
|
| 695 |
+
<h2 style="color: #00d9ff;">๐ <span id="chart-modal-title"></span></h2>
|
| 696 |
+
<button class="modal-close" onclick="closeModal('chartModal')">×</button>
|
| 697 |
+
</div>
|
| 698 |
+
<div id="tradingview-chart"></div>
|
| 699 |
+
</div>
|
| 700 |
+
</div>
|
| 701 |
+
|
| 702 |
+
<script>
|
| 703 |
+
// ์ ์ญ ๋ณ์
|
| 704 |
+
let currentTicker = '';
|
| 705 |
+
let sentimentGaugeInstance = null;
|
| 706 |
+
let metricsChartInstance = null, priceChartInstance = null, riskChartInstance = null, growthChartInstance = null;
|
| 707 |
+
let tvWidget = null;
|
| 708 |
+
|
| 709 |
+
const newsData = {{ breaking_news | tojson | safe }};
|
| 710 |
+
const screeningData = {{ screening_data | tojson | safe }};
|
| 711 |
+
|
| 712 |
+
if (typeof marked !== 'undefined') {
|
| 713 |
+
marked.setOptions({ breaks: true, gfm: true, tables: true });
|
| 714 |
+
}
|
| 715 |
+
|
| 716 |
+
// ์ค์ผ์ค๋ฌ ์ํ ์กฐํ
|
| 717 |
+
async function updateSchedulerStatus() {
|
| 718 |
+
try {
|
| 719 |
+
const response = await fetch('/api/scheduler/status');
|
| 720 |
+
const data = await response.json();
|
| 721 |
+
|
| 722 |
+
if (data.success) {
|
| 723 |
+
const scheduler = data.scheduler;
|
| 724 |
+
const indicator = document.querySelector('.scheduler-indicator');
|
| 725 |
+
const text = document.getElementById('schedulerText');
|
| 726 |
+
|
| 727 |
+
if (scheduler.is_running) {
|
| 728 |
+
indicator.classList.add('active');
|
| 729 |
+
indicator.classList.remove('inactive');
|
| 730 |
+
indicator.textContent = '๐ข';
|
| 731 |
+
|
| 732 |
+
let statusText = `์๋ ๊ฐฑ์ ํ์ฑ (${scheduler.interval_hours}์๊ฐ ์ฃผ๊ธฐ)`;
|
| 733 |
+
if (scheduler.next_run) {
|
| 734 |
+
const nextRun = new Date(scheduler.next_run);
|
| 735 |
+
statusText += ` | ๋ค์: ${nextRun.toLocaleTimeString('ko-KR')}`;
|
| 736 |
+
}
|
| 737 |
+
if (scheduler.refresh_count > 0) {
|
| 738 |
+
statusText += ` | ๊ฐฑ์ ${scheduler.refresh_count}ํ`;
|
| 739 |
+
}
|
| 740 |
+
text.textContent = statusText;
|
| 741 |
+
} else {
|
| 742 |
+
indicator.classList.remove('active');
|
| 743 |
+
indicator.classList.add('inactive');
|
| 744 |
+
indicator.textContent = '๐ด';
|
| 745 |
+
text.textContent = '์๋ ๊ฐฑ์ ๋นํ์ฑ';
|
| 746 |
+
}
|
| 747 |
+
}
|
| 748 |
+
} catch (e) {
|
| 749 |
+
console.log('์ค์ผ์ค๋ฌ ์ํ ์กฐํ ์คํจ:', e);
|
| 750 |
+
}
|
| 751 |
+
}
|
| 752 |
+
|
| 753 |
+
// ์ฆ์ ์๋๊ฐฑ์ ํธ๋ฆฌ๊ฑฐ
|
| 754 |
+
async function triggerAutoRefresh() {
|
| 755 |
+
if (!confirm('์ง๊ธ ์ฆ์ ์ ์ฒด ๋ฐ์ดํฐ๋ฅผ ๊ฐฑ์ ํ์๊ฒ ์ต๋๊น?\n(๋ฐฑ๊ทธ๋ผ์ด๋์์ ์คํ๋๋ฉฐ ๋ช ๋ถ์ด ์์๋ฉ๋๋ค)')) {
|
| 756 |
+
return;
|
| 757 |
+
}
|
| 758 |
+
|
| 759 |
+
try {
|
| 760 |
+
const response = await fetch('/api/scheduler/trigger');
|
| 761 |
+
const data = await response.json();
|
| 762 |
+
|
| 763 |
+
if (data.success) {
|
| 764 |
+
alert('โ
์๋ ๊ฐฑ์ ์ด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์์๋์์ต๋๋ค.\n\n์๋ฃ๋๋ฉด ํ์ด์ง๋ฅผ ์๋ก๊ณ ์นจํ์ธ์.');
|
| 765 |
+
} else {
|
| 766 |
+
alert('โ ๊ฐฑ์ ํธ๋ฆฌ๊ฑฐ ์คํจ: ' + (data.error || '์ ์ ์๋ ์ค๋ฅ'));
|
| 767 |
+
}
|
| 768 |
+
} catch (e) {
|
| 769 |
+
alert('โ ์๋ฒ ์ฐ๊ฒฐ ์ค๋ฅ: ' + e.message);
|
| 770 |
+
}
|
| 771 |
+
}
|
| 772 |
+
|
| 773 |
+
// ํ์ด์ง ๋ก๋ ์ ์ค์ผ์ค๋ฌ ์ํ ์กฐํ
|
| 774 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 775 |
+
updateSchedulerStatus();
|
| 776 |
+
// 1๋ถ๋ง๋ค ์ค์ผ์ค๋ฌ ์ํ ์
๋ฐ์ดํธ
|
| 777 |
+
setInterval(updateSchedulerStatus, 60000);
|
| 778 |
+
});
|
| 779 |
+
|
| 780 |
+
function switchTab(event, tabName) {
|
| 781 |
+
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
| 782 |
+
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
| 783 |
+
event.currentTarget.classList.add('active');
|
| 784 |
+
document.getElementById(tabName + '-content').classList.add('active');
|
| 785 |
+
if (tabName === 'market') setTimeout(initMarketTab, 100);
|
| 786 |
+
if (tabName === 'screening') setTimeout(() => loadMiniChartsForPage('dow'), 500);
|
| 787 |
+
}
|
| 788 |
+
|
| 789 |
+
function searchNews(title) {
|
| 790 |
+
window.open(`https://www.google.com/search?q=${encodeURIComponent(title)}&tbm=nws`, '_blank');
|
| 791 |
+
}
|
| 792 |
+
|
| 793 |
+
// NASDAQ 100 ํ์ด์ง ์ ํ
|
| 794 |
+
function showNasdaqPage(pageNum, btn) {
|
| 795 |
+
// ๋ชจ๋ ํ์ด์ง ์จ๊ธฐ๊ธฐ
|
| 796 |
+
document.querySelectorAll('.nasdaq-page').forEach(p => p.style.display = 'none');
|
| 797 |
+
// ์ ํํ ํ์ด์ง ํ์
|
| 798 |
+
document.getElementById('nasdaq-page-' + pageNum).style.display = 'grid';
|
| 799 |
+
// ํญ ํ์ฑํ
|
| 800 |
+
document.querySelectorAll('.page-tab').forEach(t => t.classList.remove('active'));
|
| 801 |
+
btn.classList.add('active');
|
| 802 |
+
// ํด๋น ํ์ด์ง ๋ฏธ๋์ฐจํธ ๋ก๋
|
| 803 |
+
setTimeout(() => loadMiniChartsForPage('nq' + pageNum), 300);
|
| 804 |
+
}
|
| 805 |
+
|
| 806 |
+
// ์คํฌ๋ฆฌ๋ ์๋ธํญ ์ ํ (DOW 30 / NASDAQ 100)
|
| 807 |
+
function switchScreeningTab(tabName, btn) {
|
| 808 |
+
// ๋ชจ๋ ์๋ธ ์ฝํ
์ธ ์จ๊ธฐ๊ธฐ
|
| 809 |
+
document.querySelectorAll('.screening-sub-content').forEach(c => c.classList.remove('active'));
|
| 810 |
+
// ์ ํํ ์ฝํ
์ธ ํ์
|
| 811 |
+
document.getElementById(tabName + '-content').classList.add('active');
|
| 812 |
+
// ํญ ํ์ฑํ
|
| 813 |
+
document.querySelectorAll('.sub-tab').forEach(t => t.classList.remove('active'));
|
| 814 |
+
btn.classList.add('active');
|
| 815 |
+
// ํด๋น ํญ ๋ฏธ๋์ฐจํธ ๋ก๋
|
| 816 |
+
if (tabName === 'dow30') {
|
| 817 |
+
setTimeout(() => loadMiniChartsForPage('dow'), 300);
|
| 818 |
+
} else {
|
| 819 |
+
setTimeout(() => loadMiniChartsForPage('nq1'), 300);
|
| 820 |
+
}
|
| 821 |
+
}
|
| 822 |
+
|
| 823 |
+
// ํน์ ํ์ด์ง์ ๋ฏธ๋์ฐจํธ๋ง ๋ก๋
|
| 824 |
+
// TradingView ์ฌ๋ณผ ๋งคํ (์ค์ ๊ฑฐ๋์์ ๋ค๋ฅผ ์ ์์)
|
| 825 |
+
// NYSE ์ข
๋ชฉ (TradingView์์ NYSE๋ก ํ์๋๋ ์ข
๋ชฉ๋ง)
|
| 826 |
+
const NYSE_TICKERS = ['UNH', 'GS', 'HD', 'CAT', 'CRM', 'MCD', 'V', 'TRV', 'AXP', 'JPM', 'IBM', 'BA', 'MMM', 'JNJ', 'PG', 'MRK', 'NKE', 'DIS', 'KO', 'WMT', 'VZ', 'DOW', 'SHW', 'TRI', 'LIN', 'HON', 'AEP', 'CEG'];
|
| 827 |
+
|
| 828 |
+
// TradingView ํน์ ์ฌ๋ณผ ๋งคํ (์ค์ ๊ฑฐ๋์์ TradingView ํ์๊ฐ ๋ค๋ฅธ ๊ฒฝ์ฐ)
|
| 829 |
+
const TRADINGVIEW_SYMBOLS = {
|
| 830 |
+
'WMT': 'NASDAQ:WMT', // Walmart - ์ค์ NYSE์ง๋ง TradingView๋ NASDAQ์ผ๋ก ํ์
|
| 831 |
+
'TRI': 'NYSE:TRI', // Thomson Reuters
|
| 832 |
+
'LIN': 'NYSE:LIN', // Linde
|
| 833 |
+
'HON': 'NASDAQ:HON', // Honeywell
|
| 834 |
+
'AEP': 'NASDAQ:AEP', // American Electric Power
|
| 835 |
+
'CEG': 'NASDAQ:CEG', // Constellation Energy
|
| 836 |
+
'ASML': 'NASDAQ:ASML', // ASML (๋ค๋๋๋์ง๋ง NASDAQ ADR)
|
| 837 |
+
'AZN': 'NASDAQ:AZN', // AstraZeneca
|
| 838 |
+
'PDD': 'NASDAQ:PDD' // PDD Holdings
|
| 839 |
+
};
|
| 840 |
+
|
| 841 |
+
// ํฐ์ปค์ ๋ง๋ TradingView ์ฌ๋ณผ ๋ฐํ
|
| 842 |
+
function getTradingViewSymbol(ticker, defaultExchange) {
|
| 843 |
+
// ํน์ ๋งคํ์ด ์์ผ๋ฉด ์ฌ์ฉ
|
| 844 |
+
if (TRADINGVIEW_SYMBOLS[ticker]) {
|
| 845 |
+
return TRADINGVIEW_SYMBOLS[ticker];
|
| 846 |
+
}
|
| 847 |
+
// NYSE ์ข
๋ชฉ์ด๋ฉด NYSE ์ฌ์ฉ
|
| 848 |
+
if (NYSE_TICKERS.includes(ticker)) {
|
| 849 |
+
return `NYSE:${ticker}`;
|
| 850 |
+
}
|
| 851 |
+
// ๊ธฐ๋ณธ๊ฐ ์ฌ์ฉ
|
| 852 |
+
return `${defaultExchange || 'NASDAQ'}:${ticker}`;
|
| 853 |
+
}
|
| 854 |
+
|
| 855 |
+
function loadMiniChartsForPage(prefix) {
|
| 856 |
+
if (!screeningData) return;
|
| 857 |
+
|
| 858 |
+
const containers = document.querySelectorAll(`[id^="mini-chart-${prefix}-"]`);
|
| 859 |
+
containers.forEach(container => {
|
| 860 |
+
if (container.dataset.loaded) return;
|
| 861 |
+
container.dataset.loaded = 'true';
|
| 862 |
+
|
| 863 |
+
const ticker = container.id.replace(`mini-chart-${prefix}-`, '');
|
| 864 |
+
const stock = screeningData.find(s => s.ticker === ticker);
|
| 865 |
+
if (!stock) return;
|
| 866 |
+
|
| 867 |
+
// TradingView ์ฌ๋ณผ ๊ฐ์ ธ์ค๊ธฐ
|
| 868 |
+
const symbol = getTradingViewSymbol(ticker, stock.exchange);
|
| 869 |
+
|
| 870 |
+
try {
|
| 871 |
+
new TradingView.MediumWidget({
|
| 872 |
+
symbols: [[ticker, `${symbol}|3D`]],
|
| 873 |
+
chartOnly: true,
|
| 874 |
+
width: '100%',
|
| 875 |
+
height: 60,
|
| 876 |
+
locale: 'kr',
|
| 877 |
+
colorTheme: 'dark',
|
| 878 |
+
autosize: false,
|
| 879 |
+
showVolume: false,
|
| 880 |
+
hideDateRanges: true,
|
| 881 |
+
scalePosition: 'no',
|
| 882 |
+
scaleMode: 'Normal',
|
| 883 |
+
fontFamily: 'sans-serif',
|
| 884 |
+
noTimeScale: true,
|
| 885 |
+
chartType: 'area',
|
| 886 |
+
lineColor: stock.change_percent >= 0 ? '#00ff88' : '#ff4757',
|
| 887 |
+
bottomColor: stock.change_percent >= 0 ? 'rgba(0,255,136,0.1)' : 'rgba(255,71,87,0.1)',
|
| 888 |
+
lineWidth: 2,
|
| 889 |
+
container_id: container.id
|
| 890 |
+
});
|
| 891 |
+
} catch (e) {
|
| 892 |
+
console.log('๋ฏธ๋์ฐจํธ ๋ก๋ ์คํจ:', ticker, e);
|
| 893 |
+
}
|
| 894 |
+
});
|
| 895 |
+
}
|
| 896 |
+
|
| 897 |
+
function initMarketTab() {
|
| 898 |
+
try {
|
| 899 |
+
initSentimentGauge();
|
| 900 |
+
initSectorChart();
|
| 901 |
+
initUpDownChart();
|
| 902 |
+
initKeywordCloud();
|
| 903 |
+
initTradingViewWidgets();
|
| 904 |
+
} catch (e) {
|
| 905 |
+
console.error('initMarketTab ์ค๋ฅ:', e);
|
| 906 |
+
}
|
| 907 |
+
}
|
| 908 |
+
|
| 909 |
+
// ์นํฐ๋ณ ํผํฌ๋จผ์ค ์ฐจํธ
|
| 910 |
+
let sectorChartInstance = null;
|
| 911 |
+
function initSectorChart() {
|
| 912 |
+
try {
|
| 913 |
+
const canvas = document.getElementById('sectorChart');
|
| 914 |
+
if (!canvas || !screeningData || screeningData.length === 0) return;
|
| 915 |
+
if (sectorChartInstance) sectorChartInstance.destroy();
|
| 916 |
+
|
| 917 |
+
// ์นํฐ๋ณ ํ๊ท ๋ฑ๋ฝ๋ฅ ๊ณ์ฐ
|
| 918 |
+
const sectorData = {};
|
| 919 |
+
screeningData.forEach(s => {
|
| 920 |
+
const sector = s.sector || 'Other';
|
| 921 |
+
if (!sectorData[sector]) sectorData[sector] = { sum: 0, count: 0 };
|
| 922 |
+
sectorData[sector].sum += s.change_percent || 0;
|
| 923 |
+
sectorData[sector].count++;
|
| 924 |
+
});
|
| 925 |
+
|
| 926 |
+
const sectors = Object.keys(sectorData).map(k => ({
|
| 927 |
+
name: k,
|
| 928 |
+
avg: sectorData[k].sum / sectorData[k].count
|
| 929 |
+
})).sort((a, b) => b.avg - a.avg);
|
| 930 |
+
|
| 931 |
+
if (sectors.length === 0) return;
|
| 932 |
+
|
| 933 |
+
const labels = sectors.map(s => s.name.replace('Consumer ', '').replace('Communication', 'Comm'));
|
| 934 |
+
const data = sectors.map(s => s.avg);
|
| 935 |
+
const colors = data.map(v => v >= 0 ? '#00ff88' : '#ff4757');
|
| 936 |
+
|
| 937 |
+
sectorChartInstance = new Chart(canvas, {
|
| 938 |
+
type: 'bar',
|
| 939 |
+
data: {
|
| 940 |
+
labels: labels,
|
| 941 |
+
datasets: [{
|
| 942 |
+
data: data,
|
| 943 |
+
backgroundColor: colors,
|
| 944 |
+
borderRadius: 4,
|
| 945 |
+
barThickness: 18
|
| 946 |
+
}]
|
| 947 |
+
},
|
| 948 |
+
options: {
|
| 949 |
+
indexAxis: 'y',
|
| 950 |
+
responsive: true,
|
| 951 |
+
maintainAspectRatio: false,
|
| 952 |
+
plugins: { legend: { display: false } },
|
| 953 |
+
scales: {
|
| 954 |
+
x: {
|
| 955 |
+
grid: { color: 'rgba(255,255,255,0.1)' },
|
| 956 |
+
ticks: { color: '#888', callback: v => v + '%' }
|
| 957 |
+
},
|
| 958 |
+
y: {
|
| 959 |
+
grid: { display: false },
|
| 960 |
+
ticks: { color: '#ccc', font: { size: 11 } }
|
| 961 |
+
}
|
| 962 |
+
}
|
| 963 |
+
}
|
| 964 |
+
});
|
| 965 |
+
} catch (e) {
|
| 966 |
+
console.error('initSectorChart ์ค๋ฅ:', e);
|
| 967 |
+
}
|
| 968 |
+
}
|
| 969 |
+
|
| 970 |
+
// ์์น/ํ๋ฝ ๋น์จ ๋๋ ์ฐจํธ
|
| 971 |
+
let upDownChartInstance = null;
|
| 972 |
+
function initUpDownChart() {
|
| 973 |
+
try {
|
| 974 |
+
const canvas = document.getElementById('upDownChart');
|
| 975 |
+
if (!canvas || !screeningData || screeningData.length === 0) return;
|
| 976 |
+
if (upDownChartInstance) upDownChartInstance.destroy();
|
| 977 |
+
|
| 978 |
+
let up = 0, down = 0, flat = 0;
|
| 979 |
+
screeningData.forEach(s => {
|
| 980 |
+
const chg = s.change_percent || 0;
|
| 981 |
+
if (chg > 0.1) up++;
|
| 982 |
+
else if (chg < -0.1) down++;
|
| 983 |
+
else flat++;
|
| 984 |
+
});
|
| 985 |
+
|
| 986 |
+
upDownChartInstance = new Chart(canvas, {
|
| 987 |
+
type: 'doughnut',
|
| 988 |
+
data: {
|
| 989 |
+
labels: ['์์น', 'ํ๋ฝ', '๋ณดํฉ'],
|
| 990 |
+
datasets: [{
|
| 991 |
+
data: [up, down, flat],
|
| 992 |
+
backgroundColor: ['#00ff88', '#ff4757', '#ffa502'],
|
| 993 |
+
borderWidth: 0
|
| 994 |
+
}]
|
| 995 |
+
},
|
| 996 |
+
options: {
|
| 997 |
+
responsive: true,
|
| 998 |
+
maintainAspectRatio: false,
|
| 999 |
+
cutout: '60%',
|
| 1000 |
+
plugins: {
|
| 1001 |
+
legend: { display: false }
|
| 1002 |
+
}
|
| 1003 |
+
}
|
| 1004 |
+
});
|
| 1005 |
+
|
| 1006 |
+
// ๋ ์ ๋ ํ์
|
| 1007 |
+
const legendEl = document.getElementById('upDownLegend');
|
| 1008 |
+
if (legendEl) {
|
| 1009 |
+
legendEl.innerHTML = `
|
| 1010 |
+
<div class="legend-item"><div class="legend-dot" style="background:#00ff88"></div>์์น ${up}๊ฐ</div>
|
| 1011 |
+
<div class="legend-item"><div class="legend-dot" style="background:#ff4757"></div>ํ๋ฝ ${down}๊ฐ</div>
|
| 1012 |
+
<div class="legend-item"><div class="legend-dot" style="background:#ffa502"></div>๋ณดํฉ ${flat}๊ฐ</div>
|
| 1013 |
+
`;
|
| 1014 |
+
}
|
| 1015 |
+
} catch (e) {
|
| 1016 |
+
console.error('initUpDownChart ์ค๋ฅ:', e);
|
| 1017 |
+
}
|
| 1018 |
+
}
|
| 1019 |
+
|
| 1020 |
+
// TradingView ์์ ฏ ์ด๊ธฐํ
|
| 1021 |
+
function initTradingViewWidgets() {
|
| 1022 |
+
try {
|
| 1023 |
+
// ํฐ์ปค ํ
์ดํ
|
| 1024 |
+
const tickerTape = document.getElementById('ticker-tape-widget');
|
| 1025 |
+
if (tickerTape && !tickerTape.dataset.loaded) {
|
| 1026 |
+
tickerTape.dataset.loaded = 'true';
|
| 1027 |
+
tickerTape.innerHTML = `
|
| 1028 |
+
<div class="tradingview-widget-container">
|
| 1029 |
+
<div class="tradingview-widget-container__widget"></div>
|
| 1030 |
+
</div>
|
| 1031 |
+
`;
|
| 1032 |
+
const script = document.createElement('script');
|
| 1033 |
+
script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-ticker-tape.js';
|
| 1034 |
+
script.async = true;
|
| 1035 |
+
script.innerHTML = JSON.stringify({
|
| 1036 |
+
"symbols": [
|
| 1037 |
+
{"proName": "FOREXCOM:SPXUSD", "title": "S&P 500"},
|
| 1038 |
+
{"proName": "FOREXCOM:NSXUSD", "title": "NASDAQ"},
|
| 1039 |
+
{"proName": "BITSTAMP:BTCUSD", "title": "Bitcoin"},
|
| 1040 |
+
{"proName": "NASDAQ:NVDA", "title": "NVIDIA"},
|
| 1041 |
+
{"proName": "NASDAQ:AAPL", "title": "Apple"},
|
| 1042 |
+
{"proName": "NASDAQ:MSFT", "title": "Microsoft"},
|
| 1043 |
+
{"proName": "NASDAQ:GOOGL", "title": "Alphabet"},
|
| 1044 |
+
{"proName": "NASDAQ:AMZN", "title": "Amazon"},
|
| 1045 |
+
{"proName": "NASDAQ:META", "title": "Meta"},
|
| 1046 |
+
{"proName": "NASDAQ:TSLA", "title": "Tesla"}
|
| 1047 |
+
],
|
| 1048 |
+
"showSymbolLogo": true,
|
| 1049 |
+
"isTransparent": true,
|
| 1050 |
+
"displayMode": "adaptive",
|
| 1051 |
+
"colorTheme": "dark",
|
| 1052 |
+
"locale": "kr"
|
| 1053 |
+
});
|
| 1054 |
+
tickerTape.querySelector('.tradingview-widget-container').appendChild(script);
|
| 1055 |
+
}
|
| 1056 |
+
|
| 1057 |
+
// ๋ง์ผ ์ค๋ฒ๋ทฐ
|
| 1058 |
+
const marketOverview = document.getElementById('market-overview-widget');
|
| 1059 |
+
if (marketOverview && !marketOverview.dataset.loaded) {
|
| 1060 |
+
marketOverview.dataset.loaded = 'true';
|
| 1061 |
+
marketOverview.innerHTML = `<div class="tradingview-widget-container" style="height:100%;width:100%"><div class="tradingview-widget-container__widget" style="height:100%;width:100%"></div></div>`;
|
| 1062 |
+
const script = document.createElement('script');
|
| 1063 |
+
script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-market-overview.js';
|
| 1064 |
+
script.async = true;
|
| 1065 |
+
script.innerHTML = JSON.stringify({
|
| 1066 |
+
"colorTheme": "dark",
|
| 1067 |
+
"dateRange": "1D",
|
| 1068 |
+
"showChart": true,
|
| 1069 |
+
"locale": "kr",
|
| 1070 |
+
"largeChartUrl": "",
|
| 1071 |
+
"isTransparent": true,
|
| 1072 |
+
"showSymbolLogo": true,
|
| 1073 |
+
"showFloatingTooltip": true,
|
| 1074 |
+
"width": "100%",
|
| 1075 |
+
"height": "100%",
|
| 1076 |
+
"tabs": [
|
| 1077 |
+
{
|
| 1078 |
+
"title": "์ง์",
|
| 1079 |
+
"symbols": [
|
| 1080 |
+
{"s": "FOREXCOM:SPXUSD", "d": "S&P 500"},
|
| 1081 |
+
{"s": "FOREXCOM:NSXUSD", "d": "NASDAQ"},
|
| 1082 |
+
{"s": "FOREXCOM:DJI", "d": "Dow Jones"},
|
| 1083 |
+
{"s": "INDEX:VIX", "d": "VIX"}
|
| 1084 |
+
],
|
| 1085 |
+
"originalTitle": "Indices"
|
| 1086 |
+
},
|
| 1087 |
+
{
|
| 1088 |
+
"title": "๋น
ํ
ํฌ",
|
| 1089 |
+
"symbols": [
|
| 1090 |
+
{"s": "NASDAQ:NVDA", "d": "NVIDIA"},
|
| 1091 |
+
{"s": "NASDAQ:AAPL", "d": "Apple"},
|
| 1092 |
+
{"s": "NASDAQ:MSFT", "d": "Microsoft"},
|
| 1093 |
+
{"s": "NASDAQ:GOOGL", "d": "Alphabet"},
|
| 1094 |
+
{"s": "NASDAQ:META", "d": "Meta"}
|
| 1095 |
+
],
|
| 1096 |
+
"originalTitle": "Big Tech"
|
| 1097 |
+
}
|
| 1098 |
+
]
|
| 1099 |
+
});
|
| 1100 |
+
marketOverview.querySelector('.tradingview-widget-container').appendChild(script);
|
| 1101 |
+
}
|
| 1102 |
+
|
| 1103 |
+
// ํํธ๋งต
|
| 1104 |
+
const heatmap = document.getElementById('stock-heatmap-widget');
|
| 1105 |
+
if (heatmap && !heatmap.dataset.loaded) {
|
| 1106 |
+
heatmap.dataset.loaded = 'true';
|
| 1107 |
+
heatmap.innerHTML = `<div class="tradingview-widget-container" style="height:100%;width:100%"><div class="tradingview-widget-container__widget" style="height:100%;width:100%"></div></div>`;
|
| 1108 |
+
const script = document.createElement('script');
|
| 1109 |
+
script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-stock-heatmap.js';
|
| 1110 |
+
script.async = true;
|
| 1111 |
+
script.innerHTML = JSON.stringify({
|
| 1112 |
+
"exchanges": [],
|
| 1113 |
+
"dataSource": "SPX500",
|
| 1114 |
+
"grouping": "sector",
|
| 1115 |
+
"blockSize": "market_cap_basic",
|
| 1116 |
+
"blockColor": "change",
|
| 1117 |
+
"locale": "kr",
|
| 1118 |
+
"symbolUrl": "",
|
| 1119 |
+
"colorTheme": "dark",
|
| 1120 |
+
"hasTopBar": false,
|
| 1121 |
+
"isDataSet498or": true,
|
| 1122 |
+
"isZoomEnabled": true,
|
| 1123 |
+
"hasSymbolTooltip": true,
|
| 1124 |
+
"isMonoSize": false,
|
| 1125 |
+
"width": "100%",
|
| 1126 |
+
"height": "100%"
|
| 1127 |
+
});
|
| 1128 |
+
heatmap.querySelector('.tradingview-widget-container').appendChild(script);
|
| 1129 |
+
}
|
| 1130 |
+
} catch (e) {
|
| 1131 |
+
console.error('initTradingViewWidgets ์ค๋ฅ:', e);
|
| 1132 |
+
}
|
| 1133 |
+
}
|
| 1134 |
+
|
| 1135 |
+
// ํํธ๋งต ์ ํ ํจ์
|
| 1136 |
+
function switchHeatmap(dataSource, btn) {
|
| 1137 |
+
// ํญ ํ์ฑํ
|
| 1138 |
+
document.querySelectorAll('.heatmap-tab').forEach(t => t.classList.remove('active'));
|
| 1139 |
+
btn.classList.add('active');
|
| 1140 |
+
|
| 1141 |
+
// ํํธ๋งต ์ฌ๋ก๋
|
| 1142 |
+
const heatmap = document.getElementById('stock-heatmap-widget');
|
| 1143 |
+
heatmap.innerHTML = `<div class="tradingview-widget-container" style="height:100%;width:100%"><div class="tradingview-widget-container__widget" style="height:100%;width:100%"></div></div>`;
|
| 1144 |
+
const script = document.createElement('script');
|
| 1145 |
+
script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-stock-heatmap.js';
|
| 1146 |
+
script.async = true;
|
| 1147 |
+
script.innerHTML = JSON.stringify({
|
| 1148 |
+
"exchanges": [],
|
| 1149 |
+
"dataSource": dataSource,
|
| 1150 |
+
"grouping": "sector",
|
| 1151 |
+
"blockSize": "market_cap_basic",
|
| 1152 |
+
"blockColor": "change",
|
| 1153 |
+
"locale": "kr",
|
| 1154 |
+
"symbolUrl": "",
|
| 1155 |
+
"colorTheme": "dark",
|
| 1156 |
+
"hasTopBar": false,
|
| 1157 |
+
"isDataSetEnabled": true,
|
| 1158 |
+
"isZoomEnabled": true,
|
| 1159 |
+
"hasSymbolTooltip": true,
|
| 1160 |
+
"isMonoSize": false,
|
| 1161 |
+
"width": "100%",
|
| 1162 |
+
"height": "100%"
|
| 1163 |
+
});
|
| 1164 |
+
heatmap.querySelector('.tradingview-widget-container').appendChild(script);
|
| 1165 |
+
}
|
| 1166 |
+
|
| 1167 |
+
function initSentimentGauge() {
|
| 1168 |
+
const canvas = document.getElementById('sentimentGauge');
|
| 1169 |
+
if (!canvas) return;
|
| 1170 |
+
if (sentimentGaugeInstance) sentimentGaugeInstance.destroy();
|
| 1171 |
+
|
| 1172 |
+
let score = 50;
|
| 1173 |
+
let pos = 0, neg = 0;
|
| 1174 |
+
newsData.forEach(n => { if (n.sentiment === 'positive') pos++; else if (n.sentiment === 'negative') neg++; });
|
| 1175 |
+
if (newsData.length > 0) score = ((pos * 100) + ((newsData.length - pos - neg) * 50)) / newsData.length;
|
| 1176 |
+
|
| 1177 |
+
if (screeningData && screeningData.length > 0) {
|
| 1178 |
+
const avgChange = screeningData.reduce((s, x) => s + (x.change_percent || 0), 0) / screeningData.length;
|
| 1179 |
+
score += avgChange * 5;
|
| 1180 |
+
}
|
| 1181 |
+
score = Math.max(0, Math.min(100, score));
|
| 1182 |
+
|
| 1183 |
+
sentimentGaugeInstance = new Chart(canvas, {
|
| 1184 |
+
type: 'doughnut',
|
| 1185 |
+
data: { datasets: [{ data: [score, 100 - score], backgroundColor: [score < 20 ? '#ff4757' : score < 40 ? '#ffa502' : score < 60 ? '#78909c' : score < 80 ? '#2ed573' : '#00ff88', 'rgba(255,255,255,0.05)'], borderWidth: 0 }] },
|
| 1186 |
+
options: { circumference: 180, rotation: -90, cutout: '75%', responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { enabled: false } } }
|
| 1187 |
+
});
|
| 1188 |
+
|
| 1189 |
+
document.getElementById('sentimentValue').textContent = score.toFixed(1);
|
| 1190 |
+
let label = 'Neutral', color = '#78909c';
|
| 1191 |
+
if (score < 20) { label = 'Strong Sell'; color = '#ff4757'; }
|
| 1192 |
+
else if (score < 40) { label = 'Sell'; color = '#ffa502'; }
|
| 1193 |
+
else if (score < 60) { label = 'Neutral'; color = '#78909c'; }
|
| 1194 |
+
else if (score < 80) { label = 'Buy'; color = '#2ed573'; }
|
| 1195 |
+
else { label = 'Strong Buy'; color = '#00ff88'; }
|
| 1196 |
+
document.getElementById('sentimentLabel').textContent = label;
|
| 1197 |
+
document.getElementById('sentimentLabel').style.color = color;
|
| 1198 |
+
}
|
| 1199 |
+
|
| 1200 |
+
function initKeywordCloud() {
|
| 1201 |
+
const container = document.getElementById('keywordCloud');
|
| 1202 |
+
if (!container) return;
|
| 1203 |
+
container.innerHTML = '';
|
| 1204 |
+
const keywords = {};
|
| 1205 |
+
|
| 1206 |
+
// ํ๊ธ + ์์ด ํค์๋ ํ์ฅ
|
| 1207 |
+
const kwList = [
|
| 1208 |
+
// ๊ฑฐ์๊ฒฝ์
|
| 1209 |
+
'๊ธ๋ฆฌ', '์ธํ๋ ์ด์
', 'Fed', 'FOMC', '๊ธ๋ฆฌ์ธํ', '๊ธ๋ฆฌ๋๊ฒฐ', 'CPI', 'GDP', '๊ณ ์ฉ', '์ค์
๋ฅ ',
|
| 1210 |
+
'interest rate', 'inflation', 'rate cut', 'rate hike', 'economy',
|
| 1211 |
+
// ๋ฌด์ญ/์ง์ ํ
|
| 1212 |
+
'๊ด์ธ', '๋ฌด์ญ', '์์ถ', '์์
', '์ ์ฌ', 'ํธ๋ผํ', 'Trump', '์ค๊ตญ', 'China', '๋ฌ์์',
|
| 1213 |
+
'์ฐํฌ๋ผ์ด๋', '์ ์', 'tariff', 'trade war',
|
| 1214 |
+
// ์๋์ง
|
| 1215 |
+
'์ ๊ฐ', 'OPEC', '์์ ', '์ฒ์ฐ๊ฐ์ค', 'oil', 'crude', 'energy',
|
| 1216 |
+
// ๊ธฐ์ /AI
|
| 1217 |
+
'AI', '์ธ๊ณต์ง๋ฅ', '๋ฐ๋์ฒด', 'GPU', 'chip', 'semiconductor', 'NVIDIA', 'AMD', 'Intel',
|
| 1218 |
+
'OpenAI', 'ChatGPT', 'Gemini', '๋ฐ์ดํฐ์ผํฐ', 'cloud',
|
| 1219 |
+
// ์ ๊ธฐ์ฐจ/๋ฐฐํฐ๋ฆฌ
|
| 1220 |
+
'๋ฐฐํฐ๋ฆฌ', 'EV', '์ ๊ธฐ์ฐจ', 'Tesla', '๋ฆฌํฌ', '์ถฉ์ ', 'electric vehicle',
|
| 1221 |
+
// ์ํธํํ
|
| 1222 |
+
'Bitcoin', 'BTC', 'crypto', '์ํธํํ', 'ETF', 'Ethereum',
|
| 1223 |
+
// ๊ธฐ์
์ค์
|
| 1224 |
+
'์ค์ ', '๋งค์ถ', '์์ด์ต', 'earnings', 'revenue', 'profit', 'beat', 'miss', 'guidance',
|
| 1225 |
+
// M&A/IPO
|
| 1226 |
+
'M&A', 'IPO', '์ธ์', 'ํฉ๋ณ', 'acquisition', 'merger',
|
| 1227 |
+
// ๊ท์
|
| 1228 |
+
'๊ท์ ', 'SEC', 'FTC', 'antitrust', '๋ฐ๋
์ ', '์์ก', 'lawsuit',
|
| 1229 |
+
// ๏ฟฝ๏ฟฝ๏ฟฝํ ์ด์
|
| 1230 |
+
'๋ฐฐ๋น', 'dividend', '์์ฌ์ฃผ', 'buyback', '๋ฆฌ์ฝ', 'recall', 'ํ์
', 'strike',
|
| 1231 |
+
// ๋น
ํ
ํฌ
|
| 1232 |
+
'Apple', 'Microsoft', 'Google', 'Amazon', 'Meta', 'Netflix'
|
| 1233 |
+
];
|
| 1234 |
+
|
| 1235 |
+
// ๋ด์ค ์ ๋ชฉ + ๋ด์ฉ์์ ํค์๋ ์ถ์ถ
|
| 1236 |
+
newsData.forEach(n => {
|
| 1237 |
+
const text = ((n.title || '') + ' ' + (n.content || '') + ' ' + (n.title_original || '')).toLowerCase();
|
| 1238 |
+
kwList.forEach(k => {
|
| 1239 |
+
const kLower = k.toLowerCase();
|
| 1240 |
+
if (text.includes(kLower)) {
|
| 1241 |
+
// ๋์๋ฌธ์ ํต์ผ (ํ์์ฉ)
|
| 1242 |
+
const displayKey = k.charAt(0).toUpperCase() + k.slice(1);
|
| 1243 |
+
keywords[displayKey] = (keywords[displayKey] || 0) + 1;
|
| 1244 |
+
}
|
| 1245 |
+
});
|
| 1246 |
+
});
|
| 1247 |
+
|
| 1248 |
+
// ์คํฌ๋ฆฌ๋ ๋ฐ์ดํฐ์์ ์นํฐ ํค์๋ ์ถ๊ฐ
|
| 1249 |
+
if (screeningData) {
|
| 1250 |
+
screeningData.forEach(s => {
|
| 1251 |
+
if (s.sector) {
|
| 1252 |
+
const sectorKey = s.sector.replace('Consumer ', '');
|
| 1253 |
+
keywords[sectorKey] = (keywords[sectorKey] || 0) + 1;
|
| 1254 |
+
}
|
| 1255 |
+
});
|
| 1256 |
+
}
|
| 1257 |
+
|
| 1258 |
+
const sorted = Object.entries(keywords).sort((a, b) => b[1] - a[1]).slice(0, 20);
|
| 1259 |
+
const max = sorted[0] ? sorted[0][1] : 1;
|
| 1260 |
+
const colors = ['#ff4757', '#ffa502', '#2ed573', '#00d9ff', '#9b59b6', '#e74c3c', '#3498db', '#1abc9c', '#f39c12', '#e91e63', '#00ff88', '#ff6b81', '#7bed9f', '#70a1ff', '#5352ed'];
|
| 1261 |
+
|
| 1262 |
+
sorted.forEach(([kw, cnt], i) => {
|
| 1263 |
+
const tag = document.createElement('span');
|
| 1264 |
+
tag.className = 'keyword-tag';
|
| 1265 |
+
const ratio = cnt / max;
|
| 1266 |
+
tag.classList.add(ratio > 0.7 ? 'size-xl' : ratio > 0.4 ? 'size-lg' : ratio > 0.2 ? 'size-md' : 'size-sm');
|
| 1267 |
+
tag.style.backgroundColor = colors[i % colors.length];
|
| 1268 |
+
tag.style.color = '#fff';
|
| 1269 |
+
tag.textContent = `${kw} (${cnt})`;
|
| 1270 |
+
tag.onclick = () => window.open(`https://www.google.com/search?q=${encodeURIComponent(kw + ' stock market news')}&tbm=nws`, '_blank');
|
| 1271 |
+
container.appendChild(tag);
|
| 1272 |
+
});
|
| 1273 |
+
|
| 1274 |
+
if (sorted.length === 0) {
|
| 1275 |
+
// ๊ธฐ๋ณธ ํธ๋ ๋ ํค์๋ ํ์
|
| 1276 |
+
const defaultKeywords = ['AI', 'Fed', '๋ฐ๋์ฒด', 'Tesla', 'NVIDIA', 'Bitcoin', '์ค์ ', '๊ธ๋ฆฌ'];
|
| 1277 |
+
defaultKeywords.forEach((kw, i) => {
|
| 1278 |
+
const tag = document.createElement('span');
|
| 1279 |
+
tag.className = 'keyword-tag size-md';
|
| 1280 |
+
tag.style.backgroundColor = colors[i % colors.length];
|
| 1281 |
+
tag.style.color = '#fff';
|
| 1282 |
+
tag.textContent = kw;
|
| 1283 |
+
tag.onclick = () => window.open(`https://www.google.com/search?q=${encodeURIComponent(kw + ' stock market news')}&tbm=nws`, '_blank');
|
| 1284 |
+
container.appendChild(tag);
|
| 1285 |
+
});
|
| 1286 |
+
}
|
| 1287 |
+
}
|
| 1288 |
+
|
| 1289 |
+
function initStockHeatmap() {
|
| 1290 |
+
const container = document.getElementById('stockHeatmap');
|
| 1291 |
+
container.innerHTML = '';
|
| 1292 |
+
if (!screeningData || screeningData.length === 0) {
|
| 1293 |
+
container.innerHTML = '<div style="color:#888;padding:20px;">๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.</div>';
|
| 1294 |
+
return;
|
| 1295 |
+
}
|
| 1296 |
+
|
| 1297 |
+
// ์๊ฐ์ด์ก ๊ธฐ์ค ์ต๋๊ฐ ๊ณ์ฐ
|
| 1298 |
+
const maxMarketCap = Math.max(...screeningData.map(s => s.market_cap || 0));
|
| 1299 |
+
|
| 1300 |
+
screeningData.forEach(stock => {
|
| 1301 |
+
const cell = document.createElement('div');
|
| 1302 |
+
cell.className = 'heatmap-cell';
|
| 1303 |
+
const chg = stock.change_percent || 0;
|
| 1304 |
+
|
| 1305 |
+
// ์๊ฐ์ด์ก ๋น์ค์ ๋ฐ๋ฅธ ํฌ๊ธฐ ์กฐ์
|
| 1306 |
+
const mcapRatio = (stock.market_cap || 0) / maxMarketCap;
|
| 1307 |
+
let cellSize = 80; // ๊ธฐ๋ณธ ํฌ๊ธฐ
|
| 1308 |
+
let fontSize = 0.85;
|
| 1309 |
+
if (mcapRatio > 0.8) { cellSize = 140; fontSize = 1.2; }
|
| 1310 |
+
else if (mcapRatio > 0.5) { cellSize = 120; fontSize = 1.05; }
|
| 1311 |
+
else if (mcapRatio > 0.3) { cellSize = 100; fontSize = 0.95; }
|
| 1312 |
+
else if (mcapRatio > 0.15) { cellSize = 90; fontSize = 0.9; }
|
| 1313 |
+
|
| 1314 |
+
cell.style.minWidth = cellSize + 'px';
|
| 1315 |
+
cell.style.minHeight = (cellSize * 0.7) + 'px';
|
| 1316 |
+
cell.style.flex = `0 0 ${cellSize}px`;
|
| 1317 |
+
|
| 1318 |
+
// ๋ฑ๋ฝ๋ฅ ์ ๋ฐ๋ฅธ ์์
|
| 1319 |
+
if (chg > 3) cell.classList.add('strong-up');
|
| 1320 |
+
else if (chg > 1) cell.classList.add('up');
|
| 1321 |
+
else if (chg > 0) cell.classList.add('slight-up');
|
| 1322 |
+
else if (chg > -1) cell.classList.add('neutral');
|
| 1323 |
+
else if (chg > -3) cell.classList.add('slight-down');
|
| 1324 |
+
else if (chg > -5) cell.classList.add('down');
|
| 1325 |
+
else cell.classList.add('strong-down');
|
| 1326 |
+
|
| 1327 |
+
cell.innerHTML = `<div class="ticker" style="font-size:${fontSize}em">${stock.ticker}</div><div class="change">${chg >= 0 ? '+' : ''}${chg.toFixed(2)}%</div><div class="price">$${stock.price.toFixed(2)}</div>`;
|
| 1328 |
+
cell.onclick = () => openChartModal(stock.ticker, stock.name, stock.exchange || 'NASDAQ');
|
| 1329 |
+
container.appendChild(cell);
|
| 1330 |
+
});
|
| 1331 |
+
}
|
| 1332 |
+
|
| 1333 |
+
function initMiniCharts() {
|
| 1334 |
+
if (!screeningData) return;
|
| 1335 |
+
screeningData.forEach((stock, i) => {
|
| 1336 |
+
const container = document.getElementById(`mini-chart-${stock.ticker}`);
|
| 1337 |
+
if (!container || container.dataset.loaded) return;
|
| 1338 |
+
container.dataset.loaded = 'true';
|
| 1339 |
+
container.innerHTML = '';
|
| 1340 |
+
|
| 1341 |
+
// ๊ฑฐ๋์์ ๋ฐ๋ฅธ ์ฌ๋ณผ ์ค์
|
| 1342 |
+
const exchange = stock.exchange || 'NASDAQ';
|
| 1343 |
+
const symbol = `${exchange}:${stock.ticker}`;
|
| 1344 |
+
|
| 1345 |
+
new TradingView.MediumWidget({
|
| 1346 |
+
symbols: [[stock.ticker, `${symbol}|3D`]],
|
| 1347 |
+
chartOnly: true,
|
| 1348 |
+
width: '100%',
|
| 1349 |
+
height: 60,
|
| 1350 |
+
locale: 'kr',
|
| 1351 |
+
colorTheme: 'dark',
|
| 1352 |
+
autosize: false,
|
| 1353 |
+
showVolume: false,
|
| 1354 |
+
hideDateRanges: true,
|
| 1355 |
+
scalePosition: 'no',
|
| 1356 |
+
scaleMode: 'Normal',
|
| 1357 |
+
fontFamily: 'sans-serif',
|
| 1358 |
+
noTimeScale: true,
|
| 1359 |
+
chartType: 'area',
|
| 1360 |
+
lineColor: stock.change_percent >= 0 ? '#00ff88' : '#ff4757',
|
| 1361 |
+
bottomColor: stock.change_percent >= 0 ? 'rgba(0,255,136,0.1)' : 'rgba(255,71,87,0.1)',
|
| 1362 |
+
lineWidth: 2,
|
| 1363 |
+
container_id: `mini-chart-${stock.ticker}`
|
| 1364 |
+
});
|
| 1365 |
+
});
|
| 1366 |
+
}
|
| 1367 |
+
|
| 1368 |
+
function openChartModal(ticker, name, exchange) {
|
| 1369 |
+
// TradingView ์ฌ๋ณผ ๊ฐ์ ธ์ค๊ธฐ
|
| 1370 |
+
const symbol = getTradingViewSymbol(ticker, exchange);
|
| 1371 |
+
|
| 1372 |
+
document.getElementById('chart-modal-title').textContent = `${ticker} - ${name}`;
|
| 1373 |
+
document.getElementById('chartModal').classList.add('active');
|
| 1374 |
+
document.getElementById('tradingview-chart').innerHTML = '';
|
| 1375 |
+
new TradingView.widget({
|
| 1376 |
+
autosize: true,
|
| 1377 |
+
symbol: symbol,
|
| 1378 |
+
interval: 'D',
|
| 1379 |
+
timezone: 'Asia/Seoul',
|
| 1380 |
+
theme: 'dark',
|
| 1381 |
+
style: '1',
|
| 1382 |
+
locale: 'kr',
|
| 1383 |
+
toolbar_bg: '#1a1a2e',
|
| 1384 |
+
enable_publishing: false,
|
| 1385 |
+
allow_symbol_change: true,
|
| 1386 |
+
container_id: 'tradingview-chart',
|
| 1387 |
+
hide_side_toolbar: false,
|
| 1388 |
+
studies: ['RSI@tv-basicstudies', 'MASimple@tv-basicstudies']
|
| 1389 |
+
});
|
| 1390 |
+
}
|
| 1391 |
+
|
| 1392 |
+
function closeModal(modalId) {
|
| 1393 |
+
document.getElementById(modalId).classList.remove('active');
|
| 1394 |
+
}
|
| 1395 |
+
|
| 1396 |
+
function renderMarkdown(text) {
|
| 1397 |
+
if (!text) return '-';
|
| 1398 |
+
if (typeof marked !== 'undefined') return marked.parse(text);
|
| 1399 |
+
return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>').replace(/\n/g, '<br>');
|
| 1400 |
+
}
|
| 1401 |
+
|
| 1402 |
+
function initAnalysisCharts(report) {
|
| 1403 |
+
[metricsChartInstance, priceChartInstance, riskChartInstance, growthChartInstance].forEach(c => c && c.destroy());
|
| 1404 |
+
const cp = report.current_price || 100, tp = report.target_price || 112;
|
| 1405 |
+
const mc = document.getElementById('metricsChart');
|
| 1406 |
+
if (mc) metricsChartInstance = new Chart(mc, { type: 'radar', data: { labels: ['์์ต์ฑ', '์ฑ์ฅ์ฑ', '์์ ์ฑ', '๋ฐธ๋ฅ์์ด์
', '๋ชจ๋ฉํ
'], datasets: [{ data: [75+Math.random()*20, 70+Math.random()*25, 65+Math.random()*20, 60+Math.random()*25, 72+Math.random()*20], backgroundColor: 'rgba(0,217,255,0.2)', borderColor: '#00d9ff', borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { r: { beginAtZero: true, max: 100, ticks: { color: '#888', backdropColor: 'transparent' }, grid: { color: 'rgba(255,255,255,0.1)' }, pointLabels: { color: '#ccc', font: { size: 10 } } } } } });
|
| 1407 |
+
const pc = document.getElementById('priceChart');
|
| 1408 |
+
if (pc) priceChartInstance = new Chart(pc, { type: 'bar', data: { labels: ['ํ์ฌ๊ฐ', '๋ชฉํ๊ฐ', '์ปจ์ผ์์ค'], datasets: [{ data: [cp, tp, tp*0.95], backgroundColor: ['#3498db', '#00ff88', '#9b59b6'], borderRadius: 5 }] }, options: { responsive: true, maintainAspectRatio: false, indexAxis: 'y', plugins: { legend: { display: false } }, scales: { x: { grid: { color: 'rgba(255,255,255,0.1)' }, ticks: { color: '#888' } }, y: { grid: { display: false }, ticks: { color: '#ccc' } } } } });
|
| 1409 |
+
const rc = document.getElementById('riskChart');
|
| 1410 |
+
if (rc) riskChartInstance = new Chart(rc, { type: 'doughnut', data: { labels: ['๊ธฐํ', '๋ฆฌ์คํฌ', '์ค๋ฆฝ'], datasets: [{ data: [55+Math.random()*15, 25+Math.random()*10, 20], backgroundColor: ['#00ff88', '#ff4757', '#ffa502'], borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: '#ccc', font: { size: 10 } } } } } });
|
| 1411 |
+
const gc = document.getElementById('growthChart');
|
| 1412 |
+
if (gc) growthChartInstance = new Chart(gc, { type: 'line', data: { labels: ['2024', '2025E', '2026E', '2027E'], datasets: [{ label: '๋งค์ถ', data: [8,12,15,18].map(v => v+Math.random()*5), borderColor: '#00d9ff', fill: true, backgroundColor: 'rgba(0,217,255,0.1)', tension: 0.4 }, { label: 'EPS', data: [10,15,20,25].map(v => v+Math.random()*5), borderColor: '#00ff88', fill: true, backgroundColor: 'rgba(0,255,136,0.1)', tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: '#ccc', font: { size: 10 } } } }, scales: { y: { grid: { color: 'rgba(255,255,255,0.1)' }, ticks: { color: '#888' } }, x: { grid: { display: false }, ticks: { color: '#ccc' } } } } });
|
| 1413 |
+
}
|
| 1414 |
+
|
| 1415 |
+
async function runDeepAnalysis(ticker, companyName) {
|
| 1416 |
+
currentTicker = ticker;
|
| 1417 |
+
document.getElementById(`deep-status-${ticker}`).innerHTML = 'โณ ๋ถ์ ์ค...';
|
| 1418 |
+
document.getElementById('modal-ticker').textContent = `${ticker} (${companyName})`;
|
| 1419 |
+
document.getElementById('deepAnalysisModal').classList.add('active');
|
| 1420 |
+
document.getElementById('loading-indicator').classList.add('active');
|
| 1421 |
+
document.getElementById('deep-analysis-result').style.display = 'none';
|
| 1422 |
+
try {
|
| 1423 |
+
const res = await fetch(`/api/deep-analysis/${ticker}`);
|
| 1424 |
+
const data = await res.json();
|
| 1425 |
+
if (data.success) {
|
| 1426 |
+
displayDeepAnalysisResult(data.report, ticker);
|
| 1427 |
+
document.getElementById(`deep-status-${ticker}`).innerHTML = 'โ
์๋ฃ';
|
| 1428 |
+
} else {
|
| 1429 |
+
document.getElementById(`deep-status-${ticker}`).innerHTML = 'โ ์คํจ';
|
| 1430 |
+
alert('๋ถ์ ์คํจ: ' + (data.error || '์ ์ ์๋ ์ค๋ฅ'));
|
| 1431 |
+
}
|
| 1432 |
+
} catch (e) {
|
| 1433 |
+
document.getElementById(`deep-status-${ticker}`).innerHTML = 'โ ์ค๋ฅ';
|
| 1434 |
+
alert('์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.');
|
| 1435 |
+
}
|
| 1436 |
+
document.getElementById('loading-indicator').classList.remove('active');
|
| 1437 |
+
}
|
| 1438 |
+
|
| 1439 |
+
function displayDeepAnalysisResult(report, ticker) {
|
| 1440 |
+
['executive_summary', 'company_overview', 'financial_analysis', 'technical_analysis', 'industry_analysis', 'risk_assessment', 'investment_thesis', 'price_targets', 'catalysts', 'final_recommendation'].forEach(key => {
|
| 1441 |
+
document.getElementById(`result-${key.replace(/_/g, '-')}`).innerHTML = renderMarkdown(report[key]);
|
| 1442 |
+
});
|
| 1443 |
+
document.getElementById('result-current-price').textContent = report.current_price ? `$${report.current_price.toLocaleString()}` : '-';
|
| 1444 |
+
document.getElementById('result-target-price').textContent = report.target_price ? `$${report.target_price.toLocaleString()}` : '-';
|
| 1445 |
+
document.getElementById('result-rating').textContent = report.rating || 'Buy';
|
| 1446 |
+
document.getElementById('result-upside').textContent = report.upside ? `+${report.upside}%` : '-';
|
| 1447 |
+
setTimeout(() => initAnalysisCharts(report), 100);
|
| 1448 |
+
document.getElementById('download-docx-btn').href = `/api/download-docx/${ticker}`;
|
| 1449 |
+
document.getElementById('download-docx-btn').style.display = 'inline-flex';
|
| 1450 |
+
document.getElementById('download-pdf-btn').style.display = 'inline-flex';
|
| 1451 |
+
document.getElementById('deep-analysis-result').style.display = 'block';
|
| 1452 |
+
}
|
| 1453 |
+
|
| 1454 |
+
async function downloadAsPDF() {
|
| 1455 |
+
const btn = document.getElementById('download-pdf-btn');
|
| 1456 |
+
const originalText = btn.textContent;
|
| 1457 |
+
btn.textContent = 'โณ ์์ฑ ์ค...';
|
| 1458 |
+
btn.disabled = true;
|
| 1459 |
+
|
| 1460 |
+
try {
|
| 1461 |
+
// ์ ์ฒด ๊ฒฐ๊ณผ ์์ญ ์บก์ฒ
|
| 1462 |
+
const resultArea = document.getElementById('deep-analysis-result');
|
| 1463 |
+
if (!resultArea) {
|
| 1464 |
+
throw new Error('๋ถ์ ๊ฒฐ๊ณผ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.');
|
| 1465 |
+
}
|
| 1466 |
+
|
| 1467 |
+
// ์ ์ฒด๋ฅผ ํ๋์ ์บ๋ฒ์ค๋ก ์บก์ฒ
|
| 1468 |
+
const canvas = await html2canvas(resultArea, {
|
| 1469 |
+
scale: 2,
|
| 1470 |
+
backgroundColor: '#1a1a2e',
|
| 1471 |
+
logging: false,
|
| 1472 |
+
useCORS: true,
|
| 1473 |
+
allowTaint: true,
|
| 1474 |
+
scrollY: -window.scrollY
|
| 1475 |
+
});
|
| 1476 |
+
|
| 1477 |
+
const imgData = canvas.toDataURL('image/jpeg', 0.9);
|
| 1478 |
+
|
| 1479 |
+
const { jsPDF } = window.jspdf;
|
| 1480 |
+
const pdf = new jsPDF('p', 'mm', 'a4');
|
| 1481 |
+
const pdfWidth = pdf.internal.pageSize.getWidth();
|
| 1482 |
+
const pdfHeight = pdf.internal.pageSize.getHeight();
|
| 1483 |
+
|
| 1484 |
+
const imgWidth = pdfWidth - 20;
|
| 1485 |
+
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
| 1486 |
+
|
| 1487 |
+
// ์ฌ๋ฌ ํ์ด์ง์ ๊ฑธ์ณ ์ด๋ฏธ์ง ๋ถํ
|
| 1488 |
+
let heightLeft = imgHeight;
|
| 1489 |
+
let position = 10;
|
| 1490 |
+
let page = 1;
|
| 1491 |
+
|
| 1492 |
+
// ์ฒซ ํ์ด์ง
|
| 1493 |
+
pdf.addImage(imgData, 'JPEG', 10, position, imgWidth, imgHeight);
|
| 1494 |
+
heightLeft -= (pdfHeight - 20);
|
| 1495 |
+
|
| 1496 |
+
// ์ถ๊ฐ ํ์ด์ง
|
| 1497 |
+
while (heightLeft > 0) {
|
| 1498 |
+
position = heightLeft - imgHeight + 10;
|
| 1499 |
+
pdf.addPage();
|
| 1500 |
+
pdf.addImage(imgData, 'JPEG', 10, position, imgWidth, imgHeight);
|
| 1501 |
+
heightLeft -= (pdfHeight - 20);
|
| 1502 |
+
page++;
|
| 1503 |
+
}
|
| 1504 |
+
|
| 1505 |
+
// ํ์ผ ์ ์ฅ
|
| 1506 |
+
const filename = `${currentTicker}_Analysis_${new Date().toISOString().split('T')[0]}.pdf`;
|
| 1507 |
+
pdf.save(filename);
|
| 1508 |
+
|
| 1509 |
+
btn.textContent = 'โ
์๋ฃ!';
|
| 1510 |
+
setTimeout(() => { btn.textContent = originalText; }, 2000);
|
| 1511 |
+
|
| 1512 |
+
} catch (e) {
|
| 1513 |
+
console.error('PDF ์์ฑ ์ค๋ฅ:', e);
|
| 1514 |
+
alert('PDF ์์ฑ ์คํจ: ' + e.message);
|
| 1515 |
+
btn.textContent = originalText;
|
| 1516 |
+
}
|
| 1517 |
+
btn.disabled = false;
|
| 1518 |
+
}
|
| 1519 |
+
|
| 1520 |
+
document.addEventListener('keydown', e => { if (e.key === 'Escape') { closeModal('deepAnalysisModal'); closeModal('chartModal'); } });
|
| 1521 |
+
document.getElementById('deepAnalysisModal').addEventListener('click', e => { if (e.target.id === 'deepAnalysisModal') closeModal('deepAnalysisModal'); });
|
| 1522 |
+
document.getElementById('chartModal').addEventListener('click', e => { if (e.target.id === 'chartModal') closeModal('chartModal'); });
|
| 1523 |
+
|
| 1524 |
+
document.addEventListener('DOMContentLoaded', () => setTimeout(initMarketTab, 100));
|
| 1525 |
+
</script>
|
| 1526 |
+
</body>
|
| 1527 |
+
</html>
|