volumeProfile static method
Implementation
static VolumeProfileResult volumeProfile(List<TaBar> points, {int buckets = 24}) {
if (points.isEmpty) {
return VolumeProfileResult(buckets: [], pocPrice: 0, pocVolume: 0);
}
double lo = points.first.low ?? points.first.close;
double hi = points.first.high ?? points.first.close;
for (final p in points) {
final low = p.low ?? p.close;
final high = p.high ?? p.close;
if (low < lo) lo = low;
if (high > hi) hi = high;
}
final range = hi - lo;
if (range <= 0) {
return VolumeProfileResult(
buckets: [VolumeBucket(priceLow: lo, priceHigh: hi, volume: points.fold(0.0, (s, p) => s + (p.volume ?? 0)))],
pocPrice: (lo + hi) / 2,
pocVolume: points.fold(0.0, (s, p) => s + (p.volume ?? 0)),
valueAreaLow: lo,
valueAreaHigh: hi,
);
}
final vols = List<double>.filled(buckets, 0);
final ups = List<double>.filled(buckets, 0);
final bucketSize = range / buckets;
for (final p in points) {
final vol = p.volume ?? 0;
if (vol <= 0) continue;
final cLow = (p.low ?? p.close);
final cHigh = (p.high ?? p.close);
// Find which buckets this candle's range overlaps
final startBucket = ((cLow - lo) / bucketSize).floor().clamp(0, buckets - 1);
final endBucket = ((cHigh - lo) / bucketSize).floor().clamp(0, buckets - 1);
final span = endBucket - startBucket + 1;
final volPerBucket = vol / span;
final isUp = p.open == null || p.close >= p.open!;
for (int b = startBucket; b <= endBucket; b++) {
vols[b] += volPerBucket;
if (isUp) ups[b] += volPerBucket;
}
}
double pocVol = 0;
int pocIdx = 0;
for (int i = 0; i < buckets; i++) {
if (vols[i] > pocVol) {
pocVol = vols[i];
pocIdx = i;
}
}
final result = List.generate(
buckets,
(i) => VolumeBucket(
priceLow: lo + i * bucketSize,
priceHigh: lo + (i + 1) * bucketSize,
volume: vols[i],
upVolume: ups[i],
),
);
final (vaLo, vaHi) = valueArea(vols, pocIdx);
return VolumeProfileResult(
buckets: result,
pocPrice: lo + (pocIdx + 0.5) * bucketSize,
pocVolume: pocVol,
valueAreaLow: result[vaLo].priceLow,
valueAreaHigh: result[vaHi].priceHigh,
);
}