Posts: 6,798
Threads: 20
Joined: Feb 2020
Mar-26-2025, 10:01 PM
(This post was last modified: Mar-27-2025, 06:40 PM by deanhystad.)
Seems like you're calculation is backwards. I think this is what you are doing:
price = [1, 3, 5, 7, 11, 13, 17]
volume = [1, 1, 2, 3, 5, 8, 13]
def cma_calc(price, volume, start=0):
total_clovol = 0
total_volume = 0
for p, v in zip(price[start:], volume[start:]):
clovol = p * v
total_clovol += clovol
total_volume += v
return total_clovol / total_volume
cma = []
for start in range(len(price)):
cma.append(cma_calc(price, volume, start))
print(cma) Output: [12.575757575757576, 12.9375, 13.258064516129032, 13.827586206896552, 14.615384615384615, 15.476190476190476, 17.0]
It is very inefficient because you keep computing the cma over and over again for the same data. Computing cma for the 7 points above takes 7 + 6 + 5 + 4 + 3 + 2 + 1 = 28 calculations. The number grows quickly. 1000 points takes 499500 calculations.
If you look at the calculation, cma = sum_clovol / sum volume. Doing the calculations in the order above doesn't make use of clovol or volume data sums already collected. However, if you compute the values in reverse order:
cma[7] = clovol[7] / volume[7]
cma[6, 7] = (clovol[7] + clovol[6]) / (volumn[7] + volume[6])
cma[5, 6, 7] = (clovol[7] + clovol[6] + clovol[5]) / (volumn[7] + volume[6] + volume[5])
You can see that we can compute the next sums using the sum we just calculated.
cma[N] = (clovol_sum(N+1) + clovol[N]) / (volume_sum(N + 1) + volume[N])
This can be written in Python as:
price = [1, 3, 5, 7, 11, 13, 17]
volume = [1, 1, 2, 3, 5, 8, 13]
def cma_calc_2(price, volume):
clovol_sum = 0
volume_sum = 0
cma = []
for p, v in zip(price[::-1], volume[::-1]): # Loop backward through the price and volume data
clovol_sum += p * v
volume_sum += v
cma.append(clovol_sum / volume_sum)
# reverse order of the cma back to the original data
return cma[::-1]
print(cma_calc_2(price, volume)) Output: [12.575757575757576, 12.9375, 13.258064516129032, 13.827586206896552, 14.615384615384615, 15.476190476190476, 17.0]
The results are the same, but this code only requires 7 calculations to compute 7 cma values. 1000 points takes 1000 calculations, a 98% reduction over the loop in loop solution.
I would use numpy to do the calculations in C because python loops are slow and numpy has functions for computing cumulative sums and can supports element-wise array multiplication.
import numpy as np
price = [1, 3, 5, 7, 11, 13, 17]
volume = [1, 1, 2, 3, 5, 8, 13]
def cma_calc_3(price, volume):
price = np.flip(np.array(price))
volume = np.flip(np.array(volume))
clovol = price * volume
clovol_sum = np.cumsum(clovol)
volume_sum = np.cumsum(volume)
cma = clovol_sum / volume_sum
return np.flip(cma)
print(cma_calc_3(price, volume)) Output: [12.57575758 12.9375 13.25806452 13.82758621 14.61538462 15.47619048 17. ]
I did some timing of all three algorithms. For 1000 points of data:
1000 points
loop in loop time = 16.6 seconds
backward loop time = 0.006 seconds
numpy time = 0.0009 seconds
I started running a test for 10,000 points. After 10 minutes I stopped the test. A rough calculation extrapolating 16.6 seconds for 1000 points returned a time of 1.8 days to calculate 10,000 points.
10,000 points
loop in loop time = 1.8 days (estimated)
backward loop time = 0.017 seconds
numpy time = 0.0019 seconds
Posts: 6,798
Threads: 20
Joined: Feb 2020
If you want to stop calculating when you match a specific value (probably never going to happen unless you round), you can write the calculation as a generator.
price = [1, 3, 5, 7, 11, 13, 17]
volume = [1, 1, 2, 3, 5, 8, 13]
def cma_generator(price, volume):
clovol_sum = 0
volume_sum = 0
for i in range(len(price) - 1, -1, -1): # Loop throuh data in reverse order.
v = volume[i]
clovol_sum += price[i] * v
volume_sum += v
yield clovol_sum / volume_sum
for cma in cma_generator(price, volume):
print(cma) Output: 17.0
15.476190476190476
14.615384615384615
13.827586206896552
13.258064516129032
12.9375
12.575757575757576
The cma values are returned in the order they are calculated. The reverse order of the price and volume data
Posts: 12
Threads: 2
Joined: Jun 2024
Mar-27-2025, 02:21 PM
(This post was last modified: Mar-27-2025, 02:34 PM by Frankd.)
Quote:A rough calculation extrapolating 16.6 seconds for 1000 points returned a time of 1.8 days to calculate 10,000 points.
numpy.net is the C# equivalent. It's a good solution, I'll take that into account. Now I'm scratching my head how to implement this in ninjascript.
But i think the equivalent would be System.Numerics and LinQ.
using System.Linq; // For LINQ operations
public class CMA_Generator : Indicator
{
protected override void OnBarUpdate()
{
// Ensure there is enough data (we must have at least the number of bars required)
if (CurrentBar >= 1)
{
// Get the close price of the CurrentBar (the latest completed bar)
double currentBarClosePrice = Close[0]; // Close of the current bar (the last bar)
// Arrays to store close prices and volumes for bars
double[] closePrices = new double[CurrentBar];
double[] volumes = new double[CurrentBar];
// Fill the arrays with close prices and volumes for each bar up to the CurrentBar
for (int i = 0; i < CurrentBar; i++)
{
closePrices[i] = Bars.GetClose(i); // Get close price for the bar at index i
volumes[i] = Bars.GetVolume(i); // Get volume for the bar at index i
}
// Efficient cumulative sum and weighted sum using LINQ
double clovolSum = closePrices.Zip(volumes, (price, volume) => price * volume).Sum(); // Weighted sum of close prices and volumes
double volumeSum = volumes.Sum(); // Total volume
// Calculate the CMA (Cumulative Moving Average)
double cma = clovolSum / volumeSum;
// Calculate the absolute difference between CMA and the current bar's close price
double difference = Math.Abs(cma - currentBarClosePrice);
// Print the calculated CMA value for the current bar but not breaking at the right place
Print("CMA: " + cma);
// Break the loop if the CMA is closest to the CurrentBar close price but still not breaking at the right place
if (difference < 0.001)
{
Print("Breaking as CMA is equal to CurrentBar Close price.");
}
}
}
} Thanks
Posts: 6,798
Threads: 20
Joined: Feb 2020
Mar-27-2025, 08:24 PM
(This post was last modified: Mar-27-2025, 08:24 PM by deanhystad.)
Oops! That was 100,000 points, not 10,000. 10,000 points would take about 27 minutes. 100,000 points about 44 hours. The backward loop time for 100,000 points is 0.014 seconds. The numpy time is 0.010 seconds.
Posts: 1,094
Threads: 143
Joined: Jul 2017
Quote:In a cumulative average (CA), the data arrive in an ordered datum stream, and the user would like to get the average of all of the data up until the current datum.
I think what we need here is some clarification of the terminology used.
Maths was never my forté, but I found this:
The Cumulative Average of any stream of data x is:
Quote:n * CAn = x1 + x2 + ... + xn
So, using brute force, save all the data elements x, add them up, then divide by n to get:
Quote:CAn
I presume you have sets of data.
However, the first "Cumulative Average" for the first bar, or any bar you wish to start at, is
Quote:(high price + low price) / 2
for any bar, divided by 1, because it is the first element you consider.
Price and sales volume are not directly related. Perhaps, on a notion of wholesale versus retail, large transactions will enjoy a lower price, but, the price quoted is just that and not necessarily dependent upon the sales volume of a transaction, so I am not sure why that is under consideration, except if you only have information about the total price and volume of each sale.
For any given CA n:
Quote:CAn+1 = (CAn + (xn+1 - CAn ) / (n + 1))
You therefore only need to know the starting point high and low prices for any given bar or data element. That is your first "Cumulative Average" at n = 1. Thereafter, add each data element x n as it comes and divide by n+1 to find your next CA n+1
Your BARs are vertical price ranges in a small time range which may be regarded as a unit. The lower end of the green bars is the lowest price, the higher end of the green bar is the highest price at that time.
There are some calculation steps in between, but the result for the CA n+1 for any n is
Quote:CAn+1 = (CAn + ((low price + high price / 2)n+1 - CAn )) / (n + 1))
If you supply some "bar data", which should be tuples of (high price, low price) I don't think it will be hard to calculate the CA for any given set of bars.
There is also an integral for Continuous Moving Average
Posts: 12
Threads: 2
Joined: Jun 2024
I'm already able to program the CMA. That's not the point. The problem is finding the number of bars needed to have a CMA whose final value is equal to the current price. The idea is to avoid using a nested loop to reduce the amount of data processed. A loop that allows a countdown, for example, 300, 299, 298 to 0 without a nested loop.
for (int i = 300, 299, 298 ...; i > CurrentBar; i--)
Posts: 6,798
Threads: 20
Joined: Feb 2020
(Mar-30-2025, 11:16 AM)Frankd Wrote: I'm already able to program the CMA. That's not the point. The problem is finding the number of bars needed to have a CMA whose final value is equal to the current price. The idea is to avoid using a nested loop to reduce the amount of data processed. A loop that allows a countdown, for example, 300, 299, 298 to 0 without a nested loop.
for (int i = 300, 299, 298 ...; i > CurrentBar; i--) You have a solution that does this now, correct? Changing the direction used when computing successive cma's lets you reuse the previously calculated cumulative sums and eliminates the need for two loops.
This:
def cma_calc_2(price, volume):
clovol_sum = 0
volume_sum = 0
cma = []
for p, v in zip(price[::-1], volume[::-1]): # Loop backward through the price and volume data
clovol_sum += p * v
volume_sum += v
cma.append(clovol_sum / volume_sum)
# reverse order of the cma back to the original data
return cma[::-1]
cma = cma_calc_2(price, volume) Instead of this:
def cma_calc(price, volume, start=0):
total_clovol = 0
total_volume = 0
for p, v in zip(price[start:], volume[start:]):
clovol = p * v
total_clovol += clovol
total_volume += v
return total_clovol / total_volume
cma = []
for start in range(len(price)):
cma.append(cma_calc(price, volume, start))
Posts: 12
Threads: 2
Joined: Jun 2024
The solution works in Python. I'm just having trouble getting it to work in NinjaScript.
Posts: 6,798
Threads: 20
Joined: Feb 2020
Mar-31-2025, 10:15 PM
(This post was last modified: Mar-31-2025, 10:15 PM by deanhystad.)
This version takes out all the nice pythonic stuff and uses fixed sized arrays and indexing. Should be easy to translate to C# and is still blindingly fast compared to the inner loop method.
import numpy as np
from time import time
price = np.random.random(1000000)
volume = np.random.randint(1, 100, size=price.shape)
def cma_calc_2(price, volume):
clovol_sum = 0
volume_sum = 0
cma = np.zeros(len(price), dtype=np.float64)
for index in range(len(price)-1, -1, -1):
v = volume[index]
clovol_sum += price[index] * v
volume_sum += v
cma[index] = clovol_sum / volume_sum
return cma
start = time()
cma_calc_2(price, volume)
print(time() - start) It is still blindingly fast when compared to the inner loop method.
Posts: 12
Threads: 2
Joined: Jun 2024
Apr-01-2025, 12:46 PM
(This post was last modified: Apr-01-2025, 12:46 PM by Frankd.)
Not only does it need to be translated into C#, but it also needs to be translated into Ninjascript. This should have been a simple task, but somewhere along the line my second loop needs to include the if statement.
According to Ninjatrader forum you can write Python functions using NumPy and call them from NinjaScript via Python for .NET, which allows interoperability between C# and Python.
namespace NinjaTrader.NinjaScript.Indicators
{
public class MultipleCMACalculation : Indicator
{
private double clovolSum = 0;
private double volumeSum = 0;
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Description = @"Enter the description for your new custom Indicator here.";
Name = "MultipleCMACalculation";
Calculate = Calculate.OnBarClose;
IsOverlay = true;
DisplayInDataBox = true;
DrawOnPricePanel = true;
DrawHorizontalGridLines = true;
DrawVerticalGridLines = true;
PaintPriceMarkers = true;
ScaleJustification = NinjaTrader.Gui.Chart.ScaleJustification.Right;
//Disable this property if your indicator requires custom values that cumulate with each new market data event.
//See Help Guide for additional information.
IsSuspendedWhileInactive = true;
AddPlot(Brushes.Orange, "MultipleCMACalculation");
}
else if (State == State.DataLoaded)
{
// cwma = new Series<double>(this);
}
}
protected override void OnBarUpdate()
{
// Ensure there is enough data (we must have at least the number of bars required)
if (CurrentBar >= 1)
{
// Get the close price of the CurrentBar (the latest completed bar)
double currentBarClosePrice = Close[0]; // Close of the current bar (the last bar)
// List to store close prices and volumes for bars
List<double> closePrices = new List<double>();
List<double> volumes = new List<double>();
// Fill the lists with close prices and volumes for each bar up to the CurrentBar
for (int i = CurrentBar - 1; i >= 0; i--)
{
closePrices.Add(Close[i]); // Get close price for the bar at index i
volumes.Add(Volume[i]); // Get volume for the bar at index i
}
// Reverse the closePrices list
closePrices.Reverse();
volumes.Reverse(); // Optionally, reverse volumes if you need them in the same order as close prices
// Initialize cumulative sums
double clovolSum = 0;
double volumeSum = 0;
// Calculate weighted sum and total volume
for (int i = 0; i < closePrices.Count; i++)
{
clovolSum += closePrices[i] * volumes[i]; // Weighted sum of close prices and volumes
volumeSum += volumes[i]; // Total volume
}
// Calculate the Cumulative Moving Average (CMA)
double cma = clovolSum / volumeSum;
// Calculate the absolute difference between CMA and the current bar's close price
double difference = Math.Abs(cma - currentBarClosePrice);
// Print the calculated CMA value for the current bar
Print("CMA: " + cma);
Print("Current Bar Close Price: " + currentBarClosePrice);
// Output the difference
Print("Difference: " + difference);
// Set the output value for the strategy or indicator
Value[0] = cma;
// Allow for a small tolerance to account for floating-point precision
if (difference < 0.0001)
{
Print("Breaking as CMA is close to CurrentBar Close price.");
}
else
{
Print("CMA is not equal to CurrentBar Close price.");
}
}
}
}
}
|