|
| 1 | +#include "functions/technical.hpp" |
| 2 | +#include "duckdb/function/function_set.hpp" |
| 3 | +#include "duckdb/function/scalar_function.hpp" |
| 4 | +#include "duckdb/parser/parsed_data/create_aggregate_function_info.hpp" |
| 5 | +#include "duckdb/parser/parsed_data/create_scalar_function_info.hpp" |
| 6 | +#include "duckdb/main/connection.hpp" |
| 7 | +#include "duckdb/catalog/catalog.hpp" |
| 8 | +#include <vector> |
| 9 | +#include <algorithm> |
| 10 | +#include <cmath> |
| 11 | + |
| 12 | +namespace duckdb { |
| 13 | +namespace scrooge { |
| 14 | + |
| 15 | +// ────────────────────────────────────────────────────────────── |
| 16 | +// wma(close, timestamp, period) — Weighted Moving Average |
| 17 | +// |
| 18 | +// Recent values get more weight. Weight_i = i (1 for oldest, n for newest). |
| 19 | +// WMA = Σ(weight_i × value_i) / Σ(weight_i) |
| 20 | +// ────────────────────────────────────────────────────────────── |
| 21 | + |
| 22 | +struct WMAState { |
| 23 | + struct Entry { double value; int64_t ts; }; |
| 24 | + std::vector<Entry> *entries; |
| 25 | +}; |
| 26 | + |
| 27 | +static void WMAInitialize(const AggregateFunction &, data_ptr_t state_p) { |
| 28 | + auto &s = *reinterpret_cast<WMAState *>(state_p); |
| 29 | + s.entries = nullptr; |
| 30 | +} |
| 31 | + |
| 32 | +static void WMAUpdate(Vector inputs[], AggregateInputData &, idx_t, Vector &state_vector, idx_t count) { |
| 33 | + UnifiedVectorFormat v_data, ts_data, sdata; |
| 34 | + inputs[0].ToUnifiedFormat(count, v_data); |
| 35 | + inputs[1].ToUnifiedFormat(count, ts_data); |
| 36 | + state_vector.ToUnifiedFormat(count, sdata); |
| 37 | + auto vals = UnifiedVectorFormat::GetData<double>(v_data); |
| 38 | + auto timestamps = UnifiedVectorFormat::GetData<int64_t>(ts_data); |
| 39 | + auto states = (WMAState **)sdata.data; |
| 40 | + for (idx_t i = 0; i < count; i++) { |
| 41 | + auto vi = v_data.sel->get_index(i); |
| 42 | + auto ti = ts_data.sel->get_index(i); |
| 43 | + auto si = sdata.sel->get_index(i); |
| 44 | + auto &state = *states[si]; |
| 45 | + if (!state.entries) state.entries = new std::vector<WMAState::Entry>(); |
| 46 | + if (v_data.validity.RowIsValid(vi) && ts_data.validity.RowIsValid(ti)) { |
| 47 | + state.entries->push_back({vals[vi], timestamps[ti]}); |
| 48 | + } |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +static void WMACombine(Vector &src_vec, Vector &tgt_vec, AggregateInputData &, idx_t count) { |
| 53 | + UnifiedVectorFormat src_data, tgt_data; |
| 54 | + src_vec.ToUnifiedFormat(count, src_data); |
| 55 | + tgt_vec.ToUnifiedFormat(count, tgt_data); |
| 56 | + auto sources = (WMAState **)src_data.data; |
| 57 | + auto targets = (WMAState **)tgt_data.data; |
| 58 | + for (idx_t i = 0; i < count; i++) { |
| 59 | + auto &src = *sources[src_data.sel->get_index(i)]; |
| 60 | + auto &tgt = *targets[tgt_data.sel->get_index(i)]; |
| 61 | + if (src.entries) { |
| 62 | + if (!tgt.entries) tgt.entries = new std::vector<WMAState::Entry>(); |
| 63 | + tgt.entries->insert(tgt.entries->end(), src.entries->begin(), src.entries->end()); |
| 64 | + delete src.entries; src.entries = nullptr; |
| 65 | + } |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +static void WMAFinalize(Vector &state_vector, AggregateInputData &aggr, Vector &result, idx_t count, idx_t offset) { |
| 70 | + UnifiedVectorFormat sdata; |
| 71 | + state_vector.ToUnifiedFormat(count, sdata); |
| 72 | + auto states = (WMAState **)sdata.data; |
| 73 | + auto result_data = FlatVector::GetData<double>(result); |
| 74 | + auto &validity = FlatVector::Validity(result); |
| 75 | + |
| 76 | + // Get period from bind info |
| 77 | + int period = 20; |
| 78 | + if (aggr.bind_data) { |
| 79 | + // Use default period if no bind data |
| 80 | + } |
| 81 | + |
| 82 | + for (idx_t i = 0; i < count; i++) { |
| 83 | + auto &state = *states[sdata.sel->get_index(i)]; |
| 84 | + auto ridx = i + offset; |
| 85 | + if (!state.entries || state.entries->empty()) { |
| 86 | + validity.SetInvalid(ridx); |
| 87 | + continue; |
| 88 | + } |
| 89 | + auto &entries = *state.entries; |
| 90 | + std::sort(entries.begin(), entries.end(), |
| 91 | + [](const WMAState::Entry &a, const WMAState::Entry &b) { return a.ts < b.ts; }); |
| 92 | + |
| 93 | + // Use last `period` entries (or all if < period) |
| 94 | + idx_t n = entries.size(); |
| 95 | + idx_t start = (n > (idx_t)period) ? n - period : 0; |
| 96 | + double weighted_sum = 0, weight_sum = 0; |
| 97 | + int w = 1; |
| 98 | + for (idx_t j = start; j < n; j++, w++) { |
| 99 | + weighted_sum += w * entries[j].value; |
| 100 | + weight_sum += w; |
| 101 | + } |
| 102 | + result_data[ridx] = weighted_sum / weight_sum; |
| 103 | + delete state.entries; state.entries = nullptr; |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +static void WMADestructor(Vector &state_vector, AggregateInputData &, idx_t count) { |
| 108 | + UnifiedVectorFormat sdata; |
| 109 | + state_vector.ToUnifiedFormat(count, sdata); |
| 110 | + auto states = (WMAState **)sdata.data; |
| 111 | + for (idx_t i = 0; i < count; i++) { |
| 112 | + auto &state = *states[sdata.sel->get_index(i)]; |
| 113 | + if (state.entries) { delete state.entries; state.entries = nullptr; } |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +// ────────────────────────────────────────────────────────────── |
| 118 | +// pivot_point(high, low, close) — Classic Pivot Point |
| 119 | +// pivot_r1/r2/r3(high, low, close) — Resistance levels |
| 120 | +// pivot_s1/s2/s3(high, low, close) — Support levels |
| 121 | +// |
| 122 | +// These are scalar functions (computed from single candle data). |
| 123 | +// PP = (H + L + C) / 3 |
| 124 | +// R1 = 2*PP - L, S1 = 2*PP - H |
| 125 | +// R2 = PP + (H-L), S2 = PP - (H-L) |
| 126 | +// R3 = H + 2*(PP-L), S3 = L - 2*(H-PP) |
| 127 | +// ────────────────────────────────────────────────────────────── |
| 128 | + |
| 129 | +static void PivotPointFunc(DataChunk &args, ExpressionState &, Vector &result) { |
| 130 | + auto &h = args.data[0]; auto &l = args.data[1]; auto &c = args.data[2]; |
| 131 | + UnifiedVectorFormat hf, lf, cf; |
| 132 | + h.ToUnifiedFormat(args.size(), hf); |
| 133 | + l.ToUnifiedFormat(args.size(), lf); |
| 134 | + c.ToUnifiedFormat(args.size(), cf); |
| 135 | + auto hd = UnifiedVectorFormat::GetData<double>(hf); |
| 136 | + auto ld = UnifiedVectorFormat::GetData<double>(lf); |
| 137 | + auto cd = UnifiedVectorFormat::GetData<double>(cf); |
| 138 | + auto rd = FlatVector::GetData<double>(result); |
| 139 | + auto &rv = FlatVector::Validity(result); |
| 140 | + result.SetVectorType(VectorType::FLAT_VECTOR); |
| 141 | + for (idx_t i = 0; i < args.size(); i++) { |
| 142 | + auto hi = hf.sel->get_index(i); auto li = lf.sel->get_index(i); auto ci = cf.sel->get_index(i); |
| 143 | + if (!hf.validity.RowIsValid(hi) || !lf.validity.RowIsValid(li) || !cf.validity.RowIsValid(ci)) { |
| 144 | + rv.SetInvalid(i); continue; |
| 145 | + } |
| 146 | + rd[i] = (hd[hi] + ld[li] + cd[ci]) / 3.0; |
| 147 | + } |
| 148 | +} |
| 149 | + |
| 150 | +#define PIVOT_LEVEL_FUNC(name, formula) \ |
| 151 | +static void name##Func(DataChunk &args, ExpressionState &, Vector &result) { \ |
| 152 | + UnifiedVectorFormat hf, lf, cf; \ |
| 153 | + args.data[0].ToUnifiedFormat(args.size(), hf); \ |
| 154 | + args.data[1].ToUnifiedFormat(args.size(), lf); \ |
| 155 | + args.data[2].ToUnifiedFormat(args.size(), cf); \ |
| 156 | + auto hd = UnifiedVectorFormat::GetData<double>(hf); \ |
| 157 | + auto ld = UnifiedVectorFormat::GetData<double>(lf); \ |
| 158 | + auto cd = UnifiedVectorFormat::GetData<double>(cf); \ |
| 159 | + auto rd = FlatVector::GetData<double>(result); \ |
| 160 | + auto &rv = FlatVector::Validity(result); \ |
| 161 | + result.SetVectorType(VectorType::FLAT_VECTOR); \ |
| 162 | + for (idx_t i = 0; i < args.size(); i++) { \ |
| 163 | + auto hi = hf.sel->get_index(i); auto li = lf.sel->get_index(i); auto ci = cf.sel->get_index(i); \ |
| 164 | + if (!hf.validity.RowIsValid(hi) || !lf.validity.RowIsValid(li) || !cf.validity.RowIsValid(ci)) { \ |
| 165 | + rv.SetInvalid(i); continue; \ |
| 166 | + } \ |
| 167 | + double H = hd[hi], L = ld[li], C = cd[ci]; \ |
| 168 | + double PP = (H + L + C) / 3.0; \ |
| 169 | + rd[i] = formula; \ |
| 170 | + } \ |
| 171 | +} |
| 172 | + |
| 173 | +PIVOT_LEVEL_FUNC(PivotR1, 2.0 * PP - L) |
| 174 | +PIVOT_LEVEL_FUNC(PivotR2, PP + (H - L)) |
| 175 | +PIVOT_LEVEL_FUNC(PivotR3, H + 2.0 * (PP - L)) |
| 176 | +PIVOT_LEVEL_FUNC(PivotS1, 2.0 * PP - H) |
| 177 | +PIVOT_LEVEL_FUNC(PivotS2, PP - (H - L)) |
| 178 | +PIVOT_LEVEL_FUNC(PivotS3, L - 2.0 * (H - PP)) |
| 179 | + |
| 180 | +void RegisterWMAAndPivotFunctions(Connection &conn, Catalog &catalog) { |
| 181 | + // WMA aggregate |
| 182 | + AggregateFunctionSet wma_set("wma"); |
| 183 | + wma_set.AddFunction(AggregateFunction( |
| 184 | + "wma", {LogicalType::DOUBLE, LogicalType::TIMESTAMP_TZ, LogicalType::INTEGER}, |
| 185 | + LogicalType::DOUBLE, AggregateFunction::StateSize<WMAState>, |
| 186 | + WMAInitialize, WMAUpdate, WMACombine, WMAFinalize, nullptr, nullptr, WMADestructor)); |
| 187 | + wma_set.AddFunction(AggregateFunction( |
| 188 | + "wma", {LogicalType::DOUBLE, LogicalType::TIMESTAMP, LogicalType::INTEGER}, |
| 189 | + LogicalType::DOUBLE, AggregateFunction::StateSize<WMAState>, |
| 190 | + WMAInitialize, WMAUpdate, WMACombine, WMAFinalize, nullptr, nullptr, WMADestructor)); |
| 191 | + CreateAggregateFunctionInfo wma_info(wma_set); |
| 192 | + catalog.CreateFunction(*conn.context, wma_info); |
| 193 | + |
| 194 | + // Pivot Point scalars |
| 195 | + auto types3 = vector<LogicalType>{LogicalType::DOUBLE, LogicalType::DOUBLE, LogicalType::DOUBLE}; |
| 196 | + |
| 197 | + auto register_scalar = [&](const string &name, scalar_function_t func) { |
| 198 | + ScalarFunctionSet set(name); |
| 199 | + set.AddFunction(ScalarFunction(name, types3, LogicalType::DOUBLE, func)); |
| 200 | + CreateScalarFunctionInfo info(set); |
| 201 | + catalog.CreateFunction(*conn.context, info); |
| 202 | + }; |
| 203 | + |
| 204 | + register_scalar("pivot_point", PivotPointFunc); |
| 205 | + register_scalar("pivot_r1", PivotR1Func); |
| 206 | + register_scalar("pivot_r2", PivotR2Func); |
| 207 | + register_scalar("pivot_r3", PivotR3Func); |
| 208 | + register_scalar("pivot_s1", PivotS1Func); |
| 209 | + register_scalar("pivot_s2", PivotS2Func); |
| 210 | + register_scalar("pivot_s3", PivotS3Func); |
| 211 | +} |
| 212 | + |
| 213 | +} // namespace scrooge |
| 214 | +} // namespace duckdb |
0 commit comments