PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
pine_position_tail.cpp
Go to the documentation of this file.
3
7
8#include <algorithm>
9#include <array>
10#include <cstring>
11#include <limits>
12#include <stdexcept>
13
15namespace {
16
17static_assert(sizeof(double) == 8 && std::numeric_limits<double>::is_iec559,
18 "position-tail scratch records preserve binary64 quantities");
19
20[[noreturn]] void invalid_record() {
21 throw coded<std::logic_error>(RunFailureCode::engine_invariant, {},
22 "invalid Pine position-tail spill record");
23}
24
25} // namespace
26
27struct PositionTailSpill::State {
28 PositionTailBinding::File file;
29 std::uint64_t end = 0;
30 std::uint64_t rows = 0;
31 // One fixed read window; append writes directly from the existing row.
32 mutable std::array<unsigned char, kWindowBytes> window{};
33 mutable std::uint64_t window_begin = 0;
34 mutable std::size_t window_size = 0;
35
36 explicit State(const std::string& directory) : file(directory) {}
37
38 void write(std::uint64_t offset, const void* data, std::size_t size) {
39 const auto* bytes = static_cast<const unsigned char*>(data);
40 for (std::size_t done = 0; done < size;) {
41 // Also bounds each syscall for identifiers larger than a window.
42 const std::size_t count = std::min(size - done, kWindowBytes);
43 file.write(offset + done, bytes + done, count);
44 done += count;
45 }
46 }
47
48 void refill(std::uint64_t offset) const {
49 // End a backwards window at the requested byte. A footer crossing
50 // this boundary is copied in pieces, just like an oversized id.
51 const std::uint64_t limit = offset + 1;
52 const std::uint64_t begin = limit > kWindowBytes ? limit - kWindowBytes : 0;
53 const auto size = static_cast<std::size_t>(limit - begin);
54 window_size = 0;
55 file.read(begin, window.data(), size);
56 window_begin = begin;
57 window_size = size;
58 }
59
60 void read(std::uint64_t offset, void* data, std::size_t size) const {
61 if (offset > end || size > end - offset) invalid_record();
62 auto* bytes = static_cast<unsigned char*>(data);
63 // Copy backwards so a reverse walk consumes each cached window
64 // before loading the preceding one.
65 std::size_t left = size;
66 while (left != 0) {
67 const std::uint64_t last = offset + left - 1;
68 if (last < window_begin || last - window_begin >= window_size) refill(last);
69 const std::size_t count = static_cast<std::size_t>(
70 std::min<std::uint64_t>(left, last - window_begin + 1));
71 std::memcpy(bytes + left - count,
72 window.data() + static_cast<std::size_t>(last - window_begin + 1) - count,
73 count);
74 left -= count;
75 }
76 }
77};
78
79PositionTailSpill::PositionTailSpill(const std::string& scratch_directory)
80 : state_(std::make_unique<State>(scratch_directory)) {}
81
83
84void PositionTailSpill::append(const Trade& trade) {
85 const auto length = trade.entry_id.size();
86 const auto maximum = static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max());
87 if (length > std::numeric_limits<std::uint32_t>::max()
88 || state_->end > maximum - kFooterBytes
89 || length > maximum - kFooterBytes - state_->end
90 || state_->rows == std::numeric_limits<std::uint64_t>::max()) {
92 "Pine position-tail spill offset exhausted");
93 }
94 std::array<unsigned char, kFooterBytes> footer{};
95 const auto id_size = static_cast<std::uint32_t>(length);
96 std::memcpy(footer.data(), &trade.exit_time, 8);
97 std::memcpy(footer.data() + 8, &trade.qty, 8);
98 std::memcpy(footer.data() + 16, &trade.entry_incarnation, 8);
99 footer[24] = trade.is_long ? 1 : 0;
100 footer[25] = trade.exit_id == kMarginCallLabel ? 1 : 0;
101 std::memcpy(footer.data() + 26, &id_size, 4);
102 state_->write(state_->end, trade.entry_id.data(), length);
103 state_->write(state_->end + length, footer.data(), footer.size());
104 // Publish only after both writes. A failed append leaves the logical
105 // end intact, so the caller never drops the row whose write failed.
106 state_->end += static_cast<std::uint64_t>(length) + kFooterBytes;
107 ++state_->rows;
108 state_->window_size = 0;
109}
110
112 state_->file.clear();
113 state_->end = 0;
114 state_->rows = 0;
115 state_->window_size = 0;
116}
117
118std::uint64_t PositionTailSpill::size_bytes() const noexcept { return state_->end; }
119std::uint64_t PositionTailSpill::row_count() const noexcept { return state_->rows; }
120
121bool PositionTailSpill::previous(std::uint64_t& cursor, Row& row) const {
122 if (cursor > state_->end) invalid_record();
123 if (cursor == 0) return false;
124 if (cursor < kFooterBytes) invalid_record();
125 std::array<unsigned char, kFooterBytes> footer{};
126 state_->read(cursor - kFooterBytes, footer.data(), footer.size());
127 Row next;
128 std::memcpy(&next.exit_time, footer.data(), 8);
129 std::memcpy(&next.qty, footer.data() + 8, 8);
130 std::memcpy(&next.entry_incarnation, footer.data() + 16, 8);
131 if (footer[24] > 1 || footer[25] > 1) invalid_record();
132 next.is_long = footer[24] != 0;
133 next.margin_call = footer[25] != 0;
134 std::memcpy(&next.entry_id_size, footer.data() + 26, 4);
135 if (next.entry_id_size > cursor - kFooterBytes) invalid_record();
136 next.entry_id_offset = cursor - kFooterBytes - next.entry_id_size;
137 cursor = next.entry_id_offset;
138 row = next;
139 return true;
140}
141
142bool PositionTailSpill::entry_id_equals(const Row& row, std::string_view entry_id) const {
143 if (entry_id.size() != row.entry_id_size) return false;
144 if (row.entry_id_offset > state_->end
145 || row.entry_id_size > state_->end - row.entry_id_offset) invalid_record();
146 // Compare in fixed-sized pieces; no owning copy of a spilled id.
147 std::array<unsigned char, 256> bytes{};
148 std::size_t left = entry_id.size();
149 while (left != 0) {
150 const std::size_t count = std::min(left, bytes.size());
151 const std::size_t begin = left - count;
152 state_->read(row.entry_id_offset + begin, bytes.data(), count);
153 if (std::memcmp(bytes.data(), entry_id.data() + begin, count) != 0) return false;
154 left = begin;
155 }
156 return true;
157}
158
159} // namespace pineforge::source::detail
Base with a code: throw coded<std::invalid_argument>(code, {args}, text).
PositionTailSpill(const std::string &scratch_directory)
bool previous(std::uint64_t &cursor, Row &row) const
bool entry_id_equals(const Row &row, std::string_view entry_id) const
constexpr char kMarginCallLabel[]
uint64_t entry_incarnation
Definition engine.hpp:213
int64_t exit_time
Definition engine.hpp:183
std::string entry_id
Definition engine.hpp:192
std::string exit_id
Definition engine.hpp:195