File size: 7,671 Bytes
1eca702
e7ab402
b127fbd
 
 
00d1951
aed74fe
 
 
 
 
 
72f648f
aed74fe
 
 
 
 
 
 
 
 
f7b023a
00d1951
f7b023a
ef62153
b127fbd
9ed40f8
68c0bb3
db216d9
c6677a7
4afb719
aad28e9
b127fbd
8217f0b
8365013
2244c21
b127fbd
18e014e
 
 
 
b127fbd
18e014e
 
 
 
 
b127fbd
 
b0129c1
 
18e014e
 
b127fbd
 
 
 
18e014e
b0129c1
 
 
 
 
fcaaa24
 
 
 
 
 
 
b0129c1
fcaaa24
 
 
 
 
 
 
b127fbd
fcaaa24
 
 
 
b0129c1
 
 
18e014e
 
 
 
b127fbd
 
 
18e014e
b0129c1
b127fbd
 
3e16c1e
641f42e
3e16c1e
 
18e014e
 
 
874d51b
 
 
 
3e16c1e
 
 
 
 
 
 
b127fbd
 
8365013
dd68bef
1eca702
8803d67
7a0c4b6
1eca702
7a0c4b6
0f39e11
1eca702
0f39e11
f7b023a
 
 
 
 
 
 
68c8c82
f7b023a
 
 
 
 
 
 
68c0bb3
aed74fe
 
 
99631c2
aefc716
1eca702
 
7a0c4b6
1eca702
f7b023a
68c8c82
f7b023a
 
 
1eca702
68c0bb3
3711265
68c0bb3
f7b023a
00d30d0
1eca702
b127fbd
3cca38c
1eca702
5e42d81
b127fbd
 
ef62153
3e1cb2c
5e42d81
b127fbd
8a87dea
1eca702
b127fbd
3cca38c
59d2785
1eca702
b127fbd
59d2785
1eca702
59d2785
b127fbd
 
59d2785
3e1cb2c
59d2785
b127fbd
59d2785
1eca702
b127fbd
6507acf
b127fbd
f7b023a
 
 
 
 
04706d1
5e42d81
 
 
 
b127fbd
16341e0
469627c
 
 
 
b127fbd
4df6e43
b127fbd
27f3e8a
e7ab402
b127fbd
4afb719
b127fbd
 
917b23a
db216d9
9ed40f8
b127fbd
 
 
85c652b
b127fbd
9ed40f8
b5147e5
85c652b
47320f8
e7ab402
00d1951
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
import gradio as gr
import sys
import time
import queue
import threading
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import os
from sklearn import datasets
from sklearn.cluster import KMeans
from sklearn.cluster import DBSCAN
import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.tree import export_text
from sklearn import metrics
import datetime
from sklearn.neural_network import MLPClassifier
from stock_manager import StockDataManager
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
manager = StockDataManager()


def run_all(tickersymbol, download_data, download_update, use_train, PREDICT, predict_date):
    # 產生設定參數摘要
    config_str = f"股票代碼:{tickersymbol}\n"
    config_str += f"更新資料:{'是' if download_data else '否'}{f' (日期: {download_update})' if download_data else ''}\n"
    config_str += f"啟用訓練:{'是' if use_train else '否'}\n"
    config_str += f"啟用預測:{'是' if PREDICT else '否'}{f'(日期: {predict_date})'if PREDICT else ''}\n"
    
    yield config_str


# --- 新增自訂的標準輸出攔截器 ---
class GlobalStreamInterceptor:
    def __init__(self, q, original_stdout):
        self.q = q
        self.original_stdout = original_stdout
        
    def write(self, buf):
        self.original_stdout.write(buf)
        # 這裡不 strip,保留原始內容給邏輯判斷
        if buf not in ["\n", ""]:
            self.q.put(buf)
        
    def flush(self):
        # 必須呼叫原始 stdout 的 flush,確保資料被清出緩衝區
        self.original_stdout.flush()
# 儲存最原始的 stdout
original_stdout = sys.stdout
stdout_lock = threading.Lock()

