PineForge v0.13.1-379-g9b50973
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
native_toolkit.hpp
Go to the documentation of this file.
1#pragma once
2
3// Header-only conveniences over the public native request surface. Nothing
4// here is kernel behaviour: every function composes submit / replace / cancel
5// and the owner, group and anchor values a host could write out by hand, so a
6// host that prefers the primitives loses nothing by ignoring this header. No
7// Pine, no source layer, no engine internals.
8
11
12#include <cstddef>
13#include <cstdint>
14#include <map>
15#include <optional>
16#include <utility>
17
19inline namespace native_toolkit_v1 {
20
21// One filled entry and the exit legs that live off it. Each present leg is
22// submitted as the parent's `WaitForApplied` child in a single OCA group, so
23// a leg cannot rest before the entry has a fill and a completed leg ends its
24// siblings. The legs carry the caller's own intent, trigger and anchor; the
25// builder owns only the owner and group fields, plus the two anchored-leg
26// knobs below, both defaulting to "leave the leg as written".
27//
28// Legs are submitted in take_profit, stop_loss, trail order. The group id is
29// the parent's incarnation: every leg of one entry shares one group, and two
30// brackets built on the same parent deliberately join that same group.
31//
32// anchor_rounding, when set, is written onto every present leg whose anchor
33// is FromOwnerFill (a leg with an absolute level is untouched); unset keeps
34// each leg's own rounding. visibility is written into the owner relation the
35// builder creates for every leg: PendingUntilArmed keeps the legs out of
36// native_working_requests() until the parent's fill arms them.
39 std::optional<native_order::Request> take_profit;
40 std::optional<native_order::Request> stop_loss;
41 std::optional<native_order::Request> trail;
42 native_order::GroupEffect sibling_effect = native_order::GroupEffect::Cancel;
43 std::optional<native_order::NativeAnchorRounding> anchor_rounding;
44 native_order::NativeArmVisibility visibility = native_order::NativeArmVisibility::Working;
45};
46
47// The handles the legs were accepted under. A leg that was absent or that the
48// host rejected stays empty; the remaining legs are still submitted.
51 std::optional<native_order::RequestHandle> take_profit;
52 std::optional<native_order::RequestHandle> stop_loss;
53 std::optional<native_order::RequestHandle> trail;
54};
55
56// Sibling cohorts, fixed so a hand-written twin can reproduce the shape.
57enum class BracketLeg : std::int64_t {
60 Trail = 3,
61};
62
63// The owner/group pair the builder writes onto every leg, plus the two
64// anchored-leg knobs: the visibility goes into the owner relation, the
65// rounding (when given) onto a FromOwnerFill anchor only.
67 const native_order::Request& leg,
68 const native_order::RequestHandle& parent,
69 BracketLeg which,
70 native_order::GroupEffect sibling_effect,
71 native_order::NativeArmVisibility visibility = native_order::NativeArmVisibility::Working,
72 std::optional<native_order::NativeAnchorRounding> anchor_rounding = std::nullopt) {
73 native_order::Request out = leg;
74 out.owner = native_order::WaitForApplied{parent, visibility};
75 out.group = native_order::Member{parent.incarnation, static_cast<std::int64_t>(which),
76 sibling_effect};
77 if (anchor_rounding) {
78 if (auto* anchor = std::get_if<native_order::FromOwnerFill>(&out.anchor)) {
79 anchor->rounding = *anchor_rounding;
80 }
81 }
82 return out;
83}
84
85// Submits the present legs. A parent handle that was never allocated has no
86// fill to wait for, so nothing is submitted for it.
88 BracketReceipt receipt;
89 receipt.parent = spec.parent;
90 if (spec.parent.incarnation == 0) return receipt;
91 const auto place = [&](const std::optional<native_order::Request>& leg, BracketLeg which)
92 -> std::optional<native_order::RequestHandle> {
93 if (!leg) return std::nullopt;
94 const auto result = host.submit(bracket_leg(*leg, spec.parent, which, spec.sibling_effect,
95 spec.visibility, spec.anchor_rounding));
96 if (result.status != native_order::SubmitStatus::Accepted || !result.handle) {
97 return std::nullopt;
98 }
99 return *result.handle;
100 };
101 receipt.take_profit = place(spec.take_profit, BracketLeg::TakeProfit);
102 receipt.stop_loss = place(spec.stop_loss, BracketLeg::StopLoss);
103 receipt.trail = place(spec.trail, BracketLeg::Trail);
104 return receipt;
105}
106
107// A host-side key to live-handle index: the bookkeeping a strategy would
108// otherwise write to re-price "its" order by name. It owns no engine state,
109// only the handles the host returned. Key must be ordered (a std::string id
110// or an integral id are the usual choices).
111//
112// This book is for a host that wants to REACH one named order again --
113// replace it, re-price it, cancel it, ask whether it is still live. It is not
114// the only way to address an order by name: a host that only ever withdraws
115// them in bulk needs no book at all, because
116// NativeStrategyHost::cancel_where(text, NativeRequestField::Label) cancels
117// exactly the live requests whose Request::label is that text (and
118// NativeRequestField::Comment does the same for the comment) in one call,
119// walking the live book once. The trade is the usual one: the predicate
120// keeps nothing in step and reads the truth the kernel already holds; this
121// book is O(log n) to a handle and survives the request's own terminal
122// states only as the key it forgets.
123template <class Key>
125public:
126 explicit OrderBook(NativeStrategyHost& host) noexcept : host_(&host) {}
127
128 std::optional<native_order::RequestHandle> handle(const Key& key) const {
129 const auto found = entries_.find(key);
130 if (found == entries_.end()) return std::nullopt;
131 return found->second.handle;
132 }
133 bool contains(const Key& key) const { return entries_.find(key) != entries_.end(); }
134 std::size_t size() const noexcept { return entries_.size(); }
135 bool empty() const noexcept { return entries_.empty(); }
136
137 // Replaces the key's request while it is still working, otherwise submits
138 // a fresh one under that key. A replacement the host rejects leaves the
139 // previous request working and still bound to the key, and reports
140 // nothing; a rejected submit leaves the key unbound.
141 //
142 // A key bound to a PendingUntilArmed child is not in the working
143 // enumeration before its arm, so the book does not ask the enumeration
144 // whether it is working: it replaces first and treats NotWorking as
145 // "gone, submit afresh". Every key bound to a Working request keeps the
146 // enumeration check, so its command footprint is unchanged.
147 std::optional<native_order::RequestHandle> submit_or_replace(
148 const Key& key, const native_order::Request& request,
149 native_order::ReplaceOptions options = {}) {
150 const auto found = entries_.find(key);
151 if (found != entries_.end()) {
152 if (found->second.pending_until_armed || working(found->second.handle)) {
153 const auto result = host_->replace(found->second.handle, request, options);
154 if (result.status == native_order::ReplaceStatus::Replaced && result.successor) {
155 found->second = Entry{*result.successor, pending_until_armed(request)};
156 return found->second.handle;
157 }
158 if (result.status == native_order::ReplaceStatus::ReplaceRejected) {
159 return std::nullopt;
160 }
161 }
162 entries_.erase(found);
163 }
164 const auto submitted = host_->submit(request);
165 if (submitted.status != native_order::SubmitStatus::Accepted || !submitted.handle) {
166 return std::nullopt;
167 }
168 entries_.emplace(key, Entry{*submitted.handle, pending_until_armed(request)});
169 return *submitted.handle;
170 }
171
172 // Cancels the key's request and forgets the key. An unknown key is not a
173 // command: nothing reaches the host. A key bound to a PendingUntilArmed
174 // child is cancelled by handle without consulting the enumeration.
176 const auto found = entries_.find(key);
177 if (found == entries_.end()) return native_order::CancelStatus::NotWorking;
178 const auto target = found->second;
179 entries_.erase(found);
180 if (!target.pending_until_armed && !working(target.handle)) {
181 return native_order::CancelStatus::NotWorking;
182 }
183 return host_->cancel(target.handle).status;
184 }
185
186 // Drops the key without commanding the host.
187 void forget(const Key& key) { entries_.erase(key); }
188
189private:
190 struct Entry {
192 bool pending_until_armed = false;
193 };
194
195 static bool pending_until_armed(const native_order::Request& request) noexcept {
196 const auto* wait = std::get_if<native_order::WaitForApplied>(&request.owner);
197 return wait != nullptr
198 && wait->visibility == native_order::NativeArmVisibility::PendingUntilArmed;
199 }
200
201 bool working(const native_order::RequestHandle& target) const {
202 for (const auto& row : host_->native_working_requests()) {
203 if (row.definition && row.definition->handle == target) return true;
204 }
205 return false;
206 }
207
208 NativeStrategyHost* host_ = nullptr;
209 std::map<Key, Entry> entries_;
210};
211
212} // inline namespace native_toolkit_v1
213} // namespace pineforge::native_toolkit
The public native host: an abstract subclass of BacktestEngine with no PineScript on it.
std::vector< NativeWorkingRequest > native_working_requests() const
Working-book snapshot and bulk cancellation.
native_order::SubmitResult submit(const native_order::Request &request)
Accept one complete request.
native_order::ReplaceResult replace(const native_order::RequestHandle &target, const native_order::Request &request)
Replace one live request: validate first, then retire that incarnation and birth a successor with a n...
native_order::CancelStatus cancel(const Key &key)
std::optional< native_order::RequestHandle > submit_or_replace(const Key &key, const native_order::Request &request, native_order::ReplaceOptions options={})
std::optional< native_order::RequestHandle > handle(const Key &key) const
NativeArmVisibility
Whether an owner-related request that arms is a working order before its arm.
BracketReceipt submit_bracket(NativeStrategyHost &host, const BracketSpec &spec)
native_order::Request bracket_leg(const native_order::Request &leg, const native_order::RequestHandle &parent, BracketLeg which, native_order::GroupEffect sibling_effect, native_order::NativeArmVisibility visibility=native_order::NativeArmVisibility::Working, std::optional< native_order::NativeAnchorRounding > anchor_rounding=std::nullopt)
Replacement behaviour that is not expressible in the successor request.
Aggregate field order keeps market construction: Request{Transact{1.0}, "buy", "comment"}...
The one owner relation that arms (the ArmedEvent).
std::optional< native_order::RequestHandle > trail
std::optional< native_order::RequestHandle > stop_loss
std::optional< native_order::RequestHandle > take_profit
std::optional< native_order::Request > take_profit
std::optional< native_order::NativeAnchorRounding > anchor_rounding