← Library

Research & ML · Field note

Google Colab

Gemini

A working note — rougher than the essays, kept here for reference.

Gemini

This project aims to explore a ML pipeline for a Synthetic Data Generation using principles of Generative Adversarial Networks to create predictive/forecasting datasets for financial data analysis and predictions. It should be able to be replicated in Colab for cloud or GPU acceleration for faster computation due to its complexity.

Using ANY possible python DS/ML/DL packages available to prototype a machine learning model. Will be using multiple financial modelling/technical ananlysis tools where needed. I intend to focus on ARIMA and LSTM, as well regression and classification techniques etc. Dataset is derived from yfinance and are therefore considered labelled data.

Some other pacakages we can use are Keras, Tensorflow, Pytorch, xgboost, decisiontree, regressor, k-means clustering, exponential smoothing etc.

Prepare a ML architecture and define the training process. Also come up with statistical means to validate and test for the generated data.

Ensure that the codes produced iteratively updates the generator to produce realistic data comparable to historical/actual data.

This task will primarily be a SUPERVISED LEARNING task


Gemini

1.  Data Preprocessing: Compute technical indicators and prepare the dataset.
2.  Synthetic Data Generation: Use a Time-Series GAN to augment the dataset.
3.  Model Training: Train a Random Forest Regressor and an LSTM model.
4.  Forecasting: Predict future prices and plot the results.

Gemini

[ ]


Gemini

1
   2
   3
   4
   5
   6
   7
   8
import pandas as pd
import numpy as np
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
import tensorflow as tf
from tensorflow.keras import layers, models, optimizers
import matplotlib.pyplot as plt
import seaborn as sns # for plotting
Start coding or generate with AI.

Gemini

Data Download


Gemini

1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
# Getting the data
ticker = input("Enter the ticker symbol (e.g., AAPL, TSLA): ")
today = pd.Timestamp.today().strftime('%Y-%m-%d')
data = yf.download(ticker, start="2024-01-01", end=today)
prices = data['Adj Close'].dropna()
#only using MA and daily returns
data['Daily Return'] = data['Adj Close'].pct_change(1) * 100
data['Daily Return'] = data['Daily Return'].fillna(0)
data['Target'] = data['Adj Close'].shift(-1)
data['MA10'] = data['Adj Close'].rolling(window=10).mean()
data['MA20'] = data['Adj Close'].rolling(window=20).mean()
data['MA50'] = data['Adj Close'].rolling(window=50).mean()
data['MA80'] = data['Adj Close'].rolling(window=80).mean()
data['MA100'] = data['Adj Close'].rolling(window=100).mean()
data['MA120'] = data['Adj Close'].rolling(window=120).mean()
Start coding or generate with AI.
Enter the ticker symbol (e.g., AAPL, TSLA): AAPL
[*********************100%***********************]  1 of 1 completed

Gemini

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
import pandas as pd
import yfinance as yf
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
data.dropna(inplace=True)

#feature selection - selecting all the aboove only
features = ['Daily Return', 'Target', 'MA10', 'MA20', 'MA50', 'MA80', 'MA100', 'MA120']
data = data[features].dropna()
print("Data after preprocessing:")
print(data.head())

#data splitting - defining the input features and target
X = data[features]
y = data['Target'] #we have to focus on features since GANs generate data similar to the training data
train_data, test_data = train_test_split(X, test_size=0.2, random_state=678) #adjust test size and random state as necessary
print("Training data shape:", train_data.shape)
print("Testing data shape:", test_data.shape)

#scaler
scaler = MinMaxScaler()
train_scaled = scaler.fit_transform(train_data)
test_scaled = scaler.transform(test_data)
train_scaled = pd.DataFrame(train_scaled, columns=features, index=train_data.index) #converting to dataframe for easier manipulation
test_scaled = pd.DataFrame(test_scaled, columns=features, index=test_data.index)
print("Scaled training data:")
print(train_scaled.head())
Start coding or generate with AI.
Data after preprocessing:
Price      Daily Return      Target        MA10        MA20        MA50  \
Ticker                                                                    
Date                                                                      
2024-06-24     0.313265  208.598785  209.161513  200.834322  186.957721   
2024-06-25     0.446814  212.769363  210.752919  201.786671  187.611427   
2024-06-26     1.999330  213.617462  211.361545  202.947050  188.425467   
2024-06-27     0.398600  210.145279  211.464313  204.134867  189.322429   
2024-06-28    -1.625421  216.261475  211.103127  205.099188  190.177449   

Price             MA80       MA100       MA120  
Ticker                                          
Date                                            
2024-06-24  181.147667  181.759709  182.628841  
2024-06-25  181.503922  182.010686  182.827706  
2024-06-26  181.925886  182.278890  183.072853  
2024-06-27  182.415246  182.565625  183.344472  
2024-06-28  182.923228  182.799427  183.593210  
Training data shape: (83, 8)
Testing data shape: (21, 8)
Scaled training data:
            Daily Return    Target      MA10      MA20      MA50      MA80  \
