Skip to content
HomeProjectsHighlightsBlogPlaygroundRésuméAbout

Stock Prediction

by Michael Rusu

I built a market-data experiment that trains three classical regressors and brings their outputs into one interface. The workflow connects historical quotes, feature preparation, model selection, and streamed progress so each stage is visible.

Code

Market-data modeling
Random Forest · SVR · Linear regression

Explore the project ↓
The problemMy contributionArchitectureMarket featuresComparing modelsEvaluationInterface

The problem

A price estimate depends on the data available when it is made. An opening price is available early in a session, while volume keeps changing throughout the day. Those differences shape both the model inputs and the interpretation of its output.

This project made the modeling workflow explorable: choose a symbol, gather its history, train three regressors, and inspect their predictions together. It also exposed the practical work around data collection, request handling, and progress reporting.

My contribution

I built the data collection, model modules, Flask orchestration, Server-Sent Events progress stream, and comparison dashboard. I connected those pieces into a request that moves from a stock symbol through feature preparation and training to a set of model results.

Architecture

Historical quote data enters feature preparation and three separate regression models, whose outputs return to the comparison interface.
Figure 1. Historical rows feed three separate regressors. PNGEditable Excalidraw Download figure

The Flask generator checks the requested symbol against its company lookup. The collection module downloads historical data through yfinance and filters symbols using the NASDAQ directory. Preprocessing extracts the selected columns before the training module dispatches each model.

The response carries a dictionary of model-specific predictions. Random Forest, support vector regression, and linear regression each contribute an output that the interface can display independently.

Market features

The feature list contains opening price, volume, month, year, and day. The code accepts dates from either a CSV column or a DataFrame index, creates calendar fields, and drops incomplete rows.

Volume is omitted when the incomplete-day flag is set or the server’s local clock falls between 09:30 and 16:00, inclusive. This is a clock-based rule; its implementation has no exchange-calendar, holiday, or timezone lookup.

Python · feature preparationSource ↗
def preprocess_data(input_data, is_latest:bool, indexing:bool):
    df = pd.DataFrame(input_data)
    target = ["Close"]
    features = ["Volume", "Open", "Month", "Year", "Day"]
    if not indexing:
        df["Date"] = pd.to_datetime(df["Date"])
        df["Month"] = df["Date"].dt.month
        df["Year"] = df["Date"].dt.year
        df["Day"] = df["Date"].dt.day
        df.drop(columns=["Date"], axis=1, inplace=True)
    else:
        df.index = pd.to_datetime(df.index)
        df["Month"] = df.index.month
        df["Year"] = df.index.year
        df["Day"] = df.index.day

    if not is_latest or (is_latest and time(9, 30) <= datetime.now().time() <= time(16, 0)):
        print("Between 9 and 4 pm")
        features.remove("Volume")

    df.dropna(axis=0, inplace=True)    
    x_data = df[features]
    y_data = df[target]
    return x_data, y_data
A fixed July 15 row is prepared with four features at noon and five after 16:00, while Close remains the target on that same row.
Figure 2. The clock rule changes which columns enter the model. PNGEditable Excalidraw Download figure

The figure uses a fixed historical row to make the feature order explicit. The target remains that row’s closing price. The training path contains no future target shift, which makes the relationship between the input date and output date central to interpreting the application’s “next price” label.

Comparing models

The training function uses a random 50/50 split with a seed of zero. Each selected algorithm runs its own five-fold GridSearchCV. The Random Forest search varies tree count, depth, and split constraints; the SVR search varies C and gamma with an RBF kernel; the linear model explores its intercept and coefficient constraints.

Python · model dispatchSource ↗
def train_models(selected_algorithms: list, x_data, y_data):
    models = {}
    x_train, x_test, y_train, y_test = train_test_split(x_data, y_data, test_size=0.5, random_state=0)
    algorithm_functions = {
        "RANDOMFOREST": train_rf_regresion, # This works
        "SVMMODEL": train_svm_model, #This works
        "LINEARREGRESSION": linear_model
    }
    for algorithm in selected_algorithms:
        if algorithm in algorithm_functions and algorithm_functions[algorithm] is not None:
            model = algorithm_functions[algorithm](x_train, x_test, y_train, y_test)
            models[algorithm] = model
            # model_file_path = f"models/{algorithm}_model.pkl"
            # os.makedirs(os.path.dirname(model_file_path), exist_ok=True)
            # joblib.dump(model, model_file_path)
        else:
            print(f"Algorithm '{algorithm}' is not supported.")
    return models

Random Forest and linear regression print mean squared error on the held-out half. The SVR module returns its selected estimator. The page’s opening animation uses three fixed schematic traces to represent these separate branches.

The Flask generator streams progress updates and then a done event containing historical data and three predictions; an unknown symbol exits with an error event.
Figure 3. Progress events lead into the final comparison response. PNGEditable Excalidraw Download figure

Server-Sent Events keep the browser informed during collection and training. The generator emits progress values through 95%, then a final done event containing the symbol, historical series, and model outputs. An unknown symbol yields an error and ends the request.

News sentiment lives in a separate endpoint. It selects article titles containing the company name, applies TextBlob polarity to each title, and returns nonzero results with their descriptions. Those values provide an additional view of the news; they are absent from the regression feature list.

Evaluation

The recorded split measures behavior on randomly held-out historical rows. Assessing a later trading period requires a chronological evaluation with a defined prediction horizon. The source also contains a synthetic-price simulation, whose random paths have a separate experimental purpose.

The archived output below records one Random Forest estimate. Its context is an individual project run. The available records support describing the implementation and its outputs, while a numerical comparison across models would need the associated test rows and saved evaluation results.

Fixed offline checks exercise the volume boundaries, date handling, missing rows, model dispatch, and streamed response. They use stubbed quote downloads and predictions.

Interface

Archived terminal output from Stock Prediction showing a completed Random Forest estimate.
An individual model output preserved from the project.

The interface brings the historical series and three estimates into the same request flow. Its progress updates make the collection and training stages visible while the browser waits for the completed result.

Explore the Stock Prediction source ↗

The linked repository requires GitHub access.

with by michael