36#include <algotradelib/backtest>
64InstrumentId index_fund() {
return InstrumentId{Country::KR, Venue::COMBINED,
"069500"}; }
67InstrumentId inverse_fund() {
return InstrumentId{Country::KR, Venue::COMBINED,
"114800"}; }
76struct InverseFundOnDowntrend {
82 std::size_t lookback_{5};
84 Decimal64 quantity_{Decimal64::parse(
"1000")};
86 std::vector<Decimal64> closes_;
89 void on_bar(Ctx& ctx,
const strategy::BarEvent& bar) {
92 if (!(
bar.instrument_ == signal_)) {
95 closes_.push_back(
bar.bar_.close_);
96 if (closes_.size() < lookback_) {
100 std::optional<Decimal64> sum = Decimal64::parse(
"0");
101 for (std::size_t at = closes_.size() - lookback_; sum && at < closes_.size(); ++at) {
106 if (!sum || !scaled) {
112 strategy::TargetPosition target;
115 target.
tag_ =
"inverse-fund";
121Instrument reference(
const InstrumentId&
id,
const char* name) {
122 Instrument instrument;
124 instrument.name_ = name;
125 instrument.type_ = SecurityType::ETF;
126 instrument.currency_ = Currency{
"KRW"};
127 instrument.lot_size_ = Decimal64::parse(
"1");
130 instrument.price_rule_ = PriceRule::unknown(
"an exchange-traded fund's tick grid is not stated here");
142const std::vector<Day>& sample() {
143 static const std::vector<Day> days = {
144 {99265, 1147, 1126}, {103250, 1113, 1080}, {107270, 1043, 1039}, {110060, 1008, 1011},
145 {108550, 984, 1026}, {101760, 1079, 1089}, {108190, 1054, 1021}, {109980, 1035, 1003},
146 {105995, 1009, 1040}, {106795, 1070, 1032}, {107710, 1036, 1024}, {109135, 991, 1009},
147 {107180, 1021, 1028}, {107615, 1057, 1025}, {107885, 1034, 1021}, {103500, 1054, 1063},
148 {103610, 1047, 1061}, {105720, 1047, 1040}, {110820, 1004, 989}, {110335, 982, 994},
149 {112100, 992, 978}, {111865, 982, 980}, {109500, 1018, 1002}, {105410, 1038, 1038},
150 {104275, 1047, 1050}, {106365, 1050, 1029}, {106280, 1019, 1030}, {109285, 1003, 1000},
151 {111700, 994, 978}, {111880, 957, 978}, {113145, 958, 966}, {109500, 971, 998},
152 {109990, 1000, 993}, {109545, 986, 998}, {111520, 1006, 981}, {112060, 985, 975},
153 {110745, 971, 987}, {108550, 999, 1008}, {105650, 1009, 1035}, {102695, 1046, 1065},
159Bar daily(std::chrono::system_clock::time_point at,
int open,
int close) {
162 const auto opened = Decimal64::parse(std::to_string(open));
163 const auto closed = Decimal64::parse(std::to_string(close));
166 bar.high_ = open > close ? opened : closed;
167 bar.low_ = open > close ? closed : opened;
168 bar.volume_ = Decimal64::parse(
"1000000");
178 backtest::ReplayConfig config;
179 config.cash_ = Decimal64::parse(
"10000000");
180 config.instruments_ = {
181 reference(index_fund(),
"an index fund (signal only)"),
182 reference(inverse_fund(),
"an inverse fund (traded)"),
184 config.series_capacity_ = 64;
186 config.limits_.permitted_phases_ = std::nullopt;
190 InverseFundOnDowntrend strategy_instance;
191 strategy_instance.signal_ = index_fund();
192 strategy_instance.fund_ = inverse_fund();
194 backtest::Backtester<InverseFundOnDowntrend> engine(std::move(strategy_instance), config);
195 const auto start = std::chrono::system_clock::time_point{} + std::chrono::hours(24 * 20000);
196 for (std::size_t at = 0; at < sample().size(); ++at) {
197 const auto when =
start + std::chrono::hours(24 *
static_cast<int>(at));
198 const auto& day = sample()[at];
202 inverse_fund(), BarInterval::ONE_DAY, BarStamp::CLOSE, daily(when, day.fund_open_, day.fund_close_)));
204 index_fund(), BarInterval::ONE_DAY, BarStamp::CLOSE, daily(when, day.index_close_, day.index_close_)));
208 const auto& result = engine.result();
209 std::cout <<
"inverse fund 114800 traded on a signal from index fund 069500, " << sample().size()
210 <<
" sample days -- no credentials, no network\n\n";
211 for (
const auto& execution : result.executions_) {
212 const auto&
fill = execution.fill_;
213 const auto day = std::chrono::duration_cast<std::chrono::hours>(
fill.at_ - start).count() / 24;
214 std::cout <<
" day " << day <<
" " << (
fill.side_ == OrderSide::BUY ?
"BUY " :
"SELL") <<
" "
215 <<
fill.quantity_.str() <<
" fund shares at " <<
fill.price_.str() <<
"\n";
217 std::cout <<
"\n orders filled: " << result.executions_.size() <<
", refused by the guard: "
218 << result.refused_.size() <<
", not buildable: " << result.rejected_.size() <<
"\n";
219 if (!result.equity_.empty()) {
220 std::cout <<
" equity: " << config.cash_.str() <<
" -> " << result.equity_.back().equity_.str() <<
"\n";
223 <<
"\nEvery order above is for the fund, and every sell is an exit of shares bought earlier: the\n"
224 <<
"account is never short, and the default sellable check never has anything to refuse.\n"
225 <<
"The fund follows the opposite of the index's move one day at a time. Held across many days its\n"
226 <<
"value is not what a short opened at the start would be worth, and it says nothing about any\n"
227 <<
"single stock. Forty sample days rank nothing.\n";
A three-letter currency code, such as KRW or USD (ISO 4217 style).
An exact signed decimal: an int64 coefficient times 10^-scale.
#define ATL_ASSERT_STRATEGY(S, Ctx)
Asserts at compile time that S is a strategy a runner with context Ctx can drive.
Replays a strategy over stored bars: fill and cost models, the replay ledger, and the report.
void start(S &strategy, Ctx &context)
Calls strategy.on_start(context) if the strategy declares on_start; otherwise does nothing.
void bar(S &strategy, Ctx &context, const BarEvent &event)
Calls strategy.on_bar(context, event) if the strategy declares on_bar; otherwise does nothing.
void fill(S &strategy, Ctx &context, const Fill &event)
Calls strategy.on_fill(context, event) if the strategy declares on_fill; otherwise does nothing.
What a strategy sees and emits: events, intents, the portfolio, reconciliation and risk limits.
BarEvent make_bar_event(InstrumentId instrument, BarInterval interval, BarStamp stamp, Bar bar)
Makes a bar event, computing its period_end_ with bar_period_end.
ProbeContextT< NoCaps > MinimalContext
A probe context whose broker implements nothing optional.
ProbeContextT< AllCaps > FullContext
A probe context whose broker implements everything optional.
BarStamp
Which edge of its period a bar's timestamp marks.
SecurityType
What kind of security an instrument is.
std::optional< Decimal64 > decimal_add(const Decimal64 &lhs, const Decimal64 &rhs)
Adds two decimals exactly.
Country
A country (market) the library supports.
Venue
A trading venue: an exchange, or the combined view across exchanges.
bool decimal_less(const Decimal64 &lhs, const Decimal64 &rhs)
Compares two decimals numerically, whatever their scales.
std::optional< Decimal64 > decimal_multiply(const Decimal64 &lhs, const Decimal64 &rhs)
Multiplies two decimals exactly; the scales add.
BarInterval
The period one bar covers.
OrderSide
The side of an order: its economic direction, buy or sell.
One bar: the prices and volume of one period, as exact decimals.
Identifies an instrument: its country, venue and symbol.
Reference data for one instrument.
The price grid (tick sizes) an instrument's order prices must sit on.
InstrumentId instrument_
The instrument.
std::string tag_
A label carried to the orders and their fills.
Decimal64 target_quantity_
The position to hold, signed: negative is short, and zero means flat.