How to Generate Buy & Sell Signals of Stock Trading
By: Sarit Maitra
Tags: Machine Learning, Buy Sell Stock, Trading Algorithms

Regression & Classification to generate buy/sell signals

How to Generate Buy & Sell Signals of Stock Trading

An example with Crude Oil daily data

Image by Author

Buy and sell signals can be generated by two moving averages — a long-period and a short-period average. When the short moving average rises or falls below the long moving average, buy or sell signals can be generated based on set parameters.

Here, with a simple example, we have shown as how to generate report on buy/sell signals and visualize the chart.

print("....Data Loading...."); print();

print('\033[4mCrude Oil Spot Price\033[0m');

data = web.DataReader('CL=F', data_source = 'yahoo', start = '2000-01-01');

data;

Let’s focus on closing price of daily stock.

df = data[['Close']];

# Plot the closing price

df.Close.plot(figsize=(10, 5));

plt.ylabel("Prices (USD)"); plt.title("Crude Oil Price Series");

plt.show();

Explanatory variables

I have used two EMAs of different time period to find the price movements; in simple moving average (SMA), each value in the time period carries equal weight, and values outside of the time period are not included in the average. EMA is a cumulative calculation, including all data where the past values have a diminishing contribution to the average; more recent values have a greater contribution. EMA allows the moving average to be more responsive to changes in the data.

df['ema10'] = (df['Close'].ewm(span=10,adjust=True,ignore_na=True).mean());

df['ema20'] = (df['Close'].ewm(span=20,adjust=True,ignore_na=True).mean());

df['price_tomorrow'] = df['Close'].shift(-1);

df.dropna(inplace=True);

X = df[['ema10', 'ema20']]; y = df['price_tomorrow'];

Plot EMA with original data

fig = go.Figure(data=[go.Candlestick(x=data.index[-100:],

open=data['Open'][-100:], high=data['High'][-100:], low=data['Low'][-100:], close=data['Close'][-100:])]);

fig.add_trace(go.Scatter(x = df.index[-100:], y = df.ema10[-100:], marker = dict(color = "blue"), name = "EMA10"));

fig.add_trace(go.Scatter(x = df.index[-100:], y = df.ema20[-100:], marker = dict(color = "gray"), name = "EMA10"));

fig.update_xaxes(showline=True, linewidth=2, linecolor='black', mirror=True);

fig.update_yaxes(showline=True, linewidth=2, linecolor='black', mirror=True);

fig.update_layout(autosize = False, width = 1200, height = 600);

fig.update_layout(title='Crude oil prices, yaxis_title='(US$)');

fig.show();

Here, shorter the period, the more weight is given to recent prices. In fact a short time period generates a line closely following the actual candles on the chart. Visually this means that there are more occurrences of the price moving above or below the EMA which can be interpreted as a signal to open or close a position.

Time series data split

# Split the data into train and test data set

tscv = TimeSeriesSplit();

print(tscv);

TimeSeriesSplit(max_train_size = 0.80, n_splits=5);

for train_index, test_index in tscv.split(X):

#print("TRAIN:", train_index, "TEST:", test_index);

X_train, X_test = X[train_index], X[test_index];

y_train, y_test = y[train_index], y[test_index]; print('Length train set: {}'.format(len(y_train)));

print('Length test set: {}'.format(len(y_test)));

Regression rule

# Create a linear regression model

model = ElasticNet(max_iter=5000, random_state=0).fit(X_train, y_train);

print("Linear Regression model:");

print("Crude oil Price (y) = %.2f * 10 Days Moving Average (x1) \

+ %.2f * 20 Days Moving Average (x2) \

+ %.2f (constant)" % (model.coef_[0], model.coef_[1], model.intercept_));

Plot actual and predicted output

y_pred = DataFrame(model.predict(X_test), index = df[-len(y_test):].index, columns = ['price']);

y_test = DataFrame(y_test, index = df[-len(y_test):].index, columns = ['price']); fig = go.Figure();

fig.add_trace(go.Scatter(x = y_pred.index, y = y_pred.price,

marker = dict(color ="red"), name = "Actual price"));

fig.add_trace(go.Scatter(x = y_test.index, y = y_test.price, marker=dict(color = "green"), name = "Predicted price"));

fig.update_xaxes(showline = True, linewidth = 2, linecolor='black', mirror = True, showspikes = True,);

fig.update_yaxes(showline = True, linewidth = 2, linecolor='black', mirror = True, showspikes = True,);

fig.update_layout(title= "Crude oil price (predicted vs actual", yaxis_title = 'price (US$)', hovermode = "x", hoverdistance = 100, spikedistance = 1000);

fig.update_layout(autosize = False, width = 1000, height = 400,);

fig.show();

Test accuracy

accuracy = model.score(X_test, y_test);

print("Accuracy: ", round(accuracy*100,2).astype(str) + '%');

oil = DataFrame();

oil['price'] = df[-len(y_test):]['Close'];

oil['pred_next_day'] = y_pred;

oil['actual_price_next_day'] = y_test;

oil['returns'] = oil['price'].pct_change().shift(-1);

oil['signal'] = np.where(oil.pred_next_day.shift(1) < oil.pred_next_day,1,0);

oil['strategy_returns'] = oil.signal * oil['returns']; ((oil['strategy_returns']+1).cumprod()).plot(figsize=(10,5));

plt.ylabel('Cumulative Returns');

plt.show();

The Sharpe ratio is a well-known and well-reputed measure of risk-adjusted return on an investment or portfolio, developed by the economist William Sharpe.

The Formula for the Sharpe Ratio is:

Average return / Std dev of return

Usually, any Sharpe ratio > 1.0 is considered acceptable.

A ratio > 2.0 is rated as very good.

A ratio of 3.0 or higher is considered excellent.

A ratio < 1.0 is considered sub-optimal.

Buy/Sell Report

df.loc[:,'pred_price'] = model.predict(df[['ema10', 'ema20']]);

df.loc[:,'signal'] = np.where(df.pred_price.shift(1) < df.pred_price,"Buy","Sell");

df.loc[:,'price_direction'] = df['signal'].replace(('Sell', 'Buy'), (0, 1));

df.tail();

Buy/Sell signals plot