Date                                                                         
2024-07-09      0.609413  0.872180  0.387638  0.437574  0.249596  0.130949   
2024-08-30      0.524789  0.535090  0.744674  0.702697  0.855276  0.691110   
2024-08-23      0.685831  0.684634  0.621646  0.627575  0.818148  0.618907   
2024-10-04      0.623874  0.498467  0.758662  0.799437  0.885246  0.920093   
2024-07-23      0.620130  0.383082  0.800906  0.759060  0.508349  0.289896   

               MA100     MA120  
Date                            
2024-07-09  0.074411  0.075035  
2024-08-30  0.503176  0.396925  
2024-08-23  0.434119  0.334180  
2024-10-04  0.786882  0.670078  
2024-07-23  0.182103  0.156660

Gemini

BUILDING THE GAN ARCHITECTURE


Gemini

66 ####BUILDING THE GAN ARCHITECTURE #defining the generator step 1 #the 'generator' will take random noise as input and generate data which resembles the real, historical data from tensorflow.keras import layers, models, optimizers import numpy as np import tensorflow as tf

def build_generator(latent_dim, output_dim): #dim- dimensions model = models.Sequential() model.add(layers.Dense(128, input_dim=latent_dim)) model.add(layers.LeakyReLU(alpha=0.2)) model.add(layers.Dense(256)) model.add(layers.LeakyReLU(alpha=0.2)) model.add(layers.Dense(512)) model.add(layers.LeakyReLU(alpha=0.2)) model.add(layers.Dense(output_dim, activation='relu')) #consider and explore other activation fucntions based on use cases return model #step 2; defining the discriminator #the discriminator takes the data as input and outputs as a probablity indicating whether the data is real or synthetic def build_discriminator(input_dim): model = models.Sequential() model.add(layers.Dense(512, input_dim=input_dim)) model.add(layers.LeakyReLU(alpha=0.2)) model.add(layers.Dropout(0.3)) model.add(layers.Dense(256)) model.add(layers.LeakyReLU(alpha=0.2)) model.add(layers.Dropout(0.3)) model.add(layers.Dense(128)) model.add(layers.LeakyReLU(alpha=0.2)) model.add(layers.Dropout(0.3)) model.add(layers.Dense(1, activation='relu')) #ReLU - reactified linear unit: alternatively consider sigmoid as an activation function return model

#step 3 compiling np.random.seed(678) tf.random.set_seed(678) # setting random seed

latent_dim = 100 # Size of the noise vector - defining the dimensions output_dim = train_scaled.shape[1] #number of features

discriminator = build_discriminator(output_dim) # Build and compile the discriminator if discriminator is None: raise ValueError("Discriminator model was NOT built correctly.") discriminator.compile(loss='binary_crossentropy', #loss function as binary - binary classification - discriminator classfies outputs as real or fake optimizer=optimizers.Adam(learning_rate=0.00001, beta_1=0.9), #adjust learning rate and beta as necessary, metrics=['accuracy']) #higher beta_1 gives more weight to recent gradients, lower value gives more value to historical data

generator = build_generator(latent_dim, output_dim) # Build the generator if generator is None: raise ValueError("Generator model was NOT built correctly.")

discriminator.trainable = False # Build the combined model - 'False' freezes the discriminator weights when training the generator gan_input = layers.Input(shape=(latent_dim,)) generated_data = generator(gan_input) gan_output = discriminator(generated_data)

gan = models.Model(gan_input, gan_output) gan.compile(loss='binary_crossentropy', optimizer=optimizers.Adam(learning_rate=0.00001, beta_1=0.9)) #adjust as needed

print("Discriminator Summary:") discriminator.summary() print("\nGenerator Summary:") generator.summary() print("\nGAN Summary:") gan.summary() Start coding or generate with AI.

Discriminator Summary: Model: "sequential_5"


Layer (type) Output Shape Param #

dense_16 (Dense) (None, 512) 4608

leaky_re_lu_12 (LeakyReLU) (None, 512) 0

dropout_6 (Dropout) (None, 512) 0

dense_17 (Dense) (None, 256) 131328

leaky_re_lu_13 (LeakyReLU) (None, 256) 0

dropout_7 (Dropout) (None, 256) 0

dense_18 (Dense) (None, 128) 32896

leaky_re_lu_14 (LeakyReLU) (None, 128) 0

dropout_8 (Dropout) (None, 128) 0

dense_19 (Dense) (None, 1) 129

================================================================= Total params: 168961 (660.00 KB) Trainable params: 0 (0.00 Byte) Non-trainable params: 168961 (660.00 KB)


Generator Summary: Model: "sequential_6"


Layer (type) Output Shape Param #

dense_20 (Dense) (None, 128) 12928

leaky_re_lu_15 (LeakyReLU) (None, 128) 0

dense_21 (Dense) (None, 256) 33024

leaky_re_lu_16 (LeakyReLU) (None, 256) 0

dense_22 (Dense) (None, 512) 131584

leaky_re_lu_17 (LeakyReLU) (None, 512) 0

dense_23 (Dense) (None, 8) 4104

================================================================= Total params: 181640 (709.53 KB) Trainable params: 181640 (709.53 KB) Non-trainable params: 0 (0.00 Byte)


