Σε αυτήν την εργασία εφαρμόζουμε Δέντρα Απόφασης (Decision Trees) χρησιμοποιώντας τη μέθοδο CART (Classification and Regression Trees) στα ιστορικά δεδομένα τιμών της μετοχής Microsoft (MSFT). Ο στόχος είναι να ταξινομήσουμε αν η τιμή κλεισίματος της επόμενης ημέρας θα είναι ανοδική (Up) ή καθοδική (Down) σε σχέση με την τρέχουσα.
Η ανάλυση ακολουθεί τα 4 βήματα του μαθήματος:
Για την ανάλυση επιλέχθηκε το dataset Microsoft Stock Data το οποίο περιλαμβάνει ιστορικά δεδομένα της μετοχής της Microsoft. Περιέχει πληροφορίες για την καθημερινή τιμή της μετοχής στο χρηματιστήριο.
Date: η ημερομηνία της συναλλαγής
Open: η τιμή της μετοχής όταν άνοιξε το χρηματιστήριο την συγκεκριμένη ημέρα
High: η υψηλότερη τιμή που έφτασε η μετοχή μέσα στη μέρα
Low: η χαμηλότερη τιμή της μετοχής μέσα στη μέρα
Close: η τιμή της μετοχής όταν έκλεισε το χρηματιστήριο
Adj Close: τιμή κλεισίματος προσαρμοσμένη ώστε να αντικατοπτρίζει την αξία μετά τον υπολογισμό τυχόν εταιρικών ενεργειών
Volume: ο αριθμός των συναλλαγών που πραγματοποιήθηκαν εκείνη την ημέρα
MSFT <- read.csv("MSFT.csv")
#Φόρτωση βιβλιοθηκών
library(tidyverse) # Εργαλεία διαχείρισης δεδομένων & ggplot2## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.0 ✔ readr 2.2.0
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.2 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
## Warning: package 'rpart' was built under R version 4.5.3
## Warning: package 'rpart.plot' was built under R version 4.5.3
## Warning: package 'caTools' was built under R version 4.5.3
## Warning: package 'ROCR' was built under R version 4.5.3
# Μετατροπή της στήλης Date σε ημερομηνία
MSFT$Date <- as.Date(MSFT$Date)
MSFT <- MSFT %>% arrange(Date)## Αριθμός παρατηρήσεων : 9083
## Αριθμός μεταβλητών : 7
## Χρονικό διάστημα : 1986-03-13 → 2022-03-24
## Open High Low
## Min. : 0.08854 Min. : 0.09201 Min. : 0.08854
## 1st Qu.: 4.05078 1st Qu.: 4.10205 1st Qu.: 4.02734
## Median : 26.82000 Median : 27.10000 Median : 26.52000
## Mean : 41.32494 Mean : 41.76089 Mean : 40.87849
## 3rd Qu.: 40.03500 3rd Qu.: 40.44375 3rd Qu.: 39.50000
## Max. :344.62000 Max. :349.67001 Max. :342.20001
## Close Volume
## Min. : 0.09028 Min. :2.304e+06
## 1st Qu.: 4.07520 1st Qu.:3.461e+07
## Median : 26.84000 Median :5.203e+07
## Mean : 41.33563 Mean :5.875e+07
## 3rd Qu.: 39.93750 3rd Qu.:7.265e+07
## Max. :343.10998 Max. :1.032e+09
ggplot(MSFT, aes(x = Date, y = Close)) +
geom_line(color = "purple", linewidth = 0.35, alpha = 0.85) +
geom_smooth(method = "loess", span = 0.08,
color = "#007", se = FALSE, linewidth = 1.1) +
scale_x_date(date_breaks = "5 years", date_labels = "%Y") +
scale_y_continuous(labels = scales::dollar_format()) +
labs(
title = "Τιμή Κλεισίματος 1986–2022",
subtitle = "Ημερίσια Τιμή & Τάση",
x = NULL, y = "Τιμή Κλεισίματος($)"
) +
theme_minimal(base_size = 13) +
theme(plot.title = element_text(face = "bold"),
panel.grid.minor = element_blank())## `geom_smooth()` using formula = 'y ~ x'
Εικ. 1 — Τιμή κλεισίματος MSFT (1986–2022)
Μετατρέπουμε τα ακατέργαστα δεδομένα τιμής σε χαρακτηριστικά (features) που περιγράφουν τη συμπεριφορά της αγοράς. Όλα υπολογίζονται αποκλειστικά από παρελθοντικές τιμές, ώστε να αποφύγουμε data leakage.
library(tidyverse)
# Βοηθητική συνάρτηση κινητού μέσου (χωρίς εξωτερικές βιβλιοθήκες)
roll_mean <- function(x, n) {
stats::filter(x, rep(1/n, n), sides = 1) %>% as.numeric()
}
roll_sd <- function(x, n) {
sapply(seq_along(x), function(i) {
if (i < n) NA else sd(x[(i - n + 1):i])
})
}
MSFT_feat <- MSFT |>
mutate(
# --- Αποδόσεις ---
Daily_Return = (Close - lag(Close)) / lag(Close) * 100,
Intraday_Move = (Close - Open) / Open * 100,
Daily_Range = (High - Low) / Open * 100,
# --- Κινητοί Μέσοι Όροι ---
MA5 = roll_mean(Close, 5),
MA20 = roll_mean(Close, 20),
MA50 = roll_mean(Close, 50),
# --- Απόσταση τιμής από ΚΜΟ (%) ---
Price_vs_MA5 = (Close - MA5) / MA5 * 100,
Price_vs_MA20 = (Close - MA20) / MA20 * 100,
Price_vs_MA50 = (Close - MA50) / MA50 * 100,
# --- Momentum & Volatility ---
Momentum_5d = (Close - lag(Close, 5)) / lag(Close, 5) * 100,
Volatility_20d = roll_sd(Daily_Return, 20),
# --- Μεταβολή όγκου ---
Vol_Change = (Volume - lag(Volume)) / lag(Volume) * 100,
# --- Μεταβλητή-Στόχος ---
# Up: η επόμενη τιμή κλεισίματος > η σημερινή
Target = ifelse(lead(Close) > Close, "Up", "Down")
) |>
drop_na() |>
mutate(Target = factor(Target, levels = c("Down", "Up")))
cat("Διαστάσεις dataset μετά την προετοιμασία:",
nrow(MSFT_feat), "γραμμές ×", ncol(MSFT_feat), "στήλες\n\n")## Διαστάσεις dataset μετά την προετοιμασία: 9033 γραμμές × 20 στήλες
## Κατανομή κλάσεων-στόχου:
##
## Down Up
## 4476 4557
##
## Ποσοστά (%):
##
## Down Up
## 49.6 50.4
## Ελλείψεις ανά στήλη (μετά drop_na):
## Date Open High Low Close
## 0 0 0 0 0
## Adj.Close Volume Daily_Return Intraday_Move Daily_Range
## 0 0 0 0 0
## MA5 MA20 MA50 Price_vs_MA5 Price_vs_MA20
## 0 0 0 0 0
## Price_vs_MA50 Momentum_5d Volatility_20d Vol_Change Target
## 0 0 0 0 0
Ο CART ακολουθεί άπληστη (greedy) στρατηγική: σε κάθε βήμα επιλέγει τον διαχωρισμό που ελαχιστοποιεί την Gini Impurity.
feat_cols <- c("Daily_Return", "Intraday_Move", "Daily_Range",
"Price_vs_MA5", "Price_vs_MA20", "Price_vs_MA50",
"Momentum_5d", "Volatility_20d", "Vol_Change")
model_df <- MSFT_feat %>% select(all_of(feat_cols), Target)
cat("Features που χρησιμοποιούνται:\n")## Features που χρησιμοποιούνται:
## • Daily_Return
## • Intraday_Move
## • Daily_Range
## • Price_vs_MA5
## • Price_vs_MA20
## • Price_vs_MA50
## • Momentum_5d
## • Volatility_20d
## • Vol_Change
set.seed(42)
# sample.split: TRUE → train, FALSE → test (80/20)
split_idx <- sample.split(model_df$Target, SplitRatio = 0.80)
train_df <- subset(model_df, split_idx)
test_df <- subset(model_df, !split_idx)
cat("Training set :", nrow(train_df), "παρατηρήσεις\n")## Training set : 7227 παρατηρήσεις
## Test set : 1806 παρατηρήσεις
## Κατανομή κλάσεων — Train:
##
## Down Up
## 49.6 50.4
##
## Κατανομή κλάσεων — Test:
##
## Down Up
## 49.6 50.4
cart_model <- rpart(
Target ~ .,
data = train_df,
method = "class",
parms = list(split = "gini"), # Κριτήριο Gini Impurity
control = rpart.control(
minsplit = 30, # Ελάχ. παρατηρήσεις για να διαχωριστεί κόμβος
minbucket = 15, # Ελάχ. παρατηρήσεις σε φύλλο
cp = 0.001,# Complexity parameter (πρίν κλάδεμα)
maxdepth = 8 # Μέγιστο βάθος
)
)
# Πίνακας cross-validation
printcp(cart_model)##
## Classification tree:
## rpart(formula = Target ~ ., data = train_df, method = "class",
## parms = list(split = "gini"), control = rpart.control(minsplit = 30,
## minbucket = 15, cp = 0.001, maxdepth = 8))
##
## Variables actually used in tree construction:
## [1] Daily_Range Daily_Return Intraday_Move Momentum_5d Price_vs_MA20
## [6] Price_vs_MA5 Price_vs_MA50 Vol_Change Volatility_20d
##
## Root node error: 3581/7227 = 0.4955
##
## n= 7227
##
## CP nsplit rel error xerror xstd
## 1 0.0268082 0 1.00000 1.00000 0.011869
## 2 0.0240156 1 0.97319 1.01480 0.011870
## 3 0.0069813 2 0.94918 0.99302 0.011868
## 4 0.0041888 3 0.94219 0.99469 0.011869
## 5 0.0035744 4 0.93801 0.99581 0.011869
## 6 0.0034906 9 0.92013 0.99693 0.011869
## 7 0.0030718 11 0.91315 0.99693 0.011869
## 8 0.0026994 13 0.90701 0.99916 0.011869
## 9 0.0026529 17 0.89612 0.99832 0.011869
## 10 0.0025133 19 0.89081 0.99888 0.011869
## 11 0.0022340 22 0.88271 1.00195 0.011870
## 12 0.0015359 25 0.87573 0.99553 0.011869
## 13 0.0013963 35 0.85395 0.99497 0.011869
## 14 0.0012566 38 0.84976 0.99721 0.011869
## 15 0.0011170 42 0.84474 1.00000 0.011869
## 16 0.0010000 48 0.83803 1.00140 0.011869