AbdelrehmanFouad commited on
Commit
4ac5ca2
·
verified ·
1 Parent(s): 5dac7b1

Upload train_job_base.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. train_job_base.py +206 -0
train_job_base.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # /// script
3
+ # dependencies = ["trl>=0.12.0", "peft>=0.7.0", "trackio", "pandas", "numpy", "datasets", "transformers", "torch", "accelerate", "huggingface_hub"]
4
+ # ///
5
+
6
+ import os
7
+ import json
8
+ import pandas as pd
9
+ import numpy as np
10
+ from datetime import datetime
11
+ from datasets import Dataset, load_dataset
12
+ from transformers import (
13
+ AutoTokenizer,
14
+ AutoModelForSeq2SeqLM,
15
+ Seq2SeqTrainer,
16
+ Seq2SeqTrainingArguments,
17
+ DataCollatorForSeq2Seq
18
+ )
19
+ import trackio
20
+ import torch
21
+ from tqdm import tqdm
22
+
23
+ # --- DATA PREPARATION ---
24
+
25
+ def process_data(tokenizer):
26
+ print("Loading USDJPY data (streaming)...")
27
+ ds_jpy = load_dataset("huggingXG/forex_USDJPY", split="train", streaming=True)
28
+
29
+ data = []
30
+ # Collect 3M ticks (~2-3 months)
31
+ max_ticks = 3000000
32
+ for i, row in enumerate(ds_jpy):
33
+ data.append(row)
34
+ if i >= max_ticks: break
35
+
36
+ df_jpy = pd.DataFrame(data)
37
+ df_jpy['timestamp'] = pd.to_datetime(df_jpy['timestamp'], format='ISO8601')
38
+ df_jpy.set_index('timestamp', inplace=True)
39
+
40
+ print("Resampling to hourly OHLCV...")
41
+ df_jpy['mid'] = (df_jpy['ask'] + df_jpy['bid']) / 2
42
+ resampled = df_jpy['mid'].resample('1h').ohlc()
43
+ resampled.dropna(inplace=True)
44
+
45
+ print("Loading Calendar data...")
46
+ ds_cal = load_dataset("Ehsanrs2/Forex_Factory_Calendar", split="train")
47
+ df_cal = ds_cal.to_pandas()
48
+ df_cal['DateTime'] = pd.to_datetime(df_cal['DateTime'], utc=True)
49
+ df_cal = df_cal[df_cal['Currency'].isin(['USD', 'JPY'])]
50
+
51
+ print("Generating examples...")
52
+ examples = []
53
+ context_size = 168
54
+ horizon_size = 24
55
+
56
+ if resampled.index.tz is None:
57
+ resampled.index = resampled.index.tz_localize('UTC')
58
+ else:
59
+ resampled.index = resampled.index.tz_convert('UTC')
60
+
61
+ indices = range(context_size, len(resampled) - horizon_size, 1)
62
+
63
+ for i in indices:
64
+ context_window = resampled.iloc[i-context_size:i]['close'].tolist()
65
+ target_window = resampled.iloc[i:i+horizon_size]['close'].tolist()
66
+
67
+ start_time = resampled.index[i]
68
+ end_time = resampled.index[i+horizon_size-1]
69
+
70
+ events_in_horizon = df_cal[(df_cal['DateTime'] >= start_time) & (df_cal['DateTime'] <= end_time)]
71
+ macro_events = []
72
+ for _, event in events_in_horizon.iterrows():
73
+ macro_events.append({
74
+ "time": event['DateTime'].strftime("%Y-%m-%dT%H:%M:%SZ"),
75
+ "event": event['Event'],
76
+ "impact": event['Impact'].lower().split()[0]
77
+ })
78
+
79
+ start_price = context_window[-1]
80
+ end_price = target_window[-1]
81
+ change = (end_price - start_price) / start_price
82
+
83
+ if change > 0.0005: direction = "up"
84
+ elif change < -0.0005: direction = "down"
85
+ else: direction = "sideways"
86
+
87
+ confidence = min(0.99, abs(change) * 50 + 0.5)
88
+ high_impact = [e['event'] for e in macro_events if 'high' in e['impact']]
89
+ macro_note = f"{high_impact[0]} in window" if high_impact else "No high impact events"
90
+
91
+ input_data = {
92
+ "context_window": [round(float(c), 4) for c in context_window],
93
+ "macro_events": macro_events
94
+ }
95
+ output_data = {
96
+ "forecast": [round(float(f), 4) for f in target_window],
97
+ "direction": direction,
98
+ "confidence": round(float(confidence), 2),
99
+ "macro_context": macro_note
100
+ }
101
+
102
+ examples.append({
103
+ "input_text": "forecast usdjpy: " + json.dumps(input_data),
104
+ "target_text": json.dumps(output_data)
105
+ })
106
+
107
+ def tokenize_fn(batch):
108
+ model_inputs = tokenizer(batch["input_text"], max_length=1024, truncation=True)
109
+ labels = tokenizer(text_target=batch["target_text"], max_length=512, truncation=True)
110
+ model_inputs["labels"] = labels["input_ids"]
111
+ return model_inputs
112
+
113
+ full_ds = Dataset.from_list(examples)
114
+ split_idx = int(len(full_ds) * 0.8)
115
+ train_ds = full_ds.select(range(split_idx))
116
+ eval_ds = full_ds.select(range(split_idx, len(full_ds)))
117
+
118
+ tokenized_train = train_ds.map(tokenize_fn, batched=True, remove_columns=full_ds.column_names)
119
+ tokenized_eval = eval_ds.map(tokenize_fn, batched=True, remove_columns=full_ds.column_names)
120
+
121
+ return tokenized_train, tokenized_eval, eval_ds
122
+
123
+ # --- EVALUATION ---
124
+
125
+ def run_custom_eval(model, tokenizer, raw_eval_ds):
126
+ print("Running custom evaluation...")
127
+ model.eval()
128
+ results = []
129
+ eval_subset = raw_eval_ds.select(range(min(100, len(raw_eval_ds))))
130
+
131
+ for example in tqdm(eval_subset):
132
+ inputs = tokenizer(example['input_text'], return_tensors="pt", max_length=1024, truncation=True).to(model.device)
133
+ with torch.no_grad():
134
+ outputs = model.generate(**inputs, max_new_tokens=512)
135
+ prediction_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
136
+
137
+ actual = json.loads(example['target_text'])
138
+ is_valid = False
139
+ schema_pass = False
140
+ pred_json = {}
141
+ try:
142
+ pred_json = json.loads(prediction_text)
143
+ is_valid = True
144
+ if all(k in pred_json for k in ["forecast", "direction", "confidence"]):
145
+ schema_pass = True
146
+ except: pass
147
+
148
+ results.append({"valid": is_valid, "schema": schema_pass, "actual": actual, "pred": pred_json if schema_pass else None})
149
+
150
+ valid_rate = np.mean([r['valid'] for r in results])
151
+ schema_rate = np.mean([r['schema'] for r in results])
152
+ valid_results = [r for r in results if r['schema']]
153
+
154
+ mae, dir_acc = 0.0, 0.0
155
+ if valid_results:
156
+ maes = [np.mean(np.abs(np.array(r['actual']['forecast']) - np.array(r['pred']['forecast']))) for r in valid_results]
157
+ accs = [1 if r['actual']['direction'] == r['pred']['direction'] else 0 for r in valid_results]
158
+ mae, dir_acc = np.mean(maes), np.mean(accs)
159
+
160
+ return {"mae": float(mae), "directional_accuracy": float(dir_acc), "valid_json_rate": float(valid_rate), "schema_pass_rate": float(schema_rate)}
161
+
162
+ def main():
163
+ trackio.init(project="usdjpy-forecasting", name="t5-efficient-base-sft")
164
+
165
+ model_id = "google/t5-efficient-base"
166
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
167
+ train_ds, tokenized_eval, raw_eval_ds = process_data(tokenizer)
168
+
169
+ model = AutoModelForSeq2SeqLM.from_pretrained(model_id)
170
+
171
+ args = Seq2SeqTrainingArguments(
172
+ output_dir="./t5-base-usdjpy",
173
+ max_steps=1000,
174
+ learning_rate=1e-4, # Slightly lower for larger model stability
175
+ per_device_train_batch_size=4,
176
+ gradient_accumulation_steps=4,
177
+ eval_strategy="steps",
178
+ eval_steps=200,
179
+ save_strategy="steps",
180
+ save_steps=500,
181
+ logging_steps=10,
182
+ push_to_hub=True,
183
+ hub_model_id="AbdelrehmanFouad/t5-efficient-base-usdjpy-forecaster",
184
+ report_to="trackio",
185
+ predict_with_generate=True,
186
+ fp16=False, # Use FP32 for maximum stability on T4
187
+ )
188
+
189
+ trainer = Seq2SeqTrainer(
190
+ model=model,
191
+ args=args,
192
+ train_dataset=train_ds,
193
+ eval_dataset=tokenized_eval,
194
+ tokenizer=tokenizer,
195
+ data_collator=DataCollatorForSeq2Seq(tokenizer, model=model),
196
+ )
197
+
198
+ trainer.train()
199
+ trainer.push_to_hub()
200
+
201
+ final_metrics = run_custom_eval(trainer.model, tokenizer, raw_eval_ds)
202
+ print(json.dumps(final_metrics, indent=2))
203
+ trackio.log(final_metrics)
204
+
205
+ if __name__ == "__main__":
206
+ main()