GAN Summary: Model: "model_1"


Layer (type) Output Shape Param #

input_2 (InputLayer) [(None, 100)] 0

sequential_6 (Sequential) (None, 8) 181640

sequential_5 (Sequential) (None, 1) 168961

================================================================= Total params: 350601 (1.34 MB) Trainable params: 181640 (709.53 KB) Non-trainable params: 168961 (660.00 KB)



---

Gemini

TRAINING GAN Parameters

---

Gemini

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

TRAINING GAN Parameters

#training the parameters - adjust all as required epochs = 10000 batch_size = 64 save_interval = 1000

real = np.ones((batch_size, 1)) fake = np.zeros((batch_size, 1))

scaler_relu = MinMaxScaler(feature_range=(-1,1)) train_scaled_relu = scaler_relu.fit_transform(train_data) train_scaled_relu = pd.DataFrame(train_scaled_relu, columns=features, index=train_data.index)

output_dim = train_scaled_relu.shape[1]

generator = build_generator(latent_dim, output_dim)

from tensorflow.keras import optimizers #rebuild discriminator discriminator = build_discriminator(output_dim) discriminator.compile(loss='binary_crossentropy', optimizer=optimizers.Adam(learning_rate=0.00001, beta_1=0.9), metrics=['accuracy']) #recompilation discriminator.trainable = False gan = models.Sequential([generator, discriminator]) gan.compile(loss='binary_crossentropy', optimizer=optimizers.Adam(learning_rate=0.00001, beta_1=0.9)) Start coding or generate with AI.


---

Gemini

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

Training loop

for epoch in range(1, epochs+1): # Train Discriminator # Select a random batch of real samples idx = np.random.randint(0, train_scaled_relu.shape[0], batch_size) #pre-determined batch size = 64, can be inreased real_data = train_scaled_relu.iloc[idx].values

    noise = np.random.normal(0, 1, (batch_size, latent_dim))  # Generate a batch of fake samples - random noise sampled from a normal distribution
    generated_data = generator.predict(noise, verbose=0) #fake data that's produced by the generator when fed the noise input

    d_loss_real = discriminator.train_on_batch(real_data, real) # Train the discriminator on real and fake data
    d_loss_fake = discriminator.train_on_batch(generated_data, fake)
    d_loss = (np.add(d_loss_real, d_loss_fake))/2 #calculate average loss and accuracy
    # d_loss_real and d_loss_fake contain two components each:
      # Loss: How well the discriminator predicts real or fake correctly.
      # Accuracy: Percentage of correct predictions (real or fake).

    #  Train Generator
    noise = np.random.normal(0, 1, (batch_size, latent_dim)) # Generate noise for the generator
    g_loss = gan.train_on_batch(noise, real) # We want the discriminator to label generated (fake) data as real - generator's goal is to fool the discriminator into believing its fake data is real

    if epoch % 1000 == 0 or epoch == 1:
        print(f"Epoch {epoch} / {epochs} | D Loss: {d_loss[0]:.4f}, D Acc: {d_loss[1] * 100:.2f}% | G Loss: {g_loss:.4f}")  # printing progress - removed [0] from g_loss

    if epoch % save_interval == 0 or epoch == epochs:
            noise = np.random.normal(0, 1, (batch_size, latent_dim))
            generated_samples = generator.predict(noise, verbose=0)
            generated_samples = scaler_relu.inverse_transform(generated_samples) # Inverse transform to original scale
            generated_df = pd.DataFrame(generated_samples, columns=features) #converting generated samples into dataframe
            print(f"Synthetic Data Sample at Epoch {epoch}:") #printing sample data
            print(generated_df.head())

Start coding or generate with AI.

Epoch 1 / 10000 | D Loss: 5.4756, D Acc: 50.00% | G Loss: 12.8922 Epoch 1000 / 10000 | D Loss: 0.7445, D Acc: 53.12% | G Loss: 1.1704 Synthetic Data Sample at Epoch 1000: Daily Return Target MA10 MA20 MA50 MA80
0 0.300927 224.030685 225.821121 230.540787 226.581360 211.372070
1 1.812495 225.974426 225.386612 229.023697 230.448730 211.900177
2 4.641741 224.299210 225.219833 235.758453 228.665665 203.015442
3 2.737932 224.005371 225.897568 230.660049 225.621597 207.525635
4 1.453462 225.319244 221.880997 219.470810 218.888840 203.015442

    MA100       MA120  

0 203.199707 201.925537
1 203.199707 201.925537
2 203.199707 209.124756
3 203.199707 201.925537
4 203.199707 201.925537
Epoch 2000 / 10000 | D Loss: 0.7449, D Acc: 57.03% | G Loss: 2.2765 Synthetic Data Sample at Epoch 2000: Daily Return Target MA10 MA20 MA50 MA80
0 0.797709 237.310928 230.609360 238.493378 253.877594 237.625244
1 -0.555073 241.225266 226.208115 236.253494 255.273407 230.365570
2 -0.555073 239.030899 229.177399 231.051422 251.746552 233.690292
3 -0.555073 242.372955 233.143982 233.058289 246.645477 232.848160
4 -0.555073 232.704590 227.830841 238.632172 251.034256 228.968796

    MA100       MA120  

