PineForge v0.13.1-379-g9b50973
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
source/market_admission.cpp
Go to the documentation of this file.
2#include <algorithm>
3#include <exception>
4#include <stdexcept>
5#include <type_traits>
6
7namespace pineforge::admission {
8inline namespace market_admission_v2 {
9namespace {
10bool same(const ReviewReceipt& a, const ReviewReceipt& b) {
11 return a.sequence == b.sequence && a.checkpoint == b.checkpoint &&
12 a.bar == b.bar && a.target_command == b.target_command;
13}
14bool same(const SizingRevision& a, const SizingRevision& b) {
15 return a.sequence == b.sequence && a.cause_fill == b.cause_fill &&
16 a.bar == b.bar && a.target_command == b.target_command;
17}
18bool known_checkpoint(Checkpoint checkpoint) {
19 return checkpoint == Checkpoint::DefaultGross ||
20 checkpoint == Checkpoint::ExplicitPair || checkpoint == Checkpoint::TerminalGross;
21}
22void require_origin(const std::shared_ptr<const CommandObservation>& origin,
23 uint64_t target, uint64_t event, int bar) {
24 if (!origin || target != origin->command || event <= origin->command ||
25 bar < origin->bar)
26 throw std::invalid_argument("admission receipt requires its exact earlier command");
27}
28}
29void Draft::bind(std::shared_ptr<const CommandObservation> observation) {
30 if(observation_ || !observation || observation->command==0)
31 throw std::logic_error("market draft requires one original command observation");
32 observation_=std::move(observation);
33}
35 require_origin(observation_, receipt.target_command, receipt.sequence, receipt.bar);
36 if (!known_checkpoint(receipt.checkpoint))
37 throw std::invalid_argument("review requires a known checkpoint");
38 if (review_) {
39 if (same(*review_, receipt)) return;
40 throw std::logic_error("first admission review cannot be replaced");
41 }
42 review_ = receipt;
43}
45 require_origin(observation_, receipt.target_command, receipt.sequence, receipt.bar);
46 if (receipt.cause_fill == 0)
47 throw std::invalid_argument("sizing revision requires committed cause");
48 if (sizing_revision_) {
49 if (same(*sizing_revision_, receipt)) return;
50 if (receipt.sequence <= sizing_revision_->sequence ||
51 receipt.bar < sizing_revision_->bar || receipt.cause_fill < sizing_revision_->cause_fill)
52 throw std::logic_error("sizing revision cannot rewrite or precede its latest cause");
53 }
54 sizing_revision_ = receipt;
55}
56uint64_t sequence(const Event& event) {
57 return std::visit([](const auto& value)->uint64_t {
58 using T=std::decay_t<decltype(value)>;
59 if constexpr(std::is_same_v<T,CommandEvent>) {
60 if(!value.observation)throw std::invalid_argument("command event requires an observation");
61 return value.observation->command;
62 }
63 else return value.receipt.sequence;
64 },event);
65}
67 if (other.active_allocations_ != 0)
68 throw std::logic_error("cannot copy admission journal with active captures");
69 next_sequence_ = other.next_sequence_;
70 outstanding_sequences_ = other.outstanding_sequences_;
71 events_ = other.events_;
72}
74 if (this == &other) return *this;
75 if (active_allocations_ != 0)
76 throw std::logic_error("cannot replace admission journal with active captures");
77 Journal copy(other);
78 std::swap(next_sequence_, copy.next_sequence_);
79 outstanding_sequences_.swap(copy.outstanding_sequences_);
80 events_.swap(copy.events_);
81 return *this;
82}
84 if (other.active_allocations_ != 0)
85 throw std::logic_error("cannot move admission journal with active captures");
86 next_sequence_ = other.next_sequence_;
87 outstanding_sequences_ = std::move(other.outstanding_sequences_);
88 events_ = std::move(other.events_);
89 other.outstanding_sequences_.clear();
90 other.events_.clear();
91 other.next_sequence_ = 1;
92}
94 if (this == &other) return *this;
95 if (active_allocations_ != 0)
96 throw std::logic_error("cannot replace admission journal with active captures");
97 Journal moved(std::move(other));
98 std::swap(next_sequence_, moved.next_sequence_);
99 outstanding_sequences_.swap(moved.outstanding_sequences_);
100 events_.swap(moved.events_);
101 return *this;
102}
104 if(next_sequence_==std::numeric_limits<uint64_t>::max())throw std::overflow_error("admission event sequence exhausted");
105 outstanding_sequences_.push_back(next_sequence_);
106 return next_sequence_++;
107}
109 const auto id = next_sequence();
110 ++active_allocations_;
111 return Allocation(*this, id);
112}
113void Journal::abandon(uint64_t id) noexcept {
114 const auto at = std::find(outstanding_sequences_.begin(), outstanding_sequences_.end(), id);
115 if (at != outstanding_sequences_.end()) outstanding_sequences_.erase(at);
116}
118 : journal_(other.journal_), sequence_(other.sequence_) {
119 other.journal_ = nullptr;
120 other.sequence_ = 0;
121}
122void Journal::release_allocation(uint64_t sequence) noexcept {
123 abandon(sequence);
124 --active_allocations_;
125}
126Allocation::~Allocation() noexcept { if (journal_) journal_->release_allocation(sequence_); }
128 const auto id=sequence(event);
129 const auto pending = std::find(outstanding_sequences_.begin(), outstanding_sequences_.end(), id);
130 if (pending == outstanding_sequences_.end())
131 throw std::logic_error("admission event has no outstanding allocation");
132 auto at=std::lower_bound(events_.begin(),events_.end(),id,[](const auto& e,uint64_t n){return sequence(e)<n;});
133 events_.insert(at,std::move(event));
134 // Consume only after insertion succeeds. Allocation failure leaves a caller
135 // free to retry this still-outstanding event; retain never restores an ID.
136 outstanding_sequences_.erase(pending);
137}
138void Journal::retain(const std::vector<uint64_t>& retained) {
139 events_.erase(std::remove_if(events_.begin(),events_.end(),[&](const auto& e){
140 return std::find(retained.begin(),retained.end(),sequence(e))==retained.end();
141 }),events_.end());
142}
144 if (!outstanding_sequences_.empty() || active_allocations_ != 0)
145 throw std::logic_error("cannot reset admission journal with unfinished events");
146 events_.clear();next_sequence_=1;
147}
148CommandCapture::CommandCapture(Allocation allocation,CommandObservation input,std::vector<BookObservation> before,
149 std::function<void(CommandEvent)> complete):allocation_(std::move(allocation)),input_(std::move(input)),before_(std::move(before)),complete_(std::move(complete)) {
150 if (input_.command != allocation_.sequence())
151 throw std::invalid_argument("command capture requires its allocated event");
152}
154 // Never run a potentially throwing completion while another exception is
155 // unwinding. The Allocation member abandons this unfinished event instead.
156 if (std::uncaught_exceptions() != 0) return;
157 complete_({std::make_shared<const CommandObservation>(input_),outcome_,0,std::move(before_),{}});
158}
159void CommandCapture::bind(Draft& draft,std::optional<SizingObservation> sizing,double equity,double price) {
160 input_.original_sizing=std::move(sizing);input_.explicit_equity=equity;input_.explicit_price=price;
161 draft.bind(std::make_shared<const CommandObservation>(input_));
163}
165 std::function<void(ReviewEvent)> complete)
166 : allocation_(std::move(allocation)),event_(std::move(event)),complete_(std::move(complete)) {
167 if (event_.receipt.sequence != allocation_.sequence() || event_.receipt.target_command != 0 ||
168 !known_checkpoint(event_.receipt.checkpoint))
169 throw std::invalid_argument("review capture requires an allocated batch checkpoint");
170}
172 if (std::uncaught_exceptions() != 0) return;
173 complete_(std::move(event_));
174}
176 const auto& origin = draft.observation();
177 if (!origin || std::none_of(event_.reviewed.begin(), event_.reviewed.end(), [&](const auto& order) {
178 return order.draft.observation() && order.draft.observation()->command == origin->command;
179 }))
180 throw std::invalid_argument("draft does not belong to this admission review");
181 auto receipt = event_.receipt;
182 receipt.target_command = origin->command;
183 require_origin(origin, receipt.target_command, receipt.sequence, receipt.bar);
184 return receipt;
185}
186
187namespace {
188struct Reflect {
189 const FieldVisitor& visit;
190 template<class T>void field(const std::string& path,const char* name,const T& value) const {
191 if constexpr(std::is_same_v<T,bool>)visit({path+"."+name,uint64_t(value?1:0)});
192 else if constexpr(std::is_enum_v<T>||std::is_same_v<T,int>)visit({path+"."+name,int64_t(value)});
193 else visit({path+"."+name,value});
194 }
195 void birth(const OrderBirth& o,const std::string& p)const {
196 field(p,"cause",o.cause());field(p,"bar",o.bar());field(p,"timestamp",o.timestamp());
197 field(p,"cursor_domain",o.cursor().domain());field(p,"cursor_position",o.cursor().position());
198 field(p,"cursor_index",o.cursor().index());field(p,"cursor_count",o.cursor().count());
199 field(p,"cursor_price",o.cursor_price());field(p,"first_fill",o.first_fill());
200 field(p,"last_fill",o.last_fill());field(p,"evaluation_ordinal",o.evaluation_ordinal());
201 }
202 void config(const Configuration& o,const std::string& p)const {
203#define F(name) field(p,#name,o.name)
204 F(process_on_close);F(calc_on_fills);F(magnifier);F(fill_recalculation);F(scheduler);
205 F(slippage);F(pyramiding);F(default_quantity_type);F(default_quantity_value);
206 F(long_margin);F(short_margin);F(commission_value);F(commission_type);F(pointvalue);F(fx);F(quantity_step);F(mintick);F(risk_direction);F(loss_days_limit);
207 F(drawdown_limit);F(intraday_loss_limit);F(position_limit);F(fill_cap_active);F(risk_halted);
208#undef F
209 }
210 void sizing(const SizingObservation& o,const std::string& p)const {
211#define F(name) field(p,#name,o.name)
212 F(quantity);F(equity);F(price);F(mark);F(fx);
213#undef F
214 }
215 void command(const CommandObservation& o,const std::string& p)const {
216#define F(name) field(p,#name,o.name)
217 F(command);F(kind);birth(o.birth,p+".birth");F(id);F(requested_quantity);F(quantity_type);F(buy);
218 field(p+".prices","limit",o.prices.limit);field(p+".prices","stop",o.prices.stop);
219 F(oca_name);F(oca_type);config(o.configuration,p+".configuration");F(bar);F(placement_side);F(placement_cycle);
220 F(prior_close_quantity);F(held_quantity);F(held_entries);F(realized_equity);F(placement_equity);F(signal_close);F(quantized_fixed_quantity);
221 field(p,"original_sizing_present",o.original_sizing.has_value());
222 if(o.original_sizing)sizing(*o.original_sizing,p+".original_sizing");
223 F(explicit_equity);F(explicit_price);
224#undef F
225 }
226 void review(const ReviewReceipt& o,const std::string& p)const {
227 field(p,"sequence",o.sequence);field(p,"checkpoint",o.checkpoint);field(p,"bar",o.bar);
228 field(p,"target_command",o.target_command);
229 }
230 void revision(const SizingRevision& o,const std::string& p)const {
231 field(p,"sequence",o.sequence);field(p,"cause_fill",o.cause_fill);field(p,"bar",o.bar);
232 field(p,"target_command",o.target_command);
233 }
234 void draft(const Draft& o,const std::string& p)const {
235 field(p,"observation_present",bool(o.observation()));
236 if(o.observation())command(*o.observation(),p+".observation");
237 field(p,"review_present",o.review().has_value());if(o.review())review(*o.review(),p+".review");
238 field(p,"sizing_revision_present",o.sizing_revision().has_value());if(o.sizing_revision())revision(*o.sizing_revision(),p+".sizing_revision");
239 }
240 void book(const BookObservation& o,const std::string& p)const {
241 field(p,"incarnation",o.incarnation);field(p,"priority",o.priority);field(p,"bar",o.bar);
242 field(p,"type",o.type);field(p,"placement_side",o.placement_side);field(p,"buy",o.buy);field(p,"id",o.id);
243 field(p,"oca_name",o.oca_name);field(p,"oca_type",o.oca_type);birth(o.birth,p+".birth");
244 field(p+".prices","limit",o.prices.limit);field(p+".prices","stop",o.prices.stop);
245 field(p+".prices","trail_points",o.prices.trail_points);field(p+".prices","trail_price",o.prices.trail_price);
246 field(p+".prices","trail_offset",o.prices.trail_offset);draft(o.draft,p+".draft");
247 }
248 void resolution(const InstructionResolution& o,const std::string& p)const {
249 field(p,"incarnation",o.incarnation);field(p,"kind",o.kind);field(p,"peer_incarnation",o.peer_incarnation);
250 field(p,"own_priority",o.own_priority);field(p,"peer_priority",o.peer_priority);field(p,"transaction_quantity",o.transaction_quantity);
251 }
252 template<class T,class Fn>void array(const std::vector<T>& values,const std::string& p,Fn fn)const {
253 field(p,"size",uint64_t(values.size()));for(size_t i=0;i<values.size();++i)fn(values[i],p+"["+std::to_string(i)+"]");
254 }
255 void event(const Event& value,const std::string& p)const {
256 field(p,"kind",uint64_t(value.index()));
257 std::visit([&](const auto& o){using T=std::decay_t<decltype(o)>;
258 if constexpr(std::is_same_v<T,CommandEvent>){
259 command(*o.observation,p+".observation");field(p,"outcome",o.outcome);field(p,"admitted_incarnation",o.admitted_incarnation);
260 array(o.before,p+".before",[&](const auto& x,const auto& q){book(x,q);});
261 array(o.removed,p+".removed",[&](auto x,const auto& q){field(q,"incarnation",x);});
262 }else if constexpr(std::is_same_v<T,ReviewEvent>){
263 review(o.receipt,p+".receipt");config(o.configuration,p+".configuration");field(p,"open_price",o.open_price);field(p,"position_side",o.position_side);field(p,"position_cycle",o.position_cycle);array(o.book,p+".book",[&](const auto& x,const auto& q){book(x,q);});array(o.reviewed,p+".reviewed",[&](const auto& x,const auto& q){book(x,q);});
264 array(o.resolutions,p+".resolutions",[&](const auto& x,const auto& q){resolution(x,q);});
265 array(o.causes,p+".causes",[&](auto x,const auto& q){field(q,"sequence",x);});
266 }else{
267 revision(o.receipt,p+".receipt");field(p,"incarnation",o.incarnation);
268 sizing(o.before,p+".before");sizing(o.after,p+".after");
269 field(p,"affordability_equity_before",o.affordability_equity_before);field(p,"affordability_equity_after",o.affordability_equity_after);
270 }
271 },value);
272 }
273};
274}
275void reflect(const Draft& value,const std::string& path,const FieldVisitor& visit){Reflect{visit}.draft(value,path);}
276void reflect(const Event& value,const std::string& path,const FieldVisitor& visit){Reflect{visit}.event(value,path);}
277void Journal::reflect(const std::string& path,const FieldVisitor& visit)const {
278 Reflect r{visit};r.field(path,"next_sequence",next_sequence_);
279 r.field(path,"active_allocations",active_allocations_);
280 r.array(outstanding_sequences_,path+".outstanding_sequences",[&](auto sequence,const auto& p){r.field(p,"sequence",sequence);});
281 r.array(events_,path+".events",[&](const auto& event,const auto& p){r.event(event,p);});
282}
283std::vector<Field> fields(const Draft& draft) {
284 std::vector<Field> result;reflect(draft,"draft",[&](const auto& field){result.push_back(field);});return result;
285}
286namespace {
287template<class T>T read_field(const std::vector<Field>& fields,const std::string& path) {
288 for(const auto& field:fields)if(field.path==path)return std::get<T>(field.value);
289 return T{}; // absent optional payload; the presence leaf is always reflected
290}
291}
292uint64_t read_unsigned(const std::vector<Field>& f,const std::string& p){return read_field<uint64_t>(f,p);}
293int64_t read_integer(const std::vector<Field>& f,const std::string& p){return read_field<int64_t>(f,p);}
294double read_double(const std::vector<Field>& f,const std::string& p){return read_field<double>(f,p);}
295std::string read_string(const std::vector<Field>& f,const std::string& p){return read_field<std::string>(f,p);}
296} // inline namespace market_admission_v2
297} // namespace pineforge::admission
CommandCapture(Allocation allocation, CommandObservation input, std::vector< BookObservation > before, std::function< void(CommandEvent)> complete)
void bind(Draft &draft, std::optional< SizingObservation > sizing, double explicit_equity, double explicit_price)
const std::shared_ptr< const CommandObservation > & observation() const
void bind(std::shared_ptr< const CommandObservation > observation)
void reflect(const std::string &path, const FieldVisitor &visit) const
void retain(const std::vector< uint64_t > &sequences)
ReviewCapture(Allocation allocation, ReviewEvent event, std::function< void(ReviewEvent)> complete)
uint64_t read_unsigned(const std::vector< Field > &fields, const std::string &path)
std::string read_string(const std::vector< Field > &fields, const std::string &path)
int64_t read_integer(const std::vector< Field > &fields, const std::string &path)
std::vector< Field > fields(const Draft &draft)
std::function< void(const Field &)> FieldVisitor
double read_double(const std::vector< Field > &fields, const std::string &path)
void reflect(const Draft &value, const std::string &path, const FieldVisitor &visit)
std::variant< CommandEvent, ReviewEvent, SizingEvent > Event
int b(int64_t c)
Definition color.hpp:30
#define F(name)