PineForge v0.13.1-379-g9b50973
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
native_price_grid_c.c
Go to the documentation of this file.
1/* The instrument price grid — from C.
2 *
3 * The same strategy, the same six bars and the same four broker models as
4 * native_price_grid_strategy.cpp, written against the C API: the grid is
5 * declared through the run-spec extension (PF_NATIVE_SPEC_EXT_PRICE_GRID,
6 * `price_grid` / `grid_rounding`), and every fill is read back from the event
7 * history with both of its prices — the raw modeled one and the one the run
8 * booked. The expected numbers are the C++ example's, hand-computed from the
9 * rounding rules; a C host and a C++ host get the same grid.
10 *
11 * cc -std=c11 native_price_grid_c.c -lpineforge_kernel -lstdc++ -o price_grid_c
12 */
13
14#include <pineforge/pineforge.h>
15
16#include <math.h>
17#include <stdio.h>
18#include <string.h>
19
20#define QUARTER_MS (15LL * 60LL * 1000LL)
21
22/* open, high, low, close, volume, timestamp (Unix milliseconds). The
23 * instrument trades on a 0.25 ladder; every print of this feed is sub-tick. */
24static const pf_bar_t kBars[] = {
25 {100.00, 100.20, 99.90, 100.10, 10.0, 0 * QUARTER_MS},
26 {100.10, 100.30, 100.05, 100.20, 10.0, 1 * QUARTER_MS},
27 {100.20, 100.80, 100.15, 100.70, 10.0, 2 * QUARTER_MS},
28 { 99.40, 99.45, 99.20, 99.30, 10.0, 3 * QUARTER_MS},
29 { 99.45, 99.65, 99.35, 99.60, 10.0, 4 * QUARTER_MS},
30 { 99.60, 99.62, 99.30, 99.40, 10.0, 5 * QUARTER_MS}
31};
32enum { kBarCount = 6, kMaxFills = 5, kMaxEvents = 128 };
33
34struct host_state {
35 pf_strategy_t handle;
36 int bars;
37 int refused;
38};
39
40static void submit(struct host_state* state, uint32_t intent, double value,
41 uint32_t trigger, double price, const char* label) {
43 memset(&request, 0, sizeof(request));
44 request.struct_size = (uint32_t)sizeof(request);
46 request.intent = intent;
48 request.intent_value = value;
49 request.trigger = trigger;
50 request.p1 = price;
51 request.label = label;
52 request.comment = "grid";
53 if (strategy_native_submit_v1(state->handle, &request, NULL, NULL) != PF_NATIVE_OK) {
54 ++state->refused;
55 }
56}
57
58static int on_bar(void* user, const pf_bar_t* bar, const pf_native_decision_v1* at) {
59 struct host_state* state = (struct host_state*)user;
60 double units = 0.0;
61 (void)bar;
62 (void)at;
63
64 ++state->bars;
65 if (state->bars == 1) {
67 } else if (state->bars == 2) {
68 /* Both levels are ladder prices. */
69 submit(state, PF_NATIVE_INTENT_REDUCE, 1.0, PF_NATIVE_TRIGGER_LIMIT, 100.75, "target");
70 submit(state, PF_NATIVE_INTENT_REDUCE, 1.0, PF_NATIVE_TRIGGER_STOP, 99.50, "protect");
71 } else if (state->bars == 4) {
72 submit(state, PF_NATIVE_INTENT_TRANSACT, 1.0, PF_NATIVE_TRIGGER_STOP, 99.75, "breakout");
73 } else if (state->bars == 5) {
74 if (strategy_native_position_v1(state->handle, &units, NULL, NULL) == PF_NATIVE_OK
75 && units > 0.0) {
77 "breakout-exit");
78 }
79 }
80 return 0;
81}
82
83static pf_native_run_spec_v1 make_spec(const char* key, double tick) {
85 memset(&spec, 0, sizeof(spec));
86 spec.struct_size = (uint32_t)sizeof(spec);
87 spec.session_key = key;
88 spec.run_number = 1;
89 spec.input_tf = "15";
90 spec.script_tf = "15";
91 spec.ticker = "MOCK";
92 spec.tickerid = "TEST:MOCK";
93 spec.type = "futures";
94 spec.currency = "USD";
95 spec.basecurrency = "";
96 spec.description = "";
97 spec.volumetype = "";
98 spec.timezone = "UTC";
99 spec.session = "24x7";
100 spec.chart_timezone = "";
101 spec.initial_capital = 10000.0;
102 spec.point_value = 1.0;
103 spec.account_fx = 1.0;
104 spec.price_tick = tick;
105 spec.fee_kind = 0; /* Percent */
106 spec.fee_value = 0.0;
107 spec.close_execution = 0; /* NextEligiblePoint */
108 spec.allowed_open_directions = 3; /* Both */
109 return spec;
110}
111
112/* price_grid: 0 None, 1 QuantizeFills, 2 QuantizeFillsAndTriggers.
113 * grid_rounding: 0 HalfUp, 1 Directional. */
114static pf_native_run_spec_ext_v1 make_ext(uint32_t grid, uint32_t rounding) {
116 memset(&ext, 0, sizeof(ext));
117 ext.struct_size = (uint32_t)sizeof(ext);
120 ext.price_grid = grid;
121 ext.grid_rounding = rounding;
122 return ext;
123}
124
125struct mode {
126 const char* name;
127 uint32_t grid;
128 uint32_t rounding;
129 int fills;
130 double raw[kMaxFills]; /* the modeled price of the path, never rounded */
131 double booked[kMaxFills]; /* hand-computed from the rounding rule */
132 int trades;
133};
134
135/* entry: the market fill at bar 1's open 100.10 (nearest tick 100.00, adverse
136 * buy tick 100.25); target: the limit at the ladder price 100.75, a fixed
137 * point of every mode; protect: the stop at 99.50 gapped through by the open
138 * 99.40 (nearest 99.50, adverse sell tick 99.25); breakout: the stop at 99.75
139 * that only the quantized path reaches (raw high 99.65, nearest tick 99.75),
140 * and its market exit at the open 99.60 (nearest 99.50). */
141static const struct mode kModes[] = {
142 {"None", 0u, 0u, 3,
143 {100.10, 100.75, 99.40}, {100.10, 100.75, 99.40}, 2},
144 {"QuantizeFills/HalfUp", 1u, 0u, 3,
145 {100.10, 100.75, 99.40}, {100.00, 100.75, 99.50}, 2},
146 {"QuantizeFills/Directional", 1u, 1u, 3,
147 {100.10, 100.75, 99.40}, {100.25, 100.75, 99.25}, 2},
148 {"QuantizeFillsAndTriggers/HalfUp", 2u, 0u, 5,
149 {100.10, 100.75, 99.40, 99.75, 99.60}, {100.00, 100.75, 99.50, 99.75, 99.50}, 3}
150};
151enum { kModeCount = 4 };
152
153/* Returns the closed-trade count, or -1 when a number is not the expected one. */
154static int run_mode(const struct mode* mode) {
155 static pf_native_event_v1 events[kMaxEvents];
156 struct host_state state;
157 pf_native_callbacks_v1 callbacks;
158 pf_native_run_spec_v1 spec = make_spec(mode->name, 0.25);
159 pf_native_run_spec_ext_v1 ext = make_ext(mode->grid, mode->rounding);
160 pf_report_t report;
161 int written;
162 int fills = 0;
163 int trades;
164 int ok = 1;
165 int i;
166
167 memset(&state, 0, sizeof(state));
168 memset(&report, 0, sizeof(report));
169 memset(&callbacks, 0, sizeof(callbacks));
170 callbacks.struct_size = (uint32_t)sizeof(callbacks);
171 callbacks.version = PF_NATIVE_API_VERSION;
172 callbacks.user = &state;
173 callbacks.on_bar = on_bar;
174
175 state.handle = strategy_native_host_create_v1(&callbacks);
176 if (!state.handle) {
177 fprintf(stderr, "%s create: the runtime refused the callback table\n", mode->name);
178 return -1;
179 }
180 if (strategy_configure_native_ext_v1(state.handle, &spec, &ext) != PF_NATIVE_OK) {
181 fprintf(stderr, "%s configure: %s\n", mode->name, strategy_get_last_error(state.handle));
182 strategy_native_host_free(state.handle);
183 return -1;
184 }
185 if (strategy_native_run_v1(state.handle, kBars, kBarCount, &report) != PF_NATIVE_OK) {
186 fprintf(stderr, "%s run: %s\n", mode->name, strategy_get_last_error(state.handle));
188 strategy_native_host_free(state.handle);
189 return -1;
190 }
191
192 printf("%-34s", mode->name);
193 memset(events, 0, sizeof(events));
194 written = strategy_native_events_v1(state.handle, 0, events, kMaxEvents);
195 for (i = 0; i < written; ++i) {
196 if (events[i].kind != PF_NATIVE_EVENT_APPLIED) continue;
197 printf(" %.2f->%.2f", events[i].raw_price, events[i].resolved_price);
198 if (fills < mode->fills
199 && (events[i].raw_price != mode->raw[fills]
200 || events[i].resolved_price != mode->booked[fills])) {
201 ok = 0;
202 }
203 /* Under a grid every booked price is a ladder price. */
204 if (mode->grid != 0u
205 && events[i].resolved_price != round(events[i].resolved_price / 0.25) * 0.25) {
206 ok = 0;
207 }
208 ++fills;
209 }
210 trades = report.total_trades;
211 printf(" | trades=%d\n", trades);
212 if (fills != mode->fills || trades != mode->trades || state.refused != 0) ok = 0;
213
215 strategy_native_host_free(state.handle);
216 if (!ok) {
217 fprintf(stderr, "%s: the fills are not the hand-computed ones\n", mode->name);
218 return -1;
219 }
220 return trades;
221}
222
223/* A quantizing grid needs a ladder: the same extension over price_tick = 0 is
224 * refused at configure time, and the handle stays unconfigured. */
226 struct host_state state;
227 pf_native_callbacks_v1 callbacks;
228 pf_native_run_spec_v1 spec = make_spec("no-ladder", 0.0);
230 int rc;
231
232 memset(&state, 0, sizeof(state));
233 memset(&callbacks, 0, sizeof(callbacks));
234 callbacks.struct_size = (uint32_t)sizeof(callbacks);
235 callbacks.version = PF_NATIVE_API_VERSION;
236 callbacks.user = &state;
237 callbacks.on_bar = on_bar;
238 state.handle = strategy_native_host_create_v1(&callbacks);
239 if (!state.handle) return 0;
240 rc = strategy_configure_native_ext_v1(state.handle, &spec, &ext);
241 printf("QuantizeFills with price_tick = 0: %s\n",
242 rc != PF_NATIVE_OK ? "refused at configure" : "NOT refused");
243 strategy_native_host_free(state.handle);
244 return rc != PF_NATIVE_OK;
245}
246
247int main(void) {
248 int closed = 0;
249 int i;
250
251 for (i = 0; i < kModeCount; ++i) {
252 const int trades = run_mode(&kModes[i]);
253 if (trades < 0) return 1;
254 closed += trades;
255 }
256 if (!grid_without_a_ladder_is_refused()) return 1;
257
258 printf("closed trades: %d\n", closed);
259 return closed > 0 ? 0 : 1;
260}
const char * strategy_get_last_error(pf_strategy_t s)
Returns the error message captured by the most recent run_backtest / run_backtest_full call on this s...
int strategy_native_run_v1(pf_strategy_t s, const pf_bar_t *bars, int n, pf_report_t *out)
Run n bars as one batch and fill out.
void strategy_native_host_free(pf_strategy_t s)
Release a handle from strategy_native_host_create_v1.
int strategy_native_position_v1(pf_strategy_t s, double *signed_units, double *average_price, uint64_t *lots)
Read the physical position.
int strategy_configure_native_ext_v1(pf_strategy_t s, const pf_native_run_spec_v1 *base, const pf_native_run_spec_ext_v1 *ext)
Configure a native run from the v1 specification plus the extension.
int strategy_native_events_v1(pf_strategy_t s, uint64_t after_ordinal, pf_native_event_v1 *out, int cap)
Copy up to cap events with an ordinal strictly greater than after_ordinal into out,...
pf_strategy_t strategy_native_host_create_v1(const pf_native_callbacks_v1 *callbacks)
Allocate a native host that forwards every kernel callback to callbacks.
int strategy_native_submit_v1(pf_strategy_t s, const pf_native_request_v1 *request, uint64_t *incarnation, uint32_t *reject)
Submit request.
void strategy_native_report_free_v1(pf_report_t *report)
Free the heap arrays inside a report filled by strategy_native_run_v1.
@ PF_NATIVE_INTENT_REDUCE
Reduce; see pf_native_reduction_t.
@ PF_NATIVE_INTENT_TRANSACT
intent_value signed units.
@ PF_NATIVE_INTENT_FLATTEN
Close the whole book.
@ PF_NATIVE_SPEC_EXT_PRICE_GRID
@ PF_NATIVE_TRIGGER_LIMIT
p1 = price; fill_through makes it market-if-touched.
@ PF_NATIVE_TRIGGER_STOP
p1 = price.
@ PF_NATIVE_TRIGGER_MARKET
p1, p2 ignored.
@ PF_NATIVE_REDUCE_EXPLICIT_UNITS
intent_value units.
@ PF_NATIVE_EVENT_APPLIED
#define PF_NATIVE_OK
Success.
static const pf_bar_t kBars[]
static pf_native_run_spec_v1 make_spec(void)
static int on_bar(void *user, const pf_bar_t *bar, const pf_native_decision_v1 *at)
@ kBarCount
#define PF_NATIVE_API_VERSION
Monotonic version of this header's native-C layouts.
static void submit(struct host_state *state, uint32_t intent, double value, uint32_t trigger, double price, const char *label)
@ kMaxFills
@ kMaxEvents
static pf_native_run_spec_ext_v1 make_ext(uint32_t grid, uint32_t rounding)
static int on_bar(void *user, const pf_bar_t *bar, const pf_native_decision_v1 *at)
static const struct mode kModes[]
static int run_mode(const struct mode *mode)
#define QUARTER_MS
int main(void)
static int grid_without_a_ladder_is_refused(void)
void * pf_strategy_t
Opaque handle to a compiled strategy instance.
Definition pineforge.h:433
Single OHLCV bar pushed into the engine.
Definition pineforge.h:127
The C host's strategy logic.
Where the kernel is, presented to every callback.
One recorded event, read back by strategy_native_events_v1.
One order request.
double p1
Limit/stop price, or trail offset.
uint32_t struct_size
sizeof(pf_native_request_v1).
uint32_t intent
pf_native_intent_t.
uint32_t version
PF_NATIVE_API_VERSION.
double intent_value
The intent's own scalar; see pf_native_intent_t.
uint32_t trigger
pf_native_trigger_t.
uint32_t reduce_size
pf_native_reduction_t, REDUCE only.
The run-specification fields pf_native_run_spec_v1 predates.
uint32_t price_grid
NativePriceGrid.
uint32_t present_mask
pf_native_spec_ext_mask_t bits.
uint32_t struct_size
sizeof(pf_native_run_spec_ext_v1).
uint32_t version
PF_NATIVE_API_VERSION.
uint32_t grid_rounding
NativeGridRounding.
Versioned native run specification.
Definition pineforge.h:469
const char * tickerid
Definition pineforge.h:473
uint32_t allowed_open_directions
Definition pineforge.h:477
const char * volumetype
Definition pineforge.h:473
const char * basecurrency
Definition pineforge.h:473
const char * input_tf
Definition pineforge.h:472
const char * session_key
Definition pineforge.h:471
const char * type
Definition pineforge.h:473
const char * script_tf
Definition pineforge.h:472
const char * chart_timezone
Definition pineforge.h:474
const char * timezone
Definition pineforge.h:474
const char * ticker
Definition pineforge.h:473
const char * session
Definition pineforge.h:474
const char * description
Definition pineforge.h:473
const char * currency
Definition pineforge.h:473
Backtest report filled by run_backtest / run_backtest_full.
Definition pineforge.h:365
int total_trades
Closed-trade count (== trades_len), including the range-end close of a position still open after the ...
Definition pineforge.h:367