0 203.199707 201.925537
1 203.199707 201.925537
2 203.199707 201.925537
3 203.199707 201.925537
4 203.199707 201.925537
Epoch 3000 / 10000 | D Loss: 0.4794, D Acc: 69.53% | G Loss: 2.4615 Synthetic Data Sample at Epoch 3000: Daily Return Target MA10 MA20 MA50 MA80
0 -0.555073 226.388565 237.734436 232.490814 229.476547 213.999084
1 1.624098 221.491531 239.961838 233.094742 222.613235 218.125992
2 0.365362 222.963425 225.223816 224.603622 211.134750 212.564484
3 0.295727 221.491531 234.037506 229.526764 214.882782 225.401718
4 -0.555073 221.491531 236.154388 228.593185 223.279541 224.667923

    MA100       MA120  

0 228.843292 201.925537
1 230.821045 201.925537
2 214.958939 201.925537
3 235.215240 201.925537
4 242.174042 201.925537
Epoch 4000 / 10000 | D Loss: 0.7006, D Acc: 64.06% | G Loss: 1.4643 Synthetic Data Sample at Epoch 4000: Daily Return Target MA10 MA20 MA50 MA80
0 0.423339 234.433502 221.015640 225.389618 218.530182 213.975876
1 1.580592 233.384857 224.892288 219.189026 219.181168 216.423462
2 0.112791 235.730896 222.059113 227.609833 226.651978 236.120056
3 -0.555073 240.116455 221.015640 231.325058 220.200562 216.929214
4 -0.555073 244.388870 221.015640 235.341965 247.477676 236.895889

    MA100       MA120  

0 215.195175 213.684753
1 203.199707 213.017700
2 214.390076 208.926056
3 214.499252 219.305588
4 223.360611 218.220871
Epoch 5000 / 10000 | D Loss: 0.4555, D Acc: 78.12% | G Loss: 1.4400 Synthetic Data Sample at Epoch 5000: Daily Return Target MA10 MA20 MA50 MA80
0 -0.555073 222.007599 221.015640 225.246826 221.553604 225.284943
1 -0.555073 224.608032 225.818344 230.289948 229.551788 228.607666
2 1.224951 221.491531 224.712585 234.526978 234.666000 244.222870
3 -0.555073 223.885345 224.956039 220.447250 213.934937 224.223053
4 1.002196 221.491531 221.015640 221.400665 225.804916 235.317459

    MA100       MA120  

0 210.546341 216.845703
1 214.893555 221.936890
2 208.342819 215.846695
3 211.158081 212.073685
4 219.478043 217.187363
Epoch 6000 / 10000 | D Loss: 0.4634, D Acc: 75.78% | G Loss: 1.3337 Synthetic Data Sample at Epoch 6000: Daily Return Target MA10 MA20 MA50 MA80
0 -0.438455 223.813904 230.367081 221.756439 230.968262 224.996124
1 -0.555073 234.939407 236.938293 228.998917 229.595932 216.074280
2 -0.555073 229.631088 230.339645 231.666550 224.591522 211.875488
3 -0.101488 228.843704 231.288940 236.469009 240.274490 229.166718
4 -0.555073 226.227478 227.032303 219.473648 232.619766 222.648026

    MA100       MA120  

0 215.488846 209.713501
1 204.807953 205.603729
2 219.767593 211.093246
3 222.984390 213.650650
4 223.348892 216.087997
Epoch 7000 / 10000 | D Loss: 0.3951, D Acc: 82.03% | G Loss: 1.2929 Synthetic Data Sample at Epoch 7000: Daily Return Target MA10 MA20 MA50 MA80
0 -0.555073 222.302399 225.019806 228.053452 220.255844 228.683716
1 -0.555073 225.429901 229.288071 242.433762 237.900238 235.013794
2 -0.400919 224.211212 226.969055 231.588165 231.093536 237.305588
3 -0.555073 235.459106 230.383224 227.786438 219.396927 236.920273
4 0.364467 235.350876 231.862625 227.138687 220.200668 215.650421

    MA100       MA120  

0 230.721649 212.411575
1 229.765411 211.884415
2 225.823563 208.946915
3 225.185928 212.935638
4 212.509872 211.157379
Epoch 8000 / 10000 | D Loss: 0.4054, D Acc: 80.47% | G Loss: 1.3201 Synthetic Data Sample at Epoch 8000: Daily Return Target MA10 MA20 MA50 MA80
0 0.437472 222.862488 222.673431 223.180618 220.048309 214.362747
1 0.213966 224.228882 225.321442 227.910828 221.386093 217.929749
2 -0.555073 228.983292 226.834656 234.189819 218.423431 228.296921
3 -0.555073 226.058212 221.015640 221.993225 214.177765 217.907135
4 -0.555073 223.395859 225.953003 223.137238 215.727463 225.212158

    MA100       MA120  

