1010#include < vector>
1111#include < memory>
1212#include < optional>
13+ #include < stdexcept>
1314#include < string>
1415#include < utility>
1516#include < iomanip>
1617#include < cstdio>
1718#include < ctime>
1819#include < boost/decimal.hpp>
1920#include " tradeManager.hpp"
21+ #include " exitRules.hpp"
2022#include " models/symbolScale.hpp"
23+ #include " strategies/strategy.hpp"
2124#include " strategies/randomStrategy.hpp"
2225
2326namespace {
2427
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-
6128// Walk every active trade, close any whose SL/TP has been hit on this
6229// tick. Two-phase to avoid invalidating the map iterator while erasing.
6330void reviewStopAndLimit (TradeManager& tradeManager, const PriceData& tick) {
@@ -67,7 +34,7 @@ void reviewStopAndLimit(TradeManager& tradeManager, const PriceData& tick) {
6734 std::vector<std::pair<std::string, boost::decimal::decimal64_t >> toClose;
6835 toClose.reserve (openTrades.size ());
6936 for (const auto & [id, trade] : openTrades) {
70- if (auto exitPrice = checkExit (trade, tick)) {
37+ if (auto exitPrice = trading::exit_rules:: checkExit (trade, tick)) {
7138 toClose.emplace_back (id, *exitPrice);
7239 }
7340 }
@@ -76,13 +43,24 @@ void reviewStopAndLimit(TradeManager& tradeManager, const PriceData& tick) {
7643 }
7744}
7845
46+ // Adding a new strategy means adding one branch here; nothing else in
47+ // Operations needs to know about the concrete type.
48+ std::unique_ptr<IStrategy>
49+ selectStrategy (const trading_definitions::Configuration& config) {
50+ const auto & name = config.STRATEGY .TRADING_VARIABLES .STRATEGY ;
51+ if (name == " RandomStrategy" ) {
52+ return std::make_unique<RandomStrategy>(config.STRATEGY );
53+ }
54+ throw std::runtime_error (" Unknown strategy: '" + name + " '" );
55+ }
56+
7957} // namespace
8058
8159void Operations::run (const std::vector<PriceData>& ticks,
8260 const trading_definitions::Configuration& config) {
8361
8462 auto tradeManager = std::make_unique<TradeManager>();
85- RandomStrategy strategy (config. STRATEGY );
63+ auto strategy = selectStrategy (config);
8664
8765 const auto & tradingVars = config.STRATEGY .TRADING_VARIABLES ;
8866
@@ -99,7 +77,7 @@ void Operations::run(const std::vector<PriceData>& ticks,
9977 // only open a trade if there is zero
10078 if (openTrades == 0 ) {
10179 // optional is false
102- if (auto signal = strategy. decide (tick)) {
80+ if (auto signal = strategy-> decide (tick)) {
10381 tradeManager->openTrade (tick,
10482 tradingVars.TRADING_SIZE ,
10583 *signal,
@@ -112,10 +90,56 @@ void Operations::run(const std::vector<PriceData>& ticks,
11290 // (e.g. trailing stops, partial closes). The default
11391 // RandomStrategy implementation is a no-op now that exits are
11492 // handled by reviewStopAndLimit above.
115- strategy. during (tickIndex, tick, *tradeManager);
93+ strategy-> during (tickIndex, tick, *tradeManager);
11694
11795 ++tickIndex;
11896 }
11997
12098 std::cout << " Final PnL: " << std::fixed << std::setprecision (2 ) << tradeManager->calculatePnl () << std::endl;
99+
100+ const auto & activeTrades = tradeManager->getActiveTrades ();
101+ const auto & closedTrades = tradeManager->getClosedTrades ();
102+
103+ const std::size_t openedCount = activeTrades.size () + closedTrades.size ();
104+ const std::size_t closedCount = closedTrades.size ();
105+
106+ std::size_t openedLong = 0 ;
107+ std::size_t openedShort = 0 ;
108+ for (const auto & [id, trade] : activeTrades) {
109+ if (trade.direction == Direction::LONG ) ++openedLong;
110+ else ++openedShort;
111+ }
112+
113+ std::size_t closedLong = 0 ;
114+ std::size_t closedShort = 0 ;
115+ std::size_t winners = 0 ;
116+ std::size_t losers = 0 ;
117+ std::size_t breakeven = 0 ;
118+ boost::decimal::decimal64_t pnlSum{0 };
119+ const boost::decimal::decimal64_t zero{0 };
120+ for (const auto & trade : closedTrades) {
121+ if (trade.direction == Direction::LONG ) ++closedLong;
122+ else ++closedShort;
123+ if (trade.pnl > zero) ++winners;
124+ else if (trade.pnl < zero) ++losers;
125+ else ++breakeven;
126+ pnlSum += trade.pnl ;
127+ }
128+ openedLong += closedLong;
129+ openedShort += closedShort;
130+
131+ std::cout << " Trades opened: " << openedCount
132+ << " (LONG: " << openedLong << " , SHORT: " << openedShort << " )" << std::endl;
133+ std::cout << " Trades closed: " << closedCount
134+ << " (LONG: " << closedLong << " , SHORT: " << closedShort << " )" << std::endl;
135+ std::cout << " Winners: " << winners
136+ << " Losers: " << losers
137+ << " Breakeven: " << breakeven << std::endl;
138+ if (closedCount == 0 ) {
139+ std::cout << " Average PnL per closed trade: n/a (0 closed)" << std::endl;
140+ } else {
141+ const auto avgPnl = pnlSum / boost::decimal::decimal64_t {static_cast <long long >(closedCount)};
142+ std::cout << " Average PnL per closed trade: "
143+ << std::fixed << std::setprecision (2 ) << avgPnl << std::endl;
144+ }
121145}
0 commit comments