algotradelib 0.0.0.9000
C++ header-only library for algorithmic trading
Loading...
Searching...
No Matches
portfolio.hpp
1#pragma once
2
3#include <algotradelib/core>
4
5#include <chrono>
6#include <optional>
7#include <vector>
8
9namespace algotradelib {
10namespace strategy {
11
20inline bool order_is_open(OrderStatus status) {
21 switch (status) {
22 case OrderStatus::UNKNOWN:
23 case OrderStatus::PENDING:
24 case OrderStatus::PENDING_CANCEL:
25 case OrderStatus::PENDING_REPLACE:
26 case OrderStatus::PARTIAL_FILLED:
27 return true;
28 case OrderStatus::FILLED:
29 case OrderStatus::CANCELED:
30 case OrderStatus::REJECTED:
31 case OrderStatus::CANCEL_REJECTED:
32 case OrderStatus::REPLACE_REJECTED:
33 case OrderStatus::REPLACED:
34 return false;
35 }
36 return true;
37}
38
49template <typename Broker>
50struct Portfolio {
56 std::vector<HoldingPosition> positions_;
64 std::vector<Money> cash_;
71 std::vector<BuyingPower> buying_power_;
77 std::vector<WorkingOrder<Broker>> working_;
84 std::chrono::system_clock::time_point as_of_{};
90 std::chrono::system_clock::time_point working_as_of_{};
91
101 bool listing_newer_than_holdings() const noexcept {
102 const std::chrono::system_clock::time_point epoch{};
103 return as_of_ != epoch && working_as_of_ != epoch && working_as_of_ > as_of_;
104 }
105
113
122 std::optional<Decimal64> quantity_of(const InstrumentId& instrument) const {
123 for (const auto& position : positions_) {
124 if (position.instrument_ == instrument) {
125 return position.quantity_;
126 }
127 }
128 return std::nullopt;
129 }
130
140 std::optional<Decimal64> net_quantity_of(const InstrumentId& instrument) const {
141 auto total = Decimal64::parse("0");
142 for (const auto& position : positions_) {
143 if (!(position.instrument_ == instrument)) {
144 continue;
145 }
146 const auto sum = decimal_add(total, position.quantity_);
147 if (!sum) {
148 return std::nullopt;
149 }
150 total = *sum;
151 }
152 return total;
153 }
154
165 Decimal64 working_quantity(const InstrumentId& instrument, OrderSide side) const {
166 Decimal64 total = Decimal64::parse("0");
167 for (const auto& order : working_) {
168 if (!(order.order_.instrument_ == instrument) || order.order_.side_ != side) {
169 continue;
170 }
171 if (!order_is_open(order.order_.status_)) {
172 continue;
173 }
174 const auto remaining = decimal_subtract(
175 order.order_.quantity_, order.order_.execution_.filled_quantity_
176 );
177 if (!remaining || decimal_less(*remaining, Decimal64::parse("0"))) {
178 continue;
179 }
180 const auto sum = decimal_add(total, *remaining);
181 if (!sum) {
182 continue;
183 }
184 total = *sum;
185 }
186 return total;
187 }
188
189 public:
193 std::optional<Decimal64> quantity_;
196 };
197
210 SellableReading reading;
211 bool seen = false;
212 for (const auto& position : positions_) {
213 if (!(position.instrument_ == instrument)) {
214 continue;
215 }
216 const SellableBasis basis =
217 position.sellable_basis_ ? *position.sellable_basis_ : sellable_basis_;
218 if (!seen) {
219 reading.quantity_ = position.sellable_quantity_;
220 reading.basis_ = basis;
221 seen = true;
222 continue;
223 }
224 // A second row of the same instrument. Figures are never summed, and a disagreement makes even
225 // the first row's figure uninterpretable, since nothing says which lot a sale draws from.
226 // First, a basis that disagrees.
227 if (basis.coverage_ != reading.basis_.coverage_ ||
228 basis.resting_ != reading.basis_.resting_) {
229 return SellableReading{reading.quantity_, SellableBasis{}};
230 }
231 // Then figures that disagree under one basis: two rows claiming the limit for the same shares
232 // with different numbers contradict each other. (KIS pages can repeat a `pdno`.)
233 // Compared as numbers: `operator==` also compares the scale, and "70" beside "70.0" is one figure.
234 if (reading.quantity_ && position.sellable_quantity_ &&
235 (decimal_less(*position.sellable_quantity_, *reading.quantity_) ||
236 decimal_less(*reading.quantity_, *position.sellable_quantity_))) {
237 return SellableReading{reading.quantity_, SellableBasis{}};
238 }
239 // A later row's figure where the first had none is the only figure stated.
240 if (!reading.quantity_) {
241 reading.quantity_ = position.sellable_quantity_;
242 }
243 }
244 return reading;
245 }
246
252 std::optional<Decimal64> cash_in(const Currency& currency) const {
253 for (const auto& amount : cash_) {
254 if (amount.currency_ == currency) {
255 return amount.amount_;
256 }
257 }
258 return std::nullopt;
259 }
260
270 std::optional<Decimal64> buying_power_in(const Currency& currency) const {
271 for (const auto& amount : buying_power_) {
272 if (amount.cash_.currency_ == currency) {
273 return amount.cash_.amount_;
274 }
275 }
276 return std::nullopt;
277 }
278
290 std::optional<Decimal64> sellable_quantity_of(const InstrumentId& instrument) const {
291 for (const auto& position : positions_) {
292 if (position.instrument_ == instrument) {
293 return position.sellable_quantity_;
294 }
295 }
296 return std::nullopt;
297 }
298
305 for (const auto& order : working_) {
306 if (order.ref_ == ref) {
307 return &order;
308 }
309 }
310 return nullptr;
311 }
312
313};
314
315} // namespace strategy
316} // namespace algotradelib
A three-letter currency code, such as KRW or USD (ISO 4217 style).
Definition types.hpp:234
An exact signed decimal: an int64 coefficient times 10^-scale.
Definition types.hpp:297
static Decimal64 parse(std::string_view input)
Parses a decimal string exactly (no binary floating point).
Definition types.hpp:323
What a strategy sees and emits: events, intents, the portfolio, reconciliation and risk limits.
Definition concept.hpp:16
bool order_is_open(OrderStatus status)
Tests whether an order in a given status may still trade.
Definition portfolio.hpp:20
The library: broker-neutral types (core), and the modules below.
OrderStatus
Where an order stands in its lifecycle.
Definition types.hpp:1879
std::optional< Decimal64 > decimal_add(const Decimal64 &lhs, const Decimal64 &rhs)
Adds two decimals exactly.
Definition types.hpp:509
std::optional< Decimal64 > decimal_subtract(const Decimal64 &lhs, const Decimal64 &rhs)
Subtracts two decimals exactly.
Definition types.hpp:530
bool decimal_less(const Decimal64 &lhs, const Decimal64 &rhs)
Compares two decimals numerically, whatever their scales.
Definition types.hpp:665
OrderSide
The side of an order: its economic direction, buy or sell.
Definition types.hpp:1683
Identifies an instrument: its country, venue and symbol.
Definition types.hpp:78
A broker's reference to one order, used to amend or cancel it.
Definition types.hpp:2111
What a sellable quantity covers, and how RiskGuard must treat resting sells against it.
Definition types.hpp:918
RestingTreatment resting_
How resting sells are treated; unestablished by default.
Definition types.hpp:948
Coverage coverage_
What the figure covers; unestablished by default.
Definition types.hpp:946
An order still open to modification or cancellation.
Definition types.hpp:2290
A sellable quantity together with what it covers, read from the same row.
A sellable quantity together with what it covers, read from the same row.
SellableBasis basis_
What the figure covers; unestablished if the rows disagree.
std::optional< Decimal64 > quantity_
The figure, or std::nullopt if no row of the instrument reported one.
What the account holds and which orders are still working, as a strategy sees it.
Definition portfolio.hpp:50
std::vector< WorkingOrder< Broker > > working_
Orders still outstanding at the broker.
Definition portfolio.hpp:77
SellableBasis sellable_basis_
What the sellable_quantity_ figures on positions_ cover.
std::vector< Money > cash_
Cash per currency, in a backtest only.
Definition portfolio.hpp:64
std::optional< Decimal64 > buying_power_in(const Currency &currency) const
Returns the buying power in a currency.
std::optional< Decimal64 > net_quantity_of(const InstrumentId &instrument) const
Returns the net quantity held of an instrument, summed over every row.
Decimal64 working_quantity(const InstrumentId &instrument, OrderSide side) const
Returns the quantity still to fill on open orders for an instrument and side.
std::chrono::system_clock::time_point working_as_of_
When the working-order listing was received, on the same clock as as_of_.
Definition portfolio.hpp:90
std::optional< Decimal64 > quantity_of(const InstrumentId &instrument) const
Returns the quantity of the first position row for an instrument.
const WorkingOrder< Broker > * find_working(const OrderRef< Broker > &ref) const
Finds a working order by its reference.
std::vector< BuyingPower > buying_power_
What may be spent, per currency.
Definition portfolio.hpp:71
std::optional< Decimal64 > cash_in(const Currency &currency) const
Returns the cash held in a currency (backtest only; see cash_).
std::chrono::system_clock::time_point as_of_
When the holdings were received, on this machine's clock.
Definition portfolio.hpp:84
std::optional< Decimal64 > sellable_quantity_of(const InstrumentId &instrument) const
Returns the sellable quantity the broker reported for an instrument (first row).
SellableReading sellable_reading_for(const InstrumentId &instrument) const
Returns an instrument's sellable quantity and its basis, taken from the same row.
std::vector< HoldingPosition > positions_
Every position row the broker reported.
Definition portfolio.hpp:56
bool listing_newer_than_holdings() const noexcept
Tests whether the working-order listing is newer than the holdings.