0 215.113388 209.067047
1 212.576706 211.169556
2 229.411362 224.663834
3 216.226395 218.464890
4 218.270126 211.267700
Epoch 9000 / 10000 | D Loss: 0.4314, D Acc: 79.69% | G Loss: 1.4937 Synthetic Data Sample at Epoch 9000: Daily Return Target MA10 MA20 MA50 MA80
0 -0.373801 222.152466 222.010681 222.972137 224.551804 217.212067
1 -0.555073 229.158691 227.670776 227.348145 221.314819 220.309189
2 0.321130 229.597656 221.015640 225.292648 229.883514 213.521072
3 1.650209 230.449310 227.909424 226.253525 222.491318 221.357864
4 0.866607 223.799545 222.664368 227.336700 221.289841 213.702179

    MA100       MA120  

0 216.593643 204.626175
1 216.236542 220.031189
2 212.158646 201.925537
3 216.878876 213.973740
4 213.983902 205.297424
Epoch 10000 / 10000 | D Loss: 0.4155, D Acc: 80.47% | G Loss: 1.3440 Synthetic Data Sample at Epoch 10000: Daily Return Target MA10 MA20 MA50 MA80
0 -0.555073 227.400085 229.990250 231.998505 232.831146 225.211563
1 -0.201686 224.619324 222.427002 227.497284 222.546295 220.034897
2 -0.555073 227.832367 228.835922 228.479843 228.501938 223.336014
3 -0.132068 229.730560 227.927765 230.235413 233.288818 220.932495
4 -0.555073 221.491531 225.760635 225.965469 233.960617 215.009079

    MA100       MA120  

0 223.988708 222.565308
1 212.721558 204.813385
2 215.325104 207.289886
3 218.111877 210.450485
4 214.325790 215.717468


---

Gemini

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

GENERATING THE SYNTHETIC DATA

Function to generate synthetic data

def generate_synthetic_data(generator, scaler, num_samples): noise = np.random.normal(0, 1, (num_samples, latent_dim)) generated_data = generator.predict(noise) # Inverse transform to original scale generated_data = scaler.inverse_transform(generated_data) synthetic_df = pd.DataFrame(generated_data, columns=features) return synthetic_df

Generating synthetic data

synthetic_data = generate_synthetic_data(generator, scaler_relu, 100000) # Generate 100000 synthetic samples - adjust number as needed

print("Synthetic Data Samples:") print(synthetic_data.head()) Start coding or generate with AI.

3125/3125 [==============================] - 5s 2ms/step Synthetic Data Samples: Daily Return Target MA10 MA20 MA50 MA80
0 -0.555073 225.253571 228.211472 233.324585 224.703903 224.260803
1 0.460629 221.491531 230.262375 227.000702 225.159561 222.358063
2 -0.555073 227.864227 228.179276 227.527588 221.283844 217.587677
3 0.336712 226.227905 225.091156 219.034042 216.282318 214.969574
4 -0.555073 224.898224 226.612381 225.889359 223.154236 217.779297

    MA100       MA120  

0 213.158157 203.735580
1 220.683594 210.636612
2 218.754227 216.948303
3 213.596863 207.087921
4 218.817230 214.346832


---

Gemini

1 2 from IPython.display import display display(synthetic_data) Start coding or generate with AI.


---

Gemini

1 2 3 4 5 6 7 8 9 10

Statistical summary

real_stats = train_scaled_relu.describe() synthetic_stats = scaler_relu.transform(synthetic_data) synthetic_stats = pd.DataFrame(synthetic_stats, columns=features)

print("Real Data Statistics:") print(real_stats)

print("\nSynthetic Data Statistics:") print(synthetic_stats.describe()) Start coding or generate with AI.

Real Data Statistics: Daily Return Target MA10 MA20 MA50 MA80
count 83.000000 83.000000 83.000000 83.000000 83.000000 83.000000
mean 0.147736 0.218260 0.229793 0.473319 0.494877 0.298927
std 0.321850 0.421383 0.454511 0.402440 0.516424 0.625288
min -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000
25% -0.003503 0.002470 0.008722 0.283813 0.244128 -0.208367
50% 0.172294 0.292632 0.321516 0.531799 0.737329 0.448896
75% 0.314796 0.463761 0.502915 0.707552 0.815695 0.883009
max 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000

       MA100      MA120  

count 83.000000 83.000000
mean 0.080485 -0.051988
std 0.639788 0.615456
min -1.000000 -1.000000
25% -0.476497 -0.583817
50% 0.075275 -0.141469
75% 0.669914 0.483734
max 1.000000 1.000000

Synthetic Data Statistics: Daily Return Target MA10 MA20 MA50
count 1.000000e+05 1.000000e+05 1.000000e+05 1.000000e+05 1.000000e+05
mean 1.212215e-01 4.372979e-01 4.917730e-01 7.428116e-01 8.420885e-01
std 1.823664e-01 2.625284e-01 2.814346e-01 2.525702e-01 2.362031e-01
min -4.919832e-09 3.062386e-07 -8.694138e-07 1.314876e-07 -5.716868e-08
25% -4.919832e-09 2.447310e-01 2.881342e-01 5.669161e-01 6.779279e-01
50% -4.919832e-09 4.224389e-01 4.820891e-01 7.294422e-01 8.308272e-01
75% 2.006224e-01 6.081367e-01 6.825519e-01 9.044234e-01 9.946167e-01
max 1.387696e+00 2.000179e+00 1.834380e+00 2.120802e+00 2.069689e+00

           MA80         MA100         MA120  

