|
9 | 9 | #include <iostream> |
10 | 10 | #include <vector> |
11 | 11 | #include <memory> |
| 12 | +#include <optional> |
12 | 13 | #include <string> |
| 14 | +#include <utility> |
13 | 15 | #include <iomanip> |
14 | 16 | #include <cstdio> |
15 | 17 | #include <ctime> |
16 | 18 | #include <boost/decimal.hpp> |
17 | 19 | #include "tradeManager.hpp" |
| 20 | +#include "models/symbolScale.hpp" |
18 | 21 | #include "strategies/randomStrategy.hpp" |
19 | 22 |
|
| 23 | +namespace { |
| 24 | + |
| 25 | +// Decide whether the current tick has hit a trade's stop-loss or |
| 26 | +// take-profit boundary. Returns the price at which the position would |
| 27 | +// close (bid for LONG exits, ask for SHORT exits) along with a flag |
| 28 | +// — `std::nullopt` means "no exit on this tick". |
| 29 | +// |
| 30 | +// Pip → price conversion uses the symbol's scaling factor: a 1.5-pip |
| 31 | +// distance on EURUSD (scale 10000) is 0.00015; on USDJPY (scale 100) |
| 32 | +// it's 0.015. Trades on unknown symbols (scale 0) are skipped — there |
| 33 | +// is no sensible pip distance to apply. |
| 34 | +std::optional<boost::decimal::decimal64_t> |
| 35 | +checkExit(const Trade& trade, const PriceData& tick) { |
| 36 | + if (trade.scalingFactor == 0) return std::nullopt; |
| 37 | + if (trade.stopDistancePips == 0 && |
| 38 | + trade.limitDistancePips == 0) { |
| 39 | + return std::nullopt; |
| 40 | + } |
| 41 | + |
| 42 | + const auto stopOffset = trade.stopDistancePips / trade.scalingFactor; |
| 43 | + const auto limitOffset = trade.limitDistancePips / trade.scalingFactor; |
| 44 | + |
| 45 | + if (trade.direction == Direction::LONG) { |
| 46 | + const auto stopPrice = trade.entryPrice - stopOffset; |
| 47 | + const auto limitPrice = trade.entryPrice + limitOffset; |
| 48 | + // Exit a long at the bid (the price the broker pays us). |
| 49 | + if (trade.stopDistancePips != 0 && tick.bid <= stopPrice) return tick.bid; |
| 50 | + if (trade.limitDistancePips != 0 && tick.bid >= limitPrice) return tick.bid; |
| 51 | + } else { |
| 52 | + const auto stopPrice = trade.entryPrice + stopOffset; |
| 53 | + const auto limitPrice = trade.entryPrice - limitOffset; |
| 54 | + // Exit a short at the ask (the price we pay to buy back). |
| 55 | + if (trade.stopDistancePips != 0 && tick.ask >= stopPrice) return tick.ask; |
| 56 | + if (trade.limitDistancePips != 0 && tick.ask <= limitPrice) return tick.ask; |
| 57 | + } |
| 58 | + return std::nullopt; |
| 59 | +} |
| 60 | + |
| 61 | +// Walk every active trade, close any whose SL/TP has been hit on this |
| 62 | +// tick. Two-phase to avoid invalidating the map iterator while erasing. |
| 63 | +void reviewStopAndLimit(TradeManager& tradeManager, const PriceData& tick) { |
| 64 | + const auto& openTrades = tradeManager.getActiveTrades(); |
| 65 | + if (openTrades.empty()) return; |
| 66 | + |
| 67 | + std::vector<std::pair<std::string, boost::decimal::decimal64_t>> toClose; |
| 68 | + toClose.reserve(openTrades.size()); |
| 69 | + for (const auto& [id, trade] : openTrades) { |
| 70 | + if (auto exitPrice = checkExit(trade, tick)) { |
| 71 | + toClose.emplace_back(id, *exitPrice); |
| 72 | + } |
| 73 | + } |
| 74 | + for (const auto& [id, exitPrice] : toClose) { |
| 75 | + tradeManager.closeTrade(id, exitPrice); |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +} // namespace |
| 80 | + |
20 | 81 | void Operations::run(const std::vector<PriceData>& ticks, |
21 | 82 | const trading_definitions::Configuration& config) { |
22 | 83 |
|
23 | | - // Create |
24 | | - auto tradeManager = new TradeManager(); |
| 84 | + auto tradeManager = std::make_unique<TradeManager>(); |
25 | 85 | RandomStrategy strategy(config.STRATEGY); |
26 | 86 |
|
| 87 | + const auto& tradingVars = config.STRATEGY.TRADING_VARIABLES; |
| 88 | + |
27 | 89 | std::size_t tickIndex = 0; |
28 | 90 | for (const auto& tick : ticks) { |
29 | 91 |
|
| 92 | + // Close any trade whose stop-loss or take-profit fired on this tick |
| 93 | + // before we consider opening a new one — otherwise an exit and an |
| 94 | + // entry could race within the same tick. |
| 95 | + reviewStopAndLimit(*tradeManager, tick); |
| 96 | + |
30 | 97 | size_t openTrades = tradeManager->reviewAccount(); |
31 | 98 |
|
32 | 99 | // only open a trade if there is zero |
33 | 100 | if (openTrades == 0) { |
34 | 101 | // optional is false |
35 | 102 | if (auto signal = strategy.decide(tick)) { |
36 | | - std::string tradeId = tradeManager->openTrade(tick, config.STRATEGY.TRADING_VARIABLES.TRADING_SIZE, *signal); |
37 | | - std::cout << "Opened trade: " << tradeId << std::endl; |
| 103 | + tradeManager->openTrade(tick, |
| 104 | + tradingVars.TRADING_SIZE, |
| 105 | + *signal, |
| 106 | + tradingVars.STOP_DISTANCE_IN_PIPS, |
| 107 | + tradingVars.LIMIT_DISTANCE_IN_PIPS); |
38 | 108 | } |
39 | 109 | } |
40 | 110 |
|
41 | | - // this would be a position manager review point |
42 | | - // randomly check account status every 100 ticks |
43 | | - if (openTrades > 0 && (std::rand() % 100) == 0) { // NOSONAR(cpp:S2245) experimentation only, not security-sensitive |
44 | | - std::cout << "Reviewing account at tick timestamp: " << tick.timestamp.time_since_epoch().count() << std::endl; |
45 | | - std::cout << "Number of open trades: " << openTrades << std::endl; |
46 | | - for (const auto& [id, trade] : tradeManager->getActiveTrades()) { |
47 | | - std::cout << "Trade ID: " << id |
48 | | - << " | Entry: " << trade.entryPrice |
49 | | - << " | Size: " << trade.size |
50 | | - << " | Direction: " << (trade.direction == Direction::LONG ? "LONG" : "SHORT") |
51 | | - << std::endl; |
52 | | - } |
53 | | - } |
54 | | - |
55 | | - // Strategy-driven management hook. The strategy itself decides |
56 | | - // whether/when to close positions; the random strategy currently |
57 | | - // closes every open trade with a small per-tick probability. |
58 | | - // Dereferencing `tradeManager` (a raw pointer) yields a reference |
59 | | - // — the parameter type is `TradeManager&`, so `*tradeManager` |
60 | | - // is what we hand in. |
| 111 | + // Strategy-driven management hook for non-SL/TP exit logic |
| 112 | + // (e.g. trailing stops, partial closes). The default |
| 113 | + // RandomStrategy implementation is a no-op now that exits are |
| 114 | + // handled by reviewStopAndLimit above. |
61 | 115 | strategy.during(tickIndex, tick, *tradeManager); |
62 | 116 |
|
63 | 117 | ++tickIndex; |
64 | 118 | } |
65 | 119 |
|
66 | 120 | std::cout << "Final PnL: " << std::fixed << std::setprecision(2) << tradeManager->calculatePnl() << std::endl; |
67 | | - |
68 | | - // bool closed = tradeManager->closeTrade(tradeId); |
69 | | - // std::cout << "Trade closed: " << (closed ? "yes" : "no") << std::endl; |
70 | 121 | } |
0 commit comments