Skip to content

Commit b1df80d

Browse files
authored
strategy: add order tracking and position queries (#60)
1 parent 6a77c25 commit b1df80d

File tree

9 files changed

+363
-65
lines changed

9 files changed

+363
-65
lines changed

docs/components/strategy/strategy.md

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,43 @@ protected:
9292

9393
## Signal Emission
9494

95+
All order methods return an `OrderId` for tracking:
96+
9597
```cpp
9698
protected:
9799
void emit(const Signal& signal);
98-
void emitMarketBuy(SymbolId symbol, Quantity qty);
99-
void emitMarketSell(SymbolId symbol, Quantity qty);
100-
void emitLimitBuy(SymbolId symbol, Price price, Quantity qty);
101-
void emitLimitSell(SymbolId symbol, Price price, Quantity qty);
100+
101+
// Returns OrderId for tracking
102+
OrderId emitMarketBuy(SymbolId symbol, Quantity qty);
103+
OrderId emitMarketSell(SymbolId symbol, Quantity qty);
104+
OrderId emitLimitBuy(SymbolId symbol, Price price, Quantity qty);
105+
OrderId emitLimitSell(SymbolId symbol, Price price, Quantity qty);
106+
102107
void emitCancel(OrderId orderId);
103108
void emitCancelAll(SymbolId symbol);
109+
void emitModify(OrderId orderId, Price newPrice, Quantity newQty);
110+
```
111+
112+
## Order and Position Tracking
113+
114+
Query order status and positions (requires `setOrderTracker` / `setPositionManager`):
115+
116+
```cpp
117+
protected:
118+
// Position queries
119+
Quantity position(SymbolId sym) const; // Net position for symbol
120+
Quantity position() const; // Primary symbol position
121+
122+
// Order status queries
123+
std::optional<OrderEventStatus> getOrderStatus(OrderId orderId) const;
124+
std::optional<OrderState> getOrder(OrderId orderId) const;
125+
```
126+
127+
Connect trackers:
128+
129+
```cpp
130+
strategy.setOrderTracker(&orderTracker);
131+
strategy.setPositionManager(&positionTracker);
104132
```
105133

106134
## Cross-Symbol Helpers
@@ -139,15 +167,28 @@ protected:
139167
auto ask = c.book.bestAsk();
140168
if (!bid || !ask) return;
141169
142-
// Check position
143-
if (c.isFlat() && shouldBuy(ev.trade.price))
170+
// Check position via tracker
171+
if (position().isZero() && shouldBuy(ev.trade.price))
144172
{
145-
emitMarketBuy(c.symbolId, Quantity::fromDouble(1.0));
173+
// Returns OrderId for tracking
174+
OrderId id = emitMarketBuy(c.symbolId, Quantity::fromDouble(1.0));
175+
_pendingOrder = id;
176+
}
177+
178+
// Check order status
179+
if (_pendingOrder)
180+
{
181+
auto status = getOrderStatus(*_pendingOrder);
182+
if (status && *status == OrderEventStatus::FILLED)
183+
{
184+
_pendingOrder = std::nullopt;
185+
}
146186
}
147187
}
148188
149189
private:
150190
bool _running{false};
191+
std::optional<OrderId> _pendingOrder;
151192
};
152193
```
153194

include/flox/log/atomic_logger.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ class AtomicLogger final : public ILogger
4848
void warn(std::string_view msg) override;
4949
void error(std::string_view msg) override;
5050

51+
void flush();
52+
5153
private:
5254
static constexpr size_t BUFFER_SIZE = 1024;
5355
static constexpr size_t MAX_MESSAGE_SIZE = 256;

include/flox/strategy/signal.h

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ enum class SignalType : uint8_t
1919
Market,
2020
Limit,
2121
Cancel,
22-
CancelAll
22+
CancelAll,
23+
Modify
2324
};
2425

2526
struct Signal
@@ -29,36 +30,43 @@ struct Signal
2930
Side side{};
3031
Price price{};
3132
Quantity quantity{};
32-
OrderId orderId{}; // for Cancel
33+
OrderId orderId{};
34+
Price newPrice{}; // for Modify
35+
Quantity newQuantity{}; // for Modify
3336

34-
static Signal marketBuy(SymbolId sym, Quantity qty)
37+
static Signal marketBuy(SymbolId sym, Quantity qty, OrderId id)
3538
{
36-
return Signal{SignalType::Market, sym, Side::BUY, Price{}, qty, 0};
39+
return Signal{SignalType::Market, sym, Side::BUY, Price{}, qty, id, Price{}, Quantity{}};
3740
}
3841

39-
static Signal marketSell(SymbolId sym, Quantity qty)
42+
static Signal marketSell(SymbolId sym, Quantity qty, OrderId id)
4043
{
41-
return Signal{SignalType::Market, sym, Side::SELL, Price{}, qty, 0};
44+
return Signal{SignalType::Market, sym, Side::SELL, Price{}, qty, id, Price{}, Quantity{}};
4245
}
4346

44-
static Signal limitBuy(SymbolId sym, Price px, Quantity qty)
47+
static Signal limitBuy(SymbolId sym, Price px, Quantity qty, OrderId id)
4548
{
46-
return Signal{SignalType::Limit, sym, Side::BUY, px, qty, 0};
49+
return Signal{SignalType::Limit, sym, Side::BUY, px, qty, id, Price{}, Quantity{}};
4750
}
4851

49-
static Signal limitSell(SymbolId sym, Price px, Quantity qty)
52+
static Signal limitSell(SymbolId sym, Price px, Quantity qty, OrderId id)
5053
{
51-
return Signal{SignalType::Limit, sym, Side::SELL, px, qty, 0};
54+
return Signal{SignalType::Limit, sym, Side::SELL, px, qty, id, Price{}, Quantity{}};
5255
}
5356

5457
static Signal cancel(OrderId id)
5558
{
56-
return Signal{SignalType::Cancel, 0, Side::BUY, Price{}, Quantity{}, id};
59+
return Signal{SignalType::Cancel, 0, Side::BUY, Price{}, Quantity{}, id, Price{}, Quantity{}};
5760
}
5861

5962
static Signal cancelAll(SymbolId sym)
6063
{
61-
return Signal{SignalType::CancelAll, sym, Side::BUY, Price{}, Quantity{}, 0};
64+
return Signal{SignalType::CancelAll, sym, Side::BUY, Price{}, Quantity{}, 0, Price{}, Quantity{}};
65+
}
66+
67+
static Signal modify(OrderId id, Price newPx, Quantity newQty)
68+
{
69+
return Signal{SignalType::Modify, 0, Side::BUY, Price{}, Quantity{}, id, newPx, newQty};
6270
}
6371
};
6472

include/flox/strategy/strategy.h

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@
1212
#include "flox/book/events/book_update_event.h"
1313
#include "flox/book/events/trade_event.h"
1414
#include "flox/engine/symbol_registry.h"
15+
#include "flox/execution/order_tracker.h"
16+
#include "flox/position/abstract_position_manager.h"
1517
#include "flox/strategy/abstract_signal_handler.h"
1618
#include "flox/strategy/abstract_strategy.h"
1719
#include "flox/strategy/signal.h"
1820
#include "flox/strategy/symbol_context.h"
1921
#include "flox/strategy/symbol_state_map.h"
2022

23+
#include <atomic>
24+
#include <optional>
2125
#include <set>
2226
#include <stdexcept>
2327
#include <vector>
@@ -52,6 +56,8 @@ class Strategy : public IStrategy
5256
SubscriberId id() const override { return _id; }
5357

5458
void setSignalHandler(ISignalHandler* handler) noexcept { _signalHandler = handler; }
59+
void setOrderTracker(OrderTracker* tracker) noexcept { _orderTracker = tracker; }
60+
void setPositionManager(IPositionManager* pm) noexcept { _positionManager = pm; }
5561

5662
void onTrade(const TradeEvent& ev) final
5763
{
@@ -99,6 +105,25 @@ class Strategy : public IStrategy
99105

100106
const std::vector<SymbolId>& symbols() const noexcept { return _symbols; }
101107

108+
// Position and order status queries
109+
Quantity position(SymbolId sym) const
110+
{
111+
return _positionManager ? _positionManager->getPosition(sym) : Quantity{};
112+
}
113+
114+
Quantity position() const { return position(_symbols[0]); }
115+
116+
std::optional<OrderEventStatus> getOrderStatus(OrderId orderId) const
117+
{
118+
return _orderTracker ? _orderTracker->getStatus(orderId) : std::nullopt;
119+
}
120+
121+
std::optional<OrderState> getOrder(OrderId orderId) const
122+
{
123+
return _orderTracker ? _orderTracker->get(orderId) : std::nullopt;
124+
}
125+
126+
// Signal emission
102127
void emit(const Signal& signal)
103128
{
104129
if (_signalHandler)
@@ -107,22 +132,53 @@ class Strategy : public IStrategy
107132
}
108133
}
109134

110-
void emitMarketBuy(SymbolId symbol, Quantity qty) { emit(Signal::marketBuy(symbol, qty)); }
111-
void emitMarketSell(SymbolId symbol, Quantity qty) { emit(Signal::marketSell(symbol, qty)); }
112-
void emitLimitBuy(SymbolId symbol, Price price, Quantity qty)
135+
OrderId emitMarketBuy(SymbolId symbol, Quantity qty)
136+
{
137+
OrderId id = nextOrderId();
138+
emit(Signal::marketBuy(symbol, qty, id));
139+
return id;
140+
}
141+
142+
OrderId emitMarketSell(SymbolId symbol, Quantity qty)
143+
{
144+
OrderId id = nextOrderId();
145+
emit(Signal::marketSell(symbol, qty, id));
146+
return id;
147+
}
148+
149+
OrderId emitLimitBuy(SymbolId symbol, Price price, Quantity qty)
113150
{
114-
emit(Signal::limitBuy(symbol, price, qty));
151+
OrderId id = nextOrderId();
152+
emit(Signal::limitBuy(symbol, price, qty, id));
153+
return id;
115154
}
116-
void emitLimitSell(SymbolId symbol, Price price, Quantity qty)
155+
156+
OrderId emitLimitSell(SymbolId symbol, Price price, Quantity qty)
117157
{
118-
emit(Signal::limitSell(symbol, price, qty));
158+
OrderId id = nextOrderId();
159+
emit(Signal::limitSell(symbol, price, qty, id));
160+
return id;
119161
}
162+
120163
void emitCancel(OrderId orderId) { emit(Signal::cancel(orderId)); }
121164
void emitCancelAll(SymbolId symbol) { emit(Signal::cancelAll(symbol)); }
122165

166+
void emitModify(OrderId orderId, Price newPrice, Quantity newQty)
167+
{
168+
emit(Signal::modify(orderId, newPrice, newQty));
169+
}
170+
123171
private:
172+
OrderId nextOrderId() noexcept
173+
{
174+
static std::atomic<OrderId> s_globalOrderId{1};
175+
return s_globalOrderId++;
176+
}
177+
124178
SubscriberId _id;
125179
ISignalHandler* _signalHandler{nullptr};
180+
OrderTracker* _orderTracker{nullptr};
181+
IPositionManager* _positionManager{nullptr};
126182
std::vector<SymbolId> _symbols;
127183
std::set<SymbolId> _symbolSet;
128184
mutable SymbolStateMap<SymbolContext> _contexts;

src/backtest/backtest_runner.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,21 @@ void BacktestRunner::onSignal(const Signal& signal)
142142
case SignalType::CancelAll:
143143
_executor.cancelAllOrders(signal.symbol);
144144
break;
145+
case SignalType::Modify:
146+
{
147+
Order newOrder{.id = _nextOrderId++,
148+
.price = signal.newPrice,
149+
.quantity = signal.newQuantity};
150+
_executor.replaceOrder(signal.orderId, newOrder);
151+
break;
152+
}
145153
}
146154
}
147155

148156
Order BacktestRunner::signalToOrder(const Signal& sig)
149157
{
150-
return Order{.id = _nextOrderId++,
158+
OrderId id = (sig.orderId != 0) ? sig.orderId : _nextOrderId++;
159+
return Order{.id = id,
151160
.side = sig.side,
152161
.price = sig.price,
153162
.quantity = sig.quantity,

src/log/atomic_logger.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ void AtomicLogger::info(std::string_view msg) { log(LogLevel::Info, msg); }
4646
void AtomicLogger::warn(std::string_view msg) { log(LogLevel::Warn, msg); }
4747
void AtomicLogger::error(std::string_view msg) { log(LogLevel::Error, msg); }
4848

49+
void AtomicLogger::flush()
50+
{
51+
_cv.notify_one();
52+
while (_readIndex.load(std::memory_order_acquire) < _writeIndex.load(std::memory_order_acquire))
53+
{
54+
std::this_thread::yield();
55+
}
56+
if (_file)
57+
{
58+
std::fflush(_file);
59+
}
60+
}
61+
4962
void AtomicLogger::log(LogLevel level, std::string_view msg)
5063
{
5164
if (level < _opts.levelThreshold)
@@ -86,17 +99,17 @@ void AtomicLogger::flushLoop()
8699
_cv.wait_for(lock, std::chrono::milliseconds(1));
87100
}
88101

89-
rotateIfNeeded();
90-
91102
while (_readIndex.load(std::memory_order_acquire) < _writeIndex.load(std::memory_order_acquire))
92103
{
104+
rotateIfNeeded();
93105
const size_t idx = _readIndex.fetch_add(1, std::memory_order_acq_rel) % BUFFER_SIZE;
94106
writeToOutput(_buffer[idx]);
95107
}
96108
}
97109

98110
while (_readIndex.load() < _writeIndex.load())
99111
{
112+
rotateIfNeeded();
100113
const size_t idx = _readIndex.fetch_add(1) % BUFFER_SIZE;
101114
writeToOutput(_buffer[idx]);
102115
}

0 commit comments

Comments
 (0)