count 1.000000e+05 1.000000e+05 1.000000e+05
mean 8.483974e-01 6.639555e-01 4.947809e-01
std 2.819230e-01 3.038520e-01 3.602992e-01
min -3.157802e-07 -1.776091e-07 -3.876443e-07
25% 6.523783e-01 4.536901e-01 2.021655e-01
50% 8.351762e-01 6.576741e-01 4.713856e-01
75% 1.031090e+00 8.651929e-01 7.387958e-01
max 2.479958e+00 2.293496e+00 2.238067e+00


---

Gemini

Visualisation of Data

---

Gemini

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 import matplotlib.pyplot as plt import seaborn as sns from scipy.stats import entropy import numpy as np import pandas as pd

Number of features

num_features = len(features) plt.figure(figsize=(10, 15))

for i, feature in enumerate(features): # Calculate KL divergence for the features real_density, real_bins = np.histogram(train_scaled_relu[feature], bins=50, density=True) synthetic_density, _ = np.histogram(synthetic_data[feature], bins=real_bins, density=True) kl_divergence = entropy(real_density + 1e-10, synthetic_density + 1e-10) # Add small epsilon to avoid division by zero

plt.subplot(num_features, 1, i + 1)
sns.kdeplot(train_scaled_relu[feature], label='Real', shade=True)
sns.kdeplot(synthetic_data[feature], label='Synthetic', shade=True)
plt.title(f'Distribution of {feature} (KL Divergence: {kl_divergence:.4f})')
plt.legend()

plt.tight_layout() plt.show()

Split the real and synthetic KDE plots into side-by-side subplots for clearer visual comparison

plt.figure(figsize=(10, 15)) for i, feature in enumerate(features): plt.subplot(num_features, 2, i * 2 + 1) sns.kdeplot(train_scaled_relu[feature], label='Real', shade=True, color='blue') plt.title(f'Real Data Distribution of {feature}') plt.legend()

plt.subplot(num_features, 2, i * 2 + 2)
sns.kdeplot(synthetic_data[feature], label='Synthetic', shade=True, color='orange')
plt.title(f'Synthetic Data Distribution of {feature}')
plt.legend()

plt.tight_layout() plt.show()

CORRELATION ANALYSIS

Correlation matrices

real_corr = train_scaled_relu.corr() synthetic_corr = synthetic_data.corr()

Plot correlation matrices side by side

plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1) sns.heatmap(real_corr, annot=True, cmap='coolwarm', fmt=".2f") plt.title('Real Data Correlation')

plt.subplot(1, 2, 2) sns.heatmap(synthetic_corr, annot=True, cmap='coolwarm', fmt=".2f") plt.title('Synthetic Data Correlation')

plt.tight_layout() plt.show()

Difference in correlation matrices

corr_diff = real_corr - synthetic_corr

plt.figure(figsize=(18, 6))

plt.subplot(1, 3, 1) sns.heatmap(real_corr, annot=True, cmap='coolwarm', cbar=False, fmt=".2f") plt.title('Real Data Correlation')

plt.subplot(1, 3, 2) sns.heatmap(synthetic_corr, annot=True, cmap='coolwarm', cbar=False, fmt=".2f") plt.title('Synthetic Data Correlation')

plt.subplot(1, 3, 3) sns.heatmap(corr_diff, annot=True, cmap='coolwarm', cbar=True, fmt=".2f") plt.title('Correlation Difference (Real - Synthetic)')

plt.tight_layout() plt.show()

Assuming `d_loss` and `discriminator_loss` are recorded lists during training

epochs = range(1, len(d_loss) + 1)

plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1) plt.plot(epochs, d_loss, label='Discriminator Accuracy', color='blue') plt.xlabel('Epochs') plt.ylabel('Accuracy') plt.title('Discriminator Accuracy Over Epochs') plt.legend()

plt.subplot(1, 2, 2) plt.plot(epochs, d_loss, label='Discriminator Loss', color='red') plt.xlabel('Epochs') plt.ylabel('Loss') plt.title('Discriminator Loss Over Epochs') plt.legend()

plt.tight_layout() plt.show()

Calculate mean and std for real and synthetic data

real_stats = train_scaled_relu.describe().loc[['mean', 'std']].T synthetic_stats = synthetic_data.describe().loc[['mean', 'std']].T

Plot side-by-side bar charts

plt.figure(figsize=(15, 5)) x = np.arange(len(features))

Means

plt.subplot(1, 2, 1) plt.bar(x - 0.2, real_stats['mean'], 0.4, label='Real', color='blue') plt.bar(x + 0.2, synthetic_stats['mean'], 0.4, label='Synthetic', color='orange') plt.xticks(x, features, rotation=90) plt.ylabel('Mean') plt.title('Feature Means') plt.legend()