def run_external_code(tickersymbol, download_data, download_update, use_train, method, PREDICT, predict_date):
    q = queue.Queue()
        
    def background_task():
        # 取得鎖,確保不會有兩個任務同時搶奪 stdout
        with stdout_lock:
            old_stdout = sys.stdout
            sys.stdout = GlobalStreamInterceptor(q, old_stdout)
        try:
            # 執行原本的邏輯
            if download_data:
                manager.fetch_data(end_date=download_update)
            else:
                print("Populating ticker from files...")
                manager.load_raw_data(isProcessData=False)
                # reads CSV files saved by fetch_data()
            if use_train:
                manager.trainModel(
                ticker = tickersymbol,
                epochs_general = 10,
                epochs_particular = 10
            )
                #manager.trainMTAModel(ticker=tickersymbol, loss_fn=method)
                    
            if PREDICT:
                signal = manager.predict_day(ticker=tickersymbol, date=predict_date)
                print(f"\nFinal signal: {signal:+.4f}")
                
        except Exception as e:
            print(f"Error occurred: {str(e)}")
        finally:
            # 確保還原
            with stdout_lock:
                sys.stdout = original_stdout
            q.put(None)

    thread = threading.Thread(target=background_task, daemon=True)
    thread.start()
    log_messages = ""
    # 監聽佇列
    while True:
        try:
            # 嘗試取得佇列內容
            chunk = q.get(timeout=0.5)
            
            # 檢查是否為結束信號
            if chunk is None: 
                break
            
            clean_chunk = str(chunk).strip()
            if clean_chunk:
                log_messages += clean_chunk + "\n"
                yield log_messages
        
        # 修正 SyntaxError:補上 try 對應的 except 區塊
        except queue.Empty:
            # 如果佇列空了但執行緒已結束,則跳出
            if not thread.is_alive():
                break
            continue
            


def update_setup(is_checked):
    return gr.update(visible=is_checked)

def train_setup(is_checked):
    return gr.update(visible=is_checked)

def predict_setup(is_checked):
    return gr.update(visible=is_checked)

def update_ticker_list(market_choice):
    if market_choice == "TW50":
        if not manager.tw50_tickers:
            manager.fillTw50Tickers()
        new_choices = manager.tw50_tickers if manager.tw50_tickers else ["抓取TW50失敗"]
        return gr.update(choices=new_choices, value=new_choices[0])
        
    elif market_choice == "SP500":
        if not manager.sp500_tickers:
            manager.fillSp500Tickers()
        new_choices = manager.sp500_tickers if manager.sp500_tickers else ["抓取SP50失敗"]
        return gr.update(choices=new_choices, value=new_choices[0])
        
    return gr.update(choices=["No tickers loaded"], value="No tickers loaded")

symbol_list = ["No tickers loaded"]
today = datetime.date.today()
yesterday = today - datetime.timedelta(days=1)
tomorow = today + datetime.timedelta(days=1)


with gr.Blocks() as demo:
    gr.Markdown("### 股票分析與訓練設定")
    
    with gr.Row():
        market_radio = gr.Radio(
            choices = ["TW50", "SP500"], 
            label = "選擇市場板塊", 
            value = None
        )
        ticker_list = gr.Dropdown(
            choices = symbol_list, 
            label = "股票代碼",
            value = symbol_list[0]
        )
        
    with gr.Row():        
        UPDATE_DATA = gr.Checkbox(label="更新資料", value=False) 
        
        end_date = gr.DateTime(
            label = "選擇數據更新日",
            type = "string",       
            value = yesterday.strftime("%Y-%m-%d"), 
            interactive=True,
            include_time = False,
            visible = False
        ) 
        
    with gr.Row():    
        TRAIN = gr.Checkbox(label="啟用訓練功能", value=False) 
        
        
    with gr.Row():    
        PREDICT = gr.Checkbox(label="啟用預測功能", value=False) 
        
        predict_date = gr.DateTime(
            label = "選擇預測日期",
            type = "string",       
            value = today.strftime("%Y-%m-%d"), 
            interactive=True,
            include_time = False,
            visible = False
        ) 
   
    with gr.Row():
        run_btn = gr.Button("啟動", variant="primary")
        
    ##--觸發事件--
    market_radio.change(
        fn = update_ticker_list,
        inputs = market_radio,
        outputs = ticker_list
    )

    UPDATE_DATA.change(
        fn = update_setup, 
        inputs = UPDATE_DATA, 
        outputs = end_date
    )
        
    PREDICT.change(
        fn = predict_setup, 
        inputs = PREDICT, 
        outputs = predict_date
    )
  
    #--兩個輸出欄位--
    config_display = gr.Textbox(label="目前設定參數", lines=3)

    # 加上 autoscroll=True 讓日誌自動向下捲動
    output_text = gr.Textbox(label="控制台輸出內容", lines=10, autoscroll=True)

    # 第一個 Click 事件負責更新左邊/上方的參數區塊
    run_btn.click(
        fn = run_all, 
        inputs = [ticker_list, UPDATE_DATA, end_date, TRAIN, PREDICT, predict_date], 
        outputs = [config_display] 
    )
    # 第二個 Click 事件負責在背景跑訓練,並透過 yield 即時更新 output_text
    run_btn.click(
        fn = run_external_code,
        inputs = [ticker_list, UPDATE_DATA, end_date, TRAIN, PREDICT, predict_date],
        outputs = output_text
    )


demo.launch(ssr_mode=False)