652 行
47 KiB
MQL5
652 行
47 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| NetGenetic.mqh |
|
|
//| Copyright DNG® |
|
|
//| https://www.mql5.com/ru/users/dng |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright DNG®"
|
|
#property link "https://www.mql5.com/ru/users/dng"
|
|
#property version "1.00"
|
|
#include "..\NeuroNet_DNG\NeuroNet.mqh"
|
|
//---
|
|
#define MaxMutation 0.5f
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CNetGenetic : public CNet
|
|
{
|
|
protected:
|
|
uint i_PopulationSize;
|
|
vectorf v_Probability;
|
|
vectorf v_Rewards;
|
|
matrixf m_Weights;
|
|
matrixf m_WeightsConv;
|
|
|
|
//---
|
|
virtual bool CreatePopulation(void);
|
|
virtual int GetAction(CBufferFloat * probability);
|
|
virtual bool GetWeights(uint layer);
|
|
float NextGenerationWeight(matrixf &array, uint shift, vectorf &probability);
|
|
float GenerateWeight(uint total);
|
|
|
|
public:
|
|
CNetGenetic();
|
|
~CNetGenetic();
|
|
//---
|
|
virtual bool Create(CArrayObj *Description, uint population_size);
|
|
virtual bool SetPopulationSize(uint size);
|
|
virtual bool feedForward(CArrayFloat *inputVals, int window = 1, bool tem = true);
|
|
virtual bool Rewards(CArrayFloat *rewards);
|
|
virtual bool NextGeneration(float quantile, float mutation, float &average, float &mamximum);
|
|
virtual bool Load(string file_name, uint population_size, bool common = true);
|
|
virtual bool SaveModel(string file_name, int model, bool common = true);
|
|
//---
|
|
virtual bool CopyModel(CArrayLayer *source, uint model);
|
|
virtual bool Detach(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
CNetGenetic::CNetGenetic() : i_PopulationSize(100)
|
|
{
|
|
SetPopulationSize(i_PopulationSize);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
CNetGenetic::~CNetGenetic()
|
|
{
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNetGenetic::Create(CArrayObj *Description, uint population_size)
|
|
{
|
|
if(CheckPointer(Description) == POINTER_INVALID)
|
|
return false;
|
|
//---
|
|
if(!SetPopulationSize(population_size))
|
|
return false;
|
|
CNet::Create(Description);
|
|
return CreatePopulation();
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNetGenetic::SetPopulationSize(uint size)
|
|
{
|
|
i_PopulationSize = size;
|
|
v_Probability = vectorf::Zeros(i_PopulationSize);
|
|
v_Rewards = vectorf::Zeros(i_PopulationSize);
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNetGenetic::Load(string file_name, uint population_size, bool common = true)
|
|
{
|
|
float temp;
|
|
datetime dt;
|
|
if(!CNet::Load(file_name, temp, temp, temp, dt, common))
|
|
return false;
|
|
//---
|
|
if(!SetPopulationSize(population_size))
|
|
return false;
|
|
return CreatePopulation();
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNetGenetic::CreatePopulation(void)
|
|
{
|
|
if(!layers || layers.Total() < 2)
|
|
return false;
|
|
//---
|
|
CLayer *layer = layers.At(0);
|
|
if(!layer || !layer.At(0))
|
|
return false;
|
|
//---
|
|
CNeuronBaseOCL *neuron_ocl = layer.At(0);
|
|
int prev_count = neuron_ocl.Neurons();
|
|
for(int i = 1; i < layers.Total(); i++)
|
|
{
|
|
layer = layers.At(i);
|
|
if(!layer || !layer.At(0))
|
|
return false;
|
|
//---
|
|
neuron_ocl = layer.At(0);
|
|
CLayerDescription *desc = neuron_ocl.GetLayerInfo();
|
|
int outputs = neuron_ocl.getConnections();
|
|
//---
|
|
for(uint n = layer.Total(); n < i_PopulationSize; n++)
|
|
{
|
|
CNeuronConvOCL *neuron_conv_ocl = NULL;
|
|
CNeuronProofOCL *neuron_proof_ocl = NULL;
|
|
CNeuronAttentionOCL *neuron_attention_ocl = NULL;
|
|
CNeuronMLMHAttentionOCL *neuron_mlattention_ocl = NULL;
|
|
CNeuronDropoutOCL *dropout = NULL;
|
|
CNeuronBatchNormOCL *batch = NULL;
|
|
CVAE *vae = NULL;
|
|
CNeuronLSTMOCL *lstm = NULL;
|
|
switch(layer.At(0).Type())
|
|
{
|
|
case defNeuron:
|
|
case defNeuronBaseOCL:
|
|
neuron_ocl = new CNeuronBaseOCL();
|
|
if(CheckPointer(neuron_ocl) == POINTER_INVALID)
|
|
return false;
|
|
if(!neuron_ocl.Init(outputs, n, opencl, desc.count, desc.optimization, desc.batch))
|
|
{
|
|
delete neuron_ocl;
|
|
return false;
|
|
}
|
|
neuron_ocl.SetActivationFunction(desc.activation);
|
|
if(!layer.Add(neuron_ocl))
|
|
{
|
|
delete neuron_ocl;
|
|
return false;
|
|
}
|
|
neuron_ocl = NULL;
|
|
break;
|
|
//---
|
|
case defNeuronConvOCL:
|
|
neuron_conv_ocl = new CNeuronConvOCL();
|
|
if(CheckPointer(neuron_conv_ocl) == POINTER_INVALID)
|
|
return false;
|
|
if(!neuron_conv_ocl.Init(outputs, n, opencl, desc.window, desc.step, desc.window_out, desc.count, desc.optimization, desc.batch))
|
|
{
|
|
delete neuron_conv_ocl;
|
|
return false;
|
|
}
|
|
neuron_conv_ocl.SetActivationFunction(desc.activation);
|
|
if(!layer.Add(neuron_conv_ocl))
|
|
{
|
|
delete neuron_conv_ocl;
|
|
return false;
|
|
}
|
|
neuron_conv_ocl = NULL;
|
|
break;
|
|
//---
|
|
case defNeuronProofOCL:
|
|
neuron_proof_ocl = new CNeuronProofOCL();
|
|
if(!neuron_proof_ocl)
|
|
return false;
|
|
if(!neuron_proof_ocl.Init(outputs, n, opencl, desc.window, desc.step, desc.count, desc.optimization, desc.batch))
|
|
{
|
|
delete neuron_proof_ocl;
|
|
return false;
|
|
}
|
|
neuron_proof_ocl.SetActivationFunction(desc.activation);
|
|
if(!layer.Add(neuron_proof_ocl))
|
|
{
|
|
delete neuron_proof_ocl;
|
|
return false;
|
|
}
|
|
neuron_proof_ocl = NULL;
|
|
break;
|
|
//---
|
|
case defNeuronAttentionOCL:
|
|
neuron_attention_ocl = new CNeuronAttentionOCL();
|
|
if(CheckPointer(neuron_attention_ocl) == POINTER_INVALID)
|
|
return false;
|
|
if(!neuron_attention_ocl.Init(outputs, n, opencl, desc.window, desc.count, desc.optimization, desc.batch))
|
|
{
|
|
delete neuron_attention_ocl;
|
|
return false;
|
|
}
|
|
neuron_attention_ocl.SetActivationFunction(desc.activation);
|
|
if(!layer.Add(neuron_attention_ocl))
|
|
{
|
|
delete neuron_attention_ocl;
|
|
return false;
|
|
}
|
|
neuron_attention_ocl = NULL;
|
|
break;
|
|
//---
|
|
case defNeuronMHAttentionOCL:
|
|
neuron_attention_ocl = new CNeuronMHAttentionOCL();
|
|
if(CheckPointer(neuron_attention_ocl) == POINTER_INVALID)
|
|
return false;
|
|
if(!neuron_attention_ocl.Init(outputs, n, opencl, desc.window, desc.count, desc.optimization, desc.batch))
|
|
{
|
|
delete neuron_attention_ocl;
|
|
return false;
|
|
}
|
|
neuron_attention_ocl.SetActivationFunction(desc.activation);
|
|
if(!layer.Add(neuron_attention_ocl))
|
|
{
|
|
delete neuron_attention_ocl;
|
|
return false;
|
|
}
|
|
neuron_attention_ocl = NULL;
|
|
break;
|
|
//---
|
|
case defNeuronMLMHAttentionOCL:
|
|
neuron_mlattention_ocl = new CNeuronMLMHAttentionOCL();
|
|
if(CheckPointer(neuron_mlattention_ocl) == POINTER_INVALID)
|
|
return false;
|
|
if(!neuron_mlattention_ocl.Init(outputs, n, opencl, desc.window, desc.window_out, desc.step, desc.count, desc.layers, desc.optimization, desc.batch))
|
|
{
|
|
delete neuron_mlattention_ocl;
|
|
return false;
|
|
}
|
|
neuron_mlattention_ocl.SetActivationFunction(desc.activation);
|
|
if(!layer.Add(neuron_mlattention_ocl))
|
|
{
|
|
delete neuron_mlattention_ocl;
|
|
return false;
|
|
}
|
|
neuron_mlattention_ocl = NULL;
|
|
break;
|
|
//---
|
|
case defNeuronDropoutOCL:
|
|
dropout = new CNeuronDropoutOCL();
|
|
if(CheckPointer(dropout) == POINTER_INVALID)
|
|
return false;
|
|
if(!dropout.Init(outputs, n, opencl, desc.count, desc.probability, desc.optimization, desc.batch))
|
|
{
|
|
delete dropout;
|
|
return false;
|
|
}
|
|
if(!layer.Add(dropout))
|
|
{
|
|
delete dropout;
|
|
return false;
|
|
}
|
|
dropout = NULL;
|
|
break;
|
|
//---
|
|
case defNeuronBatchNormOCL:
|
|
batch = new CNeuronBatchNormOCL();
|
|
if(CheckPointer(batch) == POINTER_INVALID)
|
|
return false;
|
|
if(!batch.Init(outputs, n, opencl, desc.count, desc.batch, desc.optimization))
|
|
{
|
|
delete batch;
|
|
return false;
|
|
}
|
|
batch.SetActivationFunction(desc.activation);
|
|
if(!layer.Add(batch))
|
|
{
|
|
delete batch;
|
|
return false;
|
|
}
|
|
batch = NULL;
|
|
break;
|
|
//---
|
|
case defNeuronVAEOCL:
|
|
vae = new CVAE();
|
|
if(!vae)
|
|
return false;
|
|
if(!vae.Init(outputs, n, opencl, desc.count, desc.optimization, desc.batch))
|
|
{
|
|
delete vae;
|
|
return false;
|
|
}
|
|
if(!layer.Add(vae))
|
|
{
|
|
delete vae;
|
|
return false;
|
|
}
|
|
vae = NULL;
|
|
break;
|
|
case defNeuronLSTMOCL:
|
|
lstm = new CNeuronLSTMOCL();
|
|
if(!lstm)
|
|
return false;
|
|
if(!lstm.Init(outputs, n, opencl, desc.count, desc.optimization, desc.batch))
|
|
{
|
|
delete lstm;
|
|
return false;
|
|
}
|
|
if(!lstm.SetInputs(prev_count))
|
|
{
|
|
delete lstm;
|
|
return false;
|
|
}
|
|
if(!layer.Add(lstm))
|
|
{
|
|
delete lstm;
|
|
return false;
|
|
}
|
|
lstm = NULL;
|
|
break;
|
|
//---
|
|
case defNeuronSoftMaxOCL:
|
|
neuron_ocl = new CNeuronSoftMaxOCL();
|
|
if(!neuron_ocl)
|
|
return false;
|
|
if(!neuron_ocl.Init(outputs, n, opencl, desc.count, desc.optimization, desc.batch))
|
|
{
|
|
delete neuron_ocl;
|
|
return false;
|
|
}
|
|
if(!layer.Add(neuron_ocl))
|
|
{
|
|
delete neuron_ocl;
|
|
return false;
|
|
}
|
|
neuron_ocl = NULL;
|
|
break;
|
|
//---
|
|
default:
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
if(layer.Total() > (int)i_PopulationSize)
|
|
layer.Resize(i_PopulationSize);
|
|
delete desc;
|
|
}
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNetGenetic::feedForward(CArrayFloat *inputVals, int window = 1, bool tem = true)
|
|
{
|
|
if(CheckPointer(layers) == POINTER_INVALID || CheckPointer(inputVals) == POINTER_INVALID || layers.Total() <= 1)
|
|
return false;
|
|
//---
|
|
CLayer *previous = NULL;
|
|
CLayer *current = layers.At(0);
|
|
int total = MathMin(current.Total(), inputVals.Total());
|
|
CNeuronBase *neuron = NULL;
|
|
if(CheckPointer(opencl) == POINTER_INVALID)
|
|
return false;
|
|
CNeuronBaseOCL *neuron_ocl = current.At(0);
|
|
CBufferFloat *inputs = neuron_ocl.getOutput();
|
|
int total_data = inputVals.Total();
|
|
if(!inputs.Resize(total_data))
|
|
return false;
|
|
for(int d = 0; d < total_data; d++)
|
|
{
|
|
int pos = d;
|
|
int dim = 0;
|
|
if(window > 1)
|
|
{
|
|
dim = d % window;
|
|
pos = (d - dim) / window;
|
|
}
|
|
float value = pos / pow(10000, (2 * dim + 1) / (float)(window + 1));
|
|
value = (float)(tem ? (dim % 2 == 0 ? sin(value) : cos(value)) : 0);
|
|
value += inputVals.At(d);
|
|
if(!inputs.Update(d, value))
|
|
return false;
|
|
}
|
|
if(!inputs.BufferWrite())
|
|
return false;
|
|
//---
|
|
CNeuronBaseOCL *current_ocl;
|
|
for(int l = 1; l < layers.Total(); l++)
|
|
{
|
|
previous = current;
|
|
current = layers.At(l);
|
|
if(CheckPointer(current) == POINTER_INVALID)
|
|
return false;
|
|
//---
|
|
for(uint n = 0; n < i_PopulationSize; n++)
|
|
{
|
|
current_ocl = current.At(n);
|
|
if(!current_ocl.FeedForward(previous.At(l == 1 ? 0 : n)))
|
|
return false;
|
|
continue;
|
|
}
|
|
}
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNetGenetic::Rewards(CArrayFloat *rewards)
|
|
{
|
|
if(!rewards || !layers || layers.Total() < 2)
|
|
return false;
|
|
//---
|
|
CLayer *output = layers.At(layers.Total() - 1);
|
|
if(!output)
|
|
return false;
|
|
//---
|
|
for(int i = 0; i < output.Total(); i++)
|
|
{
|
|
CNeuronBaseOCL *neuron = output.At(i);
|
|
if(!neuron)
|
|
continue;
|
|
int action = GetAction(neuron.getOutput());
|
|
v_Rewards[i] += rewards.At(action >= 0 ? action : rewards.Minimum(0, WHOLE_ARRAY));
|
|
}
|
|
//---
|
|
v_Probability = v_Rewards - v_Rewards.Min();
|
|
if(!v_Probability.Clip(0, v_Probability.Max()))
|
|
return false;
|
|
v_Probability = v_Probability / v_Probability.Sum();
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
int CNetGenetic::GetAction(CBufferFloat * probability)
|
|
{
|
|
vectorf prob;
|
|
if(!probability.GetData(prob))
|
|
return -1;
|
|
prob = prob.CumSum();
|
|
prob = prob / prob.Max();
|
|
int err_code;
|
|
float random = (float)MathRandomNormal(0.5, 0.5, err_code);
|
|
if(random >= 1)
|
|
return (int)prob.Size() - 1;
|
|
for(int i = 0; i < (int)prob.Size(); i++)
|
|
if(random <= prob[i] && prob[i] > 0)
|
|
return i;
|
|
//---
|
|
return -1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNetGenetic::NextGeneration(float quantile, float mutation, float &average, float &maximum)
|
|
{
|
|
maximum = v_Rewards.Max();
|
|
mutation = MathMin(mutation, MaxMutation);
|
|
v_Probability = v_Rewards - v_Rewards.Quantile(quantile);
|
|
if(!v_Probability.Clip(0, v_Probability.Max()))
|
|
return false;
|
|
v_Probability = v_Probability / v_Probability.Sum();
|
|
average = v_Rewards.Average(v_Probability);
|
|
//---
|
|
if(!v_Probability.Resize(i_PopulationSize + 1))
|
|
return false;
|
|
v_Probability[i_PopulationSize] = mutation;
|
|
v_Probability = (v_Probability / (1 + mutation)).CumSum();
|
|
//---
|
|
for(int l = 1; l < layers.Total(); l++)
|
|
{
|
|
if(!GetWeights(l))
|
|
{
|
|
PrintFormat("Error of load weights from layer %d", l);
|
|
return false;
|
|
}
|
|
CLayer* layer = layers.At(l);
|
|
if(!layer)
|
|
return false;
|
|
for(uint i = 0; i < i_PopulationSize; i++)
|
|
{
|
|
CNeuronBaseOCL* neuron = layer.At(i);
|
|
CBufferFloat* weights = neuron.getWeights();
|
|
if(!!weights)
|
|
{
|
|
for(int w = 0; w < weights.Total(); w++)
|
|
if(!weights.Update(w, NextGenerationWeight(m_Weights, w, v_Probability)))
|
|
{
|
|
Print("Error of update weights");
|
|
return false;
|
|
}
|
|
weights.BufferWrite();
|
|
}
|
|
if(neuron.Type() != defNeuronConvOCL)
|
|
continue;
|
|
CNeuronConvOCL* temp = neuron;
|
|
weights = temp.GetWeightsConv();
|
|
for(int w = 0; w < weights.Total(); w++)
|
|
if(!weights.Update(w, NextGenerationWeight(m_WeightsConv, w, v_Probability)))
|
|
{
|
|
Print("Error of update weights");
|
|
return false;
|
|
}
|
|
weights.BufferWrite();
|
|
}
|
|
}
|
|
v_Rewards.Fill(0);
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
float CNetGenetic::NextGenerationWeight(matrixf &array, uint shift, vectorf &probability)
|
|
{
|
|
int err_code;
|
|
float random = (float)MathRandomNormal(0.5, 0.5, err_code);
|
|
for(int i = 0; i < (int)probability.Size(); i++)
|
|
if(probability[i] > 0 && random <= probability[i] && i < (int)array.Rows())
|
|
{
|
|
return array[i, shift];
|
|
}
|
|
//---
|
|
return GenerateWeight((uint)array.Cols());
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNetGenetic::GetWeights(uint layer)
|
|
{
|
|
if(!layers)
|
|
return false;
|
|
CLayer *current_layer = layers.At(layer);
|
|
if(!current_layer)
|
|
return false;
|
|
int total = current_layer.Total();
|
|
m_Weights = matrixf::Zeros(total, 0);
|
|
m_WeightsConv = matrixf::Zeros(total, 0);
|
|
for(int m = 0; m < total; m++)
|
|
{
|
|
CNeuronBaseOCL *neuron = current_layer.At(m);
|
|
if(!neuron)
|
|
return false;
|
|
vectorf buffer;
|
|
if(!!neuron.getWeights())
|
|
{
|
|
neuron.getWeights().GetData(buffer);
|
|
if(m_Weights.Cols() <= 0)
|
|
if(!m_Weights.Resize(total, buffer.Size()))
|
|
return false;
|
|
if(!m_Weights.Row(buffer, m))
|
|
continue;
|
|
}
|
|
//---
|
|
if(neuron.Type() != defNeuronConvOCL)
|
|
continue;
|
|
CNeuronConvOCL *temp = neuron;
|
|
if(temp.GetWeightsConv().GetData(buffer) <= 0)
|
|
return false;
|
|
if(m_WeightsConv.Cols() != buffer.Size())
|
|
if(!m_WeightsConv.Resize(total, buffer.Size()))
|
|
return false;
|
|
if(!m_WeightsConv.Row(buffer, m))
|
|
return false;
|
|
}
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNetGenetic::SaveModel(string file_name, int model, bool common = true)
|
|
{
|
|
if(model < 0 || model >= (int)i_PopulationSize)
|
|
model = (int)v_Probability.ArgMax();
|
|
//---
|
|
CNetGenetic *new_model = new CNetGenetic();
|
|
if(!new_model)
|
|
return false;
|
|
if(!new_model.CopyModel(layers, model))
|
|
{
|
|
new_model.Detach();
|
|
delete new_model;
|
|
return false;
|
|
}
|
|
bool result = new_model.Save(file_name, 0, 0, 0, 0, common);
|
|
new_model.Detach();
|
|
delete new_model;
|
|
//---
|
|
return result;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNetGenetic::CopyModel(CArrayLayer *source, uint model)
|
|
{
|
|
if(!source)
|
|
return false;
|
|
if(!layers)
|
|
{
|
|
layers = new CArrayLayer();
|
|
if(!layers)
|
|
return false;
|
|
}
|
|
else
|
|
layers.Clear();
|
|
for(int l = 0; l < source.Total(); l++)
|
|
{
|
|
CLayer* source_layer = source.At(l);
|
|
if(!source_layer)
|
|
return false;
|
|
CLayer* new_layer = new CLayer(source_layer.Outputs(), -1, opencl);
|
|
if(!new_layer)
|
|
return false;
|
|
if(!new_layer.Add(source_layer.At(l == 0 ? 0 : model)))
|
|
return false;
|
|
if(!layers.Add(new_layer))
|
|
return false;
|
|
}
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CNetGenetic::Detach(void)
|
|
{
|
|
if(!layers || layers.Total() <= 0)
|
|
return true;
|
|
int total = layers.Total();
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
CLayer *layer = layers.At(i);
|
|
if(!layer)
|
|
continue;
|
|
for(int n = layer.Total() - 1; n >= 0; n--)
|
|
CObject *temp = layer.Detach(n);
|
|
}
|
|
//---
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
float CNetGenetic::GenerateWeight(uint total)
|
|
{
|
|
xor128;
|
|
float result = (float)rnd_w / UINT_MAX;
|
|
if(result == 0)
|
|
return GenerateWeight(total);
|
|
float k = (float)(1 / sqrt(total + 1));
|
|
result = (2 * result * k - k) * WeightsMultiplier;
|
|
//---
|
|
return result;
|
|
}
|
|
//+------------------------------------------------------------------+
|