Standard Deviations

plt.subplot(1, 2, 2) plt.bar(x - 0.2, real_stats['std'], 0.4, label='Real', color='blue') plt.bar(x + 0.2, synthetic_stats['std'], 0.4, label='Synthetic', color='orange') plt.xticks(x, features, rotation=90) plt.ylabel('Standard Deviation') plt.title('Feature Standard Deviations') plt.legend()

plt.tight_layout() plt.show()

Calculate metrics for each feature

metrics = [] for feature in features: real_mean = train_scaled_relu[feature].mean() synthetic_mean = synthetic_data[feature].mean() real_std = train_scaled_relu[feature].std() synthetic_std = synthetic_data[feature].std()

# Calculate KL Divergence
real_density, real_bins = np.histogram(train_scaled_relu[feature], bins=50, density=True)
synthetic_density, _ = np.histogram(synthetic_data[feature], bins=real_bins, density=True)
kl_divergence = entropy(real_density + 1e-10, synthetic_density + 1e-10)  # Add epsilon to avoid NaNs

metrics.append([feature, real_mean, synthetic_mean, real_std, synthetic_std, kl_divergence])

Convert metrics to DataFrame

metrics_df = pd.DataFrame(metrics, columns=['Feature', 'Real Mean', 'Synthetic Mean', 'Real Std', 'Synthetic Std', 'KL Divergence']) display(metrics_df)

Start coding or generate with AI.


---

Gemini

Visualisation of data

---

Gemini

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

SAVING THE TRAINED GAN MODELS

Save generator and discriminator models

generator.save('generator_model.h5') discriminator.save('discriminator_model.h5')

#To reload the models from tensorflow.keras.models import load_model generator = load_model('generator_model.h5') discriminator = load_model('discriminator_model.h5')

INCOPORATING MONITORING TOOLS

from tensorflow.keras.callbacks import TensorBoard

Define TensorBoard callback

tensorboard = TensorBoard(log_dir='./logs', histogram_freq=1, write_graph=True, write_images=True) Start coding or generate with AI.


---

Gemini

Model Performance Evaluation

---

Gemini

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

EVALUATION OF MODEL PERFORMANCE - final step before further parameter tuning and model reruns

from sklearn.decomposition import PCA

Combine real and synthetic data

combined_data = pd.concat([train_scaled_relu.sample(1000, random_state=678), scaler_relu.transform(synthetic_data).tolist()], axis=0) labels = ['Real'] * 1000 + ['Synthetic'] * len(synthetic_data)

Apply PCA

pca = PCA(n_components=2) pca_result = pca.fit_transform(combined_data)

Create a DataFrame for plotting

pca_df = pd.DataFrame(pca_result, columns=['PC1', 'PC2']) pca_df['Type'] = labels

Plot

plt.figure(figsize=(10, 7)) sns.scatterplot(x='PC1', y='PC2', hue='Type', data=pca_df, alpha=0.5) plt.title('PCA of Real vs Synthetic Data') plt.show()

#Interpretation:

• Overlap: Greater overlap indicates that the synthetic data resembles the real data.

• Separation: Minimal separation suggests good similarity between real and synthetic data.

Start coding or generate with AI.


---

Gemini

Hyperparameter Tuning: Experiment with different hyperparameters to improve GAN performance: • Learning Rates: Adjust the learning rates for the generator and discriminator. • Batch Size: Larger or smaller batch sizes can impact training stability. • Architecture Depth: Add or remove layers and neurons in the generator and discriminator. • Activation Functions: Experiment with different activation functions.

Advanced GAN Architectures: Consider advanced GAN variants for better performance on tabular data: • Conditional GANs (cGANs): Incorporate additional information or conditions. • Wasserstein GANs (WGANs): Improve training stability. • Tabular GANs (e.g., CTGAN, TGAN): Specifically designed for tabular data.

Handling Categorical Features: if you there are categorical features in the future models/runs, ensure proper encoding (e.g., one-hot encoding) before feeding them into the GAN.

Evaluating model performance: • Visual Inspection: Plot feature distributions and correlations as shown earlier. • Downstream Tasks: Use the synthetic data to train machine learning models and evaluate performance compared to models trained on real data. • Dimensionality Reduction: Use PCA or t-SNE to visualize real and synthetic data in lower dimensions.

---

Gemini

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

EVALUATION OF MODEL PERFORMANCE - final step before further parameter tuning and reruns

from sklearn.decomposition import PCA

Combine real and synthetic data

