[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/SlickQuant/alpaca-cpp/main/tests/order_request_tests.cpp [Back]  [Original]

// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Slick Quant
// https://github.com/SlickQuant/alpaca-cpp
//
// Offline tests for order request serialisation. Alpaca requires every numeric field as a
// decimal string and rejects scientific notation, so these assert the exact wire payload.

#include 

#include 
#include 
#include 
#include 

namespace alpaca::tests {

TEST(OrderRequest, MarketOrder) {
    const auto j = order_request::market("AAPL", 10, order_side::buy).to_json();

    EXPECT_EQ(j["symbol"], "AAPL");
    EXPECT_EQ(j["side"], "buy");
    EXPECT_EQ(j["type"], "market");
    EXPECT_EQ(j["time_in_force"], "day");
    EXPECT_EQ(j["qty"], "10");
    EXPECT_FALSE(j.contains("limit_price"));
    EXPECT_FALSE(j.contains("notional"));
}

TEST(OrderRequest, NotionalMarketOrder) {
    const auto j = order_request::market_notional("AAPL", 500.25, order_side::buy).to_json();

    EXPECT_EQ(j["notional"], "500.25");
    EXPECT_FALSE(j.contains("qty"));
    EXPECT_EQ(j["time_in_force"], "day");
}

TEST(OrderRequest, LimitOrderWithGtc) {
    const auto j = order_request::limit("MSFT", 5, order_side::sell, 412.5,
                                        time_in_force::gtc).to_json();

    EXPECT_EQ(j["type"], "limit");
    EXPECT_EQ(j["side"], "sell");
    EXPECT_EQ(j["limit_price"], "412.5");
    EXPECT_EQ(j["time_in_force"], "gtc");
}

TEST(OrderRequest, StopAndStopLimit) {
    const auto stop = order_request::stop("AAPL", 1, order_side::sell, 90).to_json();
    EXPECT_EQ(stop["type"], "stop");
    EXPECT_EQ(stop["stop_price"], "90");
    EXPECT_FALSE(stop.contains("limit_price"));

    const auto stop_limit = order_request::stop_limit("AAPL", 1, order_side::sell, 90, 89.5).to_json();
    EXPECT_EQ(stop_limit["type"], "stop_limit");
    EXPECT_EQ(stop_limit["stop_price"], "90");
    EXPECT_EQ(stop_limit["limit_price"], "89.5");
}

TEST(OrderRequest, TrailingStopByPriceAndPercent) {
    const auto by_price = order_request::trailing_stop_price("AAPL", 1, order_side::sell, 2.5).to_json();
    EXPECT_EQ(by_price["type"], "trailing_stop");
    EXPECT_EQ(by_price["trail_price"], "2.5");
    EXPECT_FALSE(by_price.contains("trail_percent"));

    const auto by_pct = order_request::trailing_stop_percent("AAPL", 1, order_side::sell, 1.5).to_json();
    EXPECT_EQ(by_pct["trail_percent"], "1.5");
    EXPECT_FALSE(by_pct.contains("trail_price"));
}

TEST(OrderRequest, FractionalQuantitiesKeepTheirPrecision) {
    // Fractional share quantities must survive as decimals, not become 0 or 1e-09.
    const auto j = order_request::market("AAPL", 0.001, order_side::buy).to_json();
    EXPECT_EQ(j["qty"], "0.001");

    const auto tiny = order_request::market("BTC/USD", 0.000000001, order_side::buy).to_json();
    EXPECT_EQ(tiny["qty"], "0.000000001");
}

TEST(OrderRequest, BracketOrderCarriesBothChildLegs) {
    const auto j = order_request::bracket(
        order_request::limit("AAPL", 10, order_side::buy, 100, time_in_force::gtc),
        110.0,   // take profit
        90.0     // stop loss
    ).to_json();

    EXPECT_EQ(j["order_class"], "bracket");
    EXPECT_EQ(j["limit_price"], "100");
    ASSERT_TRUE(j.contains("take_profit"));
    EXPECT_EQ(j["take_profit"]["limit_price"], "110");
    ASSERT_TRUE(j.contains("stop_loss"));
    EXPECT_EQ(j["stop_loss"]["stop_price"], "90");
    EXPECT_FALSE(j["stop_loss"].contains("limit_price"));
}

TEST(OrderRequest, BracketWithStopLimitChild) {
    const auto j = order_request::bracket(
        order_request::limit("AAPL", 10, order_side::buy, 100), 110.0, 90.0, 89.0).to_json();

    EXPECT_EQ(j["stop_loss"]["stop_price"], "90");
    EXPECT_EQ(j["stop_loss"]["limit_price"], "89");
}

TEST(OrderRequest, MultiLegOmitsTopLevelSymbolAndSide) {
    // mleg orders carry symbol/side per leg; sending a top-level symbol makes Alpaca reject them.
    const auto j = order_request::multi_leg({
        {"AAPL240628C00200000", 1, order_side::buy, position_intent::buy_to_open},
        {"AAPL240628C00210000", 1, order_side::sell, position_intent::sell_to_open},
    }, 1).to_json();

    EXPECT_EQ(j["order_class"], "mleg");
    EXPECT_FALSE(j.contains("symbol"));
    EXPECT_FALSE(j.contains("side"));
    EXPECT_EQ(j["qty"], "1");

    ASSERT_TRUE(j.contains("legs"));
    ASSERT_EQ(j["legs"].size(), 2u);
    EXPECT_EQ(j["legs"][0]["symbol"], "AAPL240628C00200000");
    EXPECT_EQ(j["legs"][0]["ratio_qty"], "1");
    EXPECT_EQ(j["legs"][0]["side"], "buy");
    EXPECT_EQ(j["legs"][0]["position_intent"], "buy_to_open");
    EXPECT_EQ(j["legs"][1]["side"], "sell");
}

TEST(OrderRequest, OptionalFlagsOnlyAppearWhenSet) {
    order_request r = order_request::limit("AAPL", 1, order_side::buy, 100);
    EXPECT_FALSE(r.to_json().contains("extended_hours"));
    EXPECT_FALSE(r.to_json().contains("client_order_id"));
    EXPECT_FALSE(r.to_json().contains("position_intent"));

    r.extended_hours = true;
    r.client_order_id = "my-id-123";
    r.position_intent = position_intent::buy_to_open;

    const auto j = r.to_json();
    EXPECT_EQ(j["extended_hours"], true);
    EXPECT_EQ(j["client_order_id"], "my-id-123");
    EXPECT_EQ(j["position_intent"], "buy_to_open");
}

TEST(ReplaceOrderRequest, SerialisesOnlySetFields) {
    replace_order_request r;
    EXPECT_TRUE(r.to_json().empty());

    r.qty = 5;
    r.limit_price = 101.25;

    const auto j = r.to_json();
    EXPECT_EQ(j.size(), 2u);
    EXPECT_EQ(j["qty"], "5");
    EXPECT_EQ(j["limit_price"], "101.25");
    EXPECT_FALSE(j.contains("stop_price"));
}

TEST(OrderQuery, BuildsFilters) {
    order_query q;
    q.status = order_status_filter::open;
    q.limit = 100;
    q.direction = sort_direction::desc;
    q.nested = true;
    q.symbols = {"AAPL", "MSFT"};

    EXPECT_EQ(q.build().str(),
              "?status=open&limit=100&direction=desc&nested=true&symbols=AAPL%2CMSFT");
}

TEST(OrderQuery, DefaultQueryIsEmpty) {
    EXPECT_TRUE(order_query{}.build().empty());
}

TEST(ClosePositionRequest, BuildsQuantitySelector) {
    close_position_request r;
    EXPECT_TRUE(r.build().empty());

    r.qty = 5;
    EXPECT_EQ(r.build().str(), "?qty=5");

    close_position_request pct;
    pct.percentage = 50;
    EXPECT_EQ(pct.build().str(), "?percentage=50");
}

TEST(WatchlistRequest, Serialises) {
    const watchlist_request r{"My List", {"AAPL", "MSFT"}};
    const auto j = r.to_json();

    EXPECT_EQ(j["name"], "My List");
    ASSERT_EQ(j["symbols"].size(), 2u);
    EXPECT_EQ(j["symbols"][0], "AAPL");
}

TEST(LocateRequest, Serialises) {
    locate_request r;
    r.symbol = "GME";
    r.requested_qty = 100;
    r.limit_price = 0.25;

    const auto j = r.to_json();
    EXPECT_EQ(j["symbol"], "GME");
    EXPECT_EQ(j["requested_qty"], 100);
    EXPECT_EQ(j["limit_price"], "0.25");
    EXPECT_FALSE(j.contains("all_or_none"));
}

}   // namespace alpaca::tests

Web Proxy Viewer  |  New URL  |  Original Page