algotradelib 0.0.0.9000
C++ header-only library for algorithmic trading
Loading...
Searching...
No Matches
instrument.hpp
1#pragma once
2
3#include "./types.hpp"
4
5#include <optional>
6#include <stdexcept>
7#include <string>
8#include <string_view>
9#include <vector>
10
11namespace algotradelib {
12
20 enum class Known {
21 UNKNOWN,
22 KOSPI,
23 KOSDAQ,
24 NYSE,
25 NASDAQ,
26 AMEX,
27 };
28
30 Known known_{Known::UNKNOWN};
38
43 std::string str() const {
44 switch (known_) {
45 case Known::KOSPI:
46 return "KOSPI";
47 case Known::KOSDAQ:
48 return "KOSDAQ";
49 case Known::NYSE:
50 return "NYSE";
51 case Known::NASDAQ:
52 return "NASDAQ";
53 case Known::AMEX:
54 return "AMEX";
55 case Known::UNKNOWN:
56 break;
57 }
58 return other_.empty() ? std::string("UNKNOWN") : other_.str();
59 }
60
67 friend bool operator==(const MarketSegment& lhs, const MarketSegment& rhs) {
68 return lhs.known_ == rhs.known_ && lhs.other_ == rhs.other_;
69 }
70
77 friend bool operator!=(const MarketSegment& lhs, const MarketSegment& rhs) {
78 return !(lhs == rhs);
79 }
80};
81
88inline MarketSegment market_segment(std::string_view code) {
89 MarketSegment segment;
90 if (code == "KOSPI") {
91 segment.known_ = MarketSegment::Known::KOSPI;
92 } else if (code == "KOSDAQ") {
93 segment.known_ = MarketSegment::Known::KOSDAQ;
94 } else if (code == "NYSE") {
95 segment.known_ = MarketSegment::Known::NYSE;
96 } else if (code == "NASDAQ") {
97 segment.known_ = MarketSegment::Known::NASDAQ;
98 } else if (code == "AMEX") {
99 segment.known_ = MarketSegment::Known::AMEX;
100 } else if (const auto preserved = Code<12>::try_make(code)) {
101 segment.other_ = *preserved;
102 }
103 return segment;
104}
105
111enum class SecurityType {
112 UNKNOWN,
113 STOCK,
114 FOREIGN_STOCK,
115 DEPOSITARY_RECEIPT,
116 INFRASTRUCTURE_FUND,
117 REIT,
118 ETF,
119 FOREIGN_ETF,
120 ETN,
121 STOCK_WARRANT,
122};
123
126 UNKNOWN,
127 SCHEDULED,
128 ACTIVE,
129 DELISTED,
130};
131
141
150struct PriceRule {
152 enum class Kind {
154 STATED,
156 UNCONSTRAINED,
161 UNKNOWN,
162 };
163
167 std::vector<TickBand> bands_;
169 std::string source_;
170
175 constexpr bool grid_known() const noexcept {
176 switch (kind_) {
177 case Kind::STATED:
179 return true;
180 case Kind::UNKNOWN:
181 return false;
182 }
183 return false;
184 }
185
192 std::optional<Decimal64> tick_at(const Decimal64& price) const {
193 std::optional<Decimal64> tick;
194 for (const auto& band : bands_) {
195 if (decimal_less(price, band.from_)) {
196 break;
197 }
198 tick = band.tick_;
199 }
200 return tick;
201 }
202
210 std::optional<bool> on_tick(const Decimal64& price) const {
211 if (!grid_known()) {
212 return std::nullopt;
213 }
214 const auto tick = tick_at(price);
215 if (!tick) {
216 return true;
217 }
218 const auto rest = remainder(price, *tick);
219 if (!rest) {
220 return std::nullopt;
221 }
222 return rest->coefficient() == 0;
223 }
224
237 std::optional<Decimal64> round_to_tick(const Decimal64& price, OrderSide side) const {
238 if (!grid_known()) {
239 return std::nullopt;
240 }
241 const auto tick = tick_at(price);
242 if (!tick) {
243 return price;
244 }
245 const auto rest = remainder(price, *tick);
246 if (!rest) {
247 return std::nullopt;
248 }
249 if (rest->coefficient() == 0) {
250 return decimal_normalized(price);
251 }
252 const auto floor_price = decimal_subtract(price, *rest);
253 if (!floor_price) {
254 return std::nullopt;
255 }
256 if (side == OrderSide::BUY) {
257 return floor_price;
258 }
259 return decimal_add(*floor_price, *tick);
260 }
261
267 PriceRule rule;
268 rule.kind_ = Kind::STATED;
269 rule.source_ = "KRX cash equity tick table, 2023-01 revision (library-maintained)";
270 rule.bands_ = {
276 TickBand{Decimal64::parse("200000"), Decimal64::parse("500")},
277 TickBand{Decimal64::parse("500000"), Decimal64::parse("1000")},
278 };
279 return rule;
280 }
281
287 PriceRule rule;
289 rule.source_ = "no exchange price grid";
290 return rule;
291 }
292
298 static PriceRule unknown(std::string reason) {
299 PriceRule rule;
300 rule.kind_ = Kind::UNKNOWN;
301 rule.source_ = std::move(reason);
302 return rule;
303 }
304
305private:
306 // `price` modulo `tick`, exactly.
307 static std::optional<Decimal64> remainder(const Decimal64& price, const Decimal64& tick) {
308 std::int64_t left = 0;
309 std::int64_t right = 0;
310 std::uint8_t scale = 0;
311 if (!decimal_detail::align(price, tick, left, right, scale) || right == 0) {
312 return std::nullopt;
313 }
314 auto rest = left % right;
315 if (rest < 0) {
316 rest += right < 0 ? -right : right;
317 }
318 return Decimal64(rest, scale);
319 }
320};
321
334 switch (country) {
335 case Country::US:
337 case Country::KR:
338 break;
339 }
340 if (country != Country::KR) {
341 // Not a `Country` (a cast): no grid is known.
342 return PriceRule::unknown("no price grid is known for this market");
343 }
344 switch (type) {
345 case SecurityType::STOCK:
346 case SecurityType::FOREIGN_STOCK:
347 case SecurityType::DEPOSITARY_RECEIPT:
348 case SecurityType::INFRASTRUCTURE_FUND:
349 case SecurityType::REIT:
350 return PriceRule::krx_equity();
351 case SecurityType::ETF:
352 case SecurityType::FOREIGN_ETF:
353 case SecurityType::ETN:
354 return PriceRule::unknown(
355 "KRX prices funds and notes on a ladder this library does not carry"
356 );
357 case SecurityType::STOCK_WARRANT:
358 case SecurityType::UNKNOWN:
359 break;
360 }
361 return PriceRule::unknown("no price grid is known for this security type");
362}
363
381public:
383 enum class State {
385 NOT_STATED,
387 STATED,
390 };
391
393 StatedDate() = default;
394
401 StatedDate result;
402 result.state_ = State::STATED;
403 result.date_ = date;
404 return result;
405 }
406
412 StatedDate result;
413 result.state_ = State::UNREADABLE;
414 return result;
415 }
416
421 State state() const noexcept { return state_; }
422
428 const Date& date() const {
429 if (state_ != State::STATED) {
430 throw std::logic_error("StatedDate::date() read a date that was not stated");
431 }
432 return date_;
433 }
434
441 friend bool operator==(const StatedDate& lhs, const StatedDate& rhs) {
442 return lhs.state_ == rhs.state_ && (lhs.state_ != State::STATED || lhs.date_ == rhs.date_);
443 }
444
451 friend bool operator!=(const StatedDate& lhs, const StatedDate& rhs) { return !(lhs == rhs); }
452
453private:
454 State state_{State::NOT_STATED};
455 Date date_{};
456};
457
467
478 std::string name_;
480 std::string english_name_;
482 std::string isin_;
486 SecurityType type_{SecurityType::UNKNOWN};
488 InstrumentStatus status_{InstrumentStatus::UNKNOWN};
492 bool common_share_{true};
502 std::optional<Decimal64> shares_outstanding_;
504 std::optional<Decimal64> lot_size_;
507
517 using State = StatedDate::State;
518 if (listed_on_.state() == State::STATED && day < listed_on_.date()) {
520 }
521 if (delisted_on_.state() == State::STATED && !(day < delisted_on_.date())) {
523 }
524 if (leaves_exclusion_unknown(listed_on_.state()) || leaves_exclusion_unknown(delisted_on_.state())) {
526 }
528 }
529
530private:
531 // Whether a date in this state could rule a day out without saying so: only an unreadable one.
532 static constexpr bool leaves_exclusion_unknown(StatedDate::State state) noexcept {
533 switch (state) {
535 return true;
538 return false;
539 }
540 return true;
541 }
542};
543
544} // namespace algotradelib
A short uppercase code (letters, digits, _), stored inline without allocation.
Definition types.hpp:131
bool empty() const noexcept
Tests whether the code is empty.
Definition types.hpp:198
static std::optional< Code > try_make(std::string_view text)
Makes a code without throwing.
Definition types.hpp:163
std::string str() const
Returns the code as a string.
Definition types.hpp:192
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
Decimal64()=default
Constructs zero.
static Decimal64 parse(std::string_view input)
Parses a decimal string exactly (no binary floating point).
Definition types.hpp:323
A date a broker stated, did not state, or stated in a form that could not be read.
State
What the broker's field said.
@ STATED
A date that was read.
@ UNREADABLE
Something that is not a readable calendar date.
@ NOT_STATED
No date: absent, null, or the broker's empty spelling.
friend bool operator==(const StatedDate &lhs, const StatedDate &rhs)
Compares two values: the same state, and the same date when stated.
const Date & date() const
Returns the date.
State state() const noexcept
Returns what the broker's field said.
friend bool operator!=(const StatedDate &lhs, const StatedDate &rhs)
Compares two values for inequality.
StatedDate()=default
Constructs a date the broker did not state.
static StatedDate unreadable()
Makes an unreadable date.
static StatedDate stated(Date date)
Makes a stated date.
The library: broker-neutral types (core), and the modules below.
PriceRule price_rule_for(Country country, SecurityType type)
Returns the library's price grid for a kind of security in a country.
MarketSegment market_segment(std::string_view code)
Maps a broker's segment code to a MarketSegment.
SecurityType
What kind of security an instrument is.
std::optional< Decimal64 > decimal_add(const Decimal64 &lhs, const Decimal64 &rhs)
Adds two decimals exactly.
Definition types.hpp:509
InstrumentStatus
Whether an instrument is listed, awaiting listing, or delisted.
DateExclusion
What an instrument's listing and delisting dates say about one day.
@ EXCLUDED
A readable date rules the day out: listed after it, or delisted on or before it.
@ UNREADABLE
No readable date rules it out, but an unreadable date might.
@ NOT_EXCLUDED
No date rules it out and neither is unreadable. This does not mean "listed".
Country
A country (market) the library supports.
Definition types.hpp:30
Decimal64 decimal_normalized(const Decimal64 &value)
Removes trailing fractional zeros, so equal numbers compare equal with ==.
Definition types.hpp:455
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
A calendar date with no time of day or time zone.
Definition types.hpp:1142
Identifies an instrument: its country, venue and symbol.
Definition types.hpp:78
Reference data for one instrument.
std::optional< Decimal64 > shares_outstanding_
Shares outstanding, if published; KIS's blank or zero is empty, never zero shares.
InstrumentStatus status_
Listed, awaiting listing, or delisted.
Currency currency_
The currency it trades in.
InstrumentId id_
The instrument.
std::string isin_
The ISIN, or empty.
StatedDate delisted_on_
The delisting date; always NOT_STATED from Kiwoom, which publishes none.
DateExclusion dates_exclude(Date day) const
Tests whether the listing and delisting dates rule a day out.
StatedDate listed_on_
The listing date.
bool common_share_
Whether this is the common share, not a preferred or derived line.
PriceRule price_rule_
The price grid; kind UNKNOWN where the library has no table.
MarketSegment segment_
The market segment.
std::optional< Decimal64 > lot_size_
The order quantity increment, if the broker publishes one.
std::string english_name_
The English name, or empty.
SecurityType type_
The kind of security, which selects the price grid.
std::string name_
The local-language name, as the broker spells it.
The market segment an instrument is listed on, such as KOSPI or NASDAQ.
Code< 12 > other_
The broker's code, when known_ is UNKNOWN and the code fits Code<12>.
friend bool operator!=(const MarketSegment &lhs, const MarketSegment &rhs)
Compares two segments for inequality.
Known known_
The segment, or UNKNOWN if the broker's code was not recognized.
Known
The segments the library recognizes.
std::string str() const
Returns the segment's name for display.
friend bool operator==(const MarketSegment &lhs, const MarketSegment &rhs)
Compares the normalized segment and the preserved code.
The price grid (tick sizes) an instrument's order prices must sit on.
std::optional< Decimal64 > tick_at(const Decimal64 &price) const
Returns the tick size at a price.
std::string source_
Where the table came from and its revision, or why there is none.
static PriceRule unconstrained()
Returns a rule with no grid, for a market quoted in free decimals.
std::optional< bool > on_tick(const Decimal64 &price) const
Tests whether a price is on the grid.
std::optional< Decimal64 > round_to_tick(const Decimal64 &price, OrderSide side) const
Rounds a price to its band's tick in the order's favour: a buy rounds down, a sell up.
std::vector< TickBand > bands_
The bands, ascending by from_; empty unless kind_ is STATED.
static PriceRule krx_equity()
Returns the KRX cash-equity tick table (January 2023 revision).
Kind
Whether the rule states a grid, imposes none, or does not know it.
@ STATED
bands_ describe the market's grid.
@ UNCONSTRAINED
The market imposes no grid (free decimals).
@ UNKNOWN
The library has no grid for this instrument (for example a KRX ETF or ETN) and will not guess one.
constexpr bool grid_known() const noexcept
Tests whether the rule says anything about the grid: a stated one, or none at all.
static PriceRule unknown(std::string reason)
Returns a rule that knows no grid and refuses to round.
Kind kind_
The kind of rule; UNKNOWN by default, so an unset rule refuses to round.
One band of a price grid: the tick size from a price upward, until the next band.
Decimal64 from_
The price the band starts at, inclusive.
Decimal64 tick_
The tick size within the band.