combined_data = pd.concat([train_scaled_relu.sample(1000, random_state=678), #use a standardised random state to replicatae results scaler_relu.transform(synthetic_data).tolist()], axis=0) labels = ['Real'] * 1000 + ['Synthetic'] * len(synthetic_data)

Apply PCA

pca = PCA(n_components=2) pca_result = pca.fit_transform(combined_data)

Create a DataFrame for plotting

pca_df = pd.DataFrame(pca_result, columns=['PC1', 'PC2']) pca_df['Type'] = labels

Plot

plt.figure(figsize=(10, 7)) sns.scatterplot(x='PC1', y='PC2', hue='Type', data=pca_df, alpha=0.5) plt.title('PCA of Real vs Synthetic Data') plt.show()

#Interpretation:

• Overlap: Greater overlap indicates that the synthetic data resembles the real data.

• Separation: Minimal separation suggests good similarity between real and synthetic data.

Start coding or generate with AI.


---

Gemini

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 import tensorflow as tf from tensorflow.keras import layers

Generator Model

def build_generator(seq_length, features): model = tf.keras.Sequential([ layers.Input(shape=(seq_length, features)), layers.LSTM(128, return_sequences=True), layers.LSTM(64), layers.Dense(seq_length * features), layers.Reshape((seq_length, features)), ]) return model

Discriminator Model

def build_discriminator(seq_length, features): model = tf.keras.Sequential([ layers.Input(shape=(seq_length, features)), layers.LSTM(64, return_sequences=True), layers.LSTM(32), layers.Dense(1, activation='sigmoid'), #0-1 range, commonly used as an activation function in ML ]) return model

Hyperparameters

seq_length = 50 features = normalized_data.shape[1]

Initialize models

generator = build_generator(seq_length, features) discriminator = build_discriminator(seq_length, features)

Discriminator

discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

GAN Model (Combining Generator + Discriminator)

discriminator.trainable = False gan_input = tf.keras.Input(shape=(seq_length, features)) generated_data = generator(gan_input) gan_output = discriminator(generated_data) gan = tf.keras.Model(gan_input, gan_output) gan.compile(optimizer='adam', loss='binary_crossentropy')

import tensorflow as tf from tensorflow.keras import layers

GAN Generator

def build_generator(seq_length, features): model = tf.keras.Sequential([ layers.Input(shape=(seq_length, features)), layers.LSTM(128, return_sequences=True), layers.LSTM(64), layers.Dense(seq_length * features), layers.Reshape((seq_length, features)), ]) return model

GAN Discriminator

def build_discriminator(seq_length, features): model = tf.keras.Sequential([ layers.Input(shape=(seq_length, features)), layers.LSTM(64, return_sequences=True), layers.LSTM(32), layers.Dense(1, activation='sigmoid'), ]) return model

Hyperparameters

seq_length = 50 # Number of time steps in each sequence features = normalized_data.shape[1]

Build GAN models

generator = build_generator(seq_length, features) discriminator = build_discriminator(seq_length, features)

Compile discriminator

discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

GAN combining generator and discriminator

discriminator.trainable = False gan_input = tf.keras.Input(shape=(seq_length, features)) generated_data = generator(gan_input) gan_output = discriminator(generated_data) gan = tf.keras.Model(gan_input, gan_output) gan.compile(optimizer='adam', loss='binary_crossentropy')

Prepare training sequences

def create_sequences(data, seq_length): sequences = [] for i in range(len(data) - seq_length): sequences.append(data[i:i + seq_length]) return np.array(sequences)

sequences = create_sequences(normalized_data, seq_length)

Training loop

epochs = 1000 batch_size = 64

for epoch in range(epochs): # Sample real data idx = np.random.randint(0, sequences.shape[0], batch_size) real_data = sequences[idx]

# Generate synthetic data
noise = np.random.normal(0, 1, (batch_size, seq_length, features))
synthetic_data = generator.predict(noise)

# Train discriminator
real_labels = np.ones((batch_size, 1))
fake_labels = np.zeros((batch_size, 1))
d_loss_real = discriminator.train_on_batch(real_data, real_labels)
d_loss_fake = discriminator.train_on_batch(synthetic_data, fake_labels)
d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

# Train generator
g_loss = gan.train_on_batch(noise, real_labels)

# Print progress
if epoch % 100 == 0:
    print(f"Epoch {epoch}, D Loss: {d_loss}, G Loss: {g_loss}")

Start coding or generate with AI.


---

Gemini

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

LSTM Model for forecasting

def build_lstm_model(input_shape): model = tf.keras.Sequential([ layers.LSTM(128, return_sequences=True, input_shape=input_shape), layers.LSTM(64), layers.Dense(1) ]) model.compile(optimizer='adam', loss='mean_squared_error') return model

Prepare LSTM data

X = sequences[:, :-1] y = sequences[:, -1, 0] # Predict the next 'Adj Close'

Train LSTM

lstm_model = build_lstm_model((seq_length - 1, features)) lstm_model.fit(X, y, epochs=1000, batch_size=64)

Forecast with LSTM

lstm_forecast = lstm_model.predict(X[-10:]) print("LSTM Forecast:", scaler.inverse_transform(lstm_forecast)) Start coding or generate with AI.


---

Gemini

1 2 3 4 5 from sklearn.metrics import mean_squared_error rmse = np.sqrt(mean_squared_error(real_values, predicted_values)) print("RMSE:", rmse) mape = np.mean(np.abs((real_values - predicted_values) / real_values)) * 100 print("MAPE:", mape) Start coding or generate with AI.