Skip to content

Commit

Permalink
Release v0.0.2
Browse files Browse the repository at this point in the history
  • Loading branch information
janucaria committed Feb 24, 2025
2 parents be2da85 + 4cd91ba commit 0ff12ac
Show file tree
Hide file tree
Showing 38 changed files with 1,122 additions and 498 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.25)
project(pludux
VERSION 0.0.1
VERSION 0.0.2
HOMEPAGE_URL "https://github.com/janucaria/pludux"
LANGUAGES CXX
)
Expand Down
154 changes: 102 additions & 52 deletions apps/backtest/cli/sources/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <nlohmann/json.hpp>

#include <pludux/backtest.hpp>
#include <pludux/backtest/backtesting_summary.hpp>

auto main(int, const char**) -> int
{
Expand Down Expand Up @@ -41,62 +42,111 @@ auto main(int, const char**) -> int
auto open_trade = std::optional<pludux::backtest::TradeRecord>{};
auto closed_trades = std::vector<pludux::backtest::TradeRecord>{};

while(backtest.run(asset)) {
// Nothing to do here
while(backtest.should_run(asset)) {
backtest.run(asset);
}

auto result = backtest.backtesting_summary();

std::cout << "Asset: " << asset_file << std::endl;
std::cout << "Total profit: " << result.total_profit() << std::endl;
std::cout << "Total trades: " << result.closed_trades().size() << std::endl;
std::cout << "Average profit: " << result.average_win() << std::endl;
std::cout << "Average loss: " << result.average_loss() << std::endl;

std::cout << std::endl;
std::cout << "exit signal rate: " << result.exit_signal_rate() << std::endl;
std::cout << "Average exit signal: " << result.average_exit_signal()
<< std::endl;
std::cout << "exit signal ev: " << result.exit_signal_expected_value()
<< std::endl;

std::cout << "take profit rate: " << result.take_profit_rate() << std::endl;
std::cout << "Average take profit: " << result.average_take_profit()
<< std::endl;
std::cout << "take profit ev: " << result.take_profit_expected_value()
<< std::endl;

std::cout << "stop loss rate: " << result.stop_loss_rate() << std::endl;
std::cout << "Average stop loss: " << result.average_stop_loss() << std::endl;
std::cout << "stop loss ev: " << result.stop_loss_expected_value()
<< std::endl;
std::cout << std::endl;

std::cout << "Expected value: " << result.expected_value() << std::endl;

// get the duration in days
std::cout << "Total duration: " << result.total_duration() / (60 * 60 * 24)
<< " days" << std::endl;
std::cout << "Average duration: "
<< result.average_duration() / (60 * 60 * 24) << " days"
<< std::endl;

std::cout << "Win rate: " << result.win_rate() << std::endl;
std::cout << "Loss Rate: " << result.loss_rate() << std::endl;
std::cout << "BE Rate: " << result.break_even_rate() << std::endl;
std::cout << std::endl;
std::cout << std::endl;
auto& ostream = std::cout;

auto summary = pludux::backtest::BacktestingSummary{};
const auto& history = backtest.history();
for(int i = 0, ii = history.size(); i < ii; ++i) {
const auto& trade = history[i].trade_record();
const auto is_last_trade = i == ii - 1;

if(trade.has_value() &&
(trade->is_closed() || is_last_trade && trade->is_open())) {
summary.add_trade(*trade);
}
}

ostream << std::format("Risk per trade: {:.2f}\n", backtest.capital_risk());
ostream << std::format("Total profit: {:.2f}\n", summary.total_profit());
auto total_duration_days = std::chrono::duration_cast<std::chrono::days>(
std::chrono::seconds(summary.total_duration()))
.count();
ostream << std::format("Total duration: {} days\n", total_duration_days);
ostream << std::format("Total trades: {}\n", summary.total_trades());

ostream << std::format("Average profit: {:.2f}\n", summary.average_win());
ostream << std::format("Average loss: {:.2f}\n", -summary.average_loss());

auto average_duration_days =
std::chrono::duration_cast<std::chrono::days>(
std::chrono::seconds(summary.average_duration()))
.count();
ostream << std::format("Average duration: {} days\n", average_duration_days);

ostream << "\n\n";
ostream << "OPEN TRADE\n";
ostream << "----------\n";
ostream << std::format("Unrealized profit: {:.2f}\n",
summary.unrealized_profit());

auto ongoing_trade_duration_days =
std::chrono::duration_cast<std::chrono::days>(
std::chrono::seconds(summary.ongoing_trade_duration()))
.count();
ostream << std::format("Ongoing duration: {} days\n",
ongoing_trade_duration_days);

ostream << "\n\n";
ostream << "CLOSED TRADES\n";
ostream << "-------------\n";
ostream << std::format("Exit signal rate: {:.2f}%\n",
summary.exit_signal_rate() * 100);
ostream << std::format("Average exit signal: {:.2f}\n",
summary.average_exit_signal());
ostream << std::format("Exit signal EV: {:.2f}\n",
summary.exit_signal_expected_value());

ostream << std::format("Take profit rate: {:.2f}%\n",
summary.take_profit_rate() * 100);
ostream << std::format("Average take profit: {:.2f}\n",
summary.average_take_profit());
ostream << std::format("Take profit EV: {:.2f}\n",
summary.take_profit_expected_value());

ostream << std::format("Stop loss rate: {:.2f}%\n",
summary.stop_loss_rate() * 100);
ostream << std::format("Average stop loss: {:.2f}\n",
-summary.average_stop_loss());
ostream << std::format("Stop loss EV: {:.2f}\n",
summary.stop_loss_expected_value());
ostream << "\n";

ostream << std::format("Expected value (EV): {:.2f}\n",
summary.expected_value());
ostream << std::format("EV to risk rate: {:.2f}%\n",
summary.expected_value() / backtest.capital_risk() *
100);

ostream << std::format("Total closed trades: {}\n",
summary.closed_trades().size());
ostream << std::format("Win rate: {:.2f}%\n", summary.win_rate() * 100);
ostream << std::format("Loss rate: {:.2f}%\n", summary.loss_rate() * 100);
ostream << std::format("Break even rate: {:.2f}%\n",
summary.break_even_rate() * 100);
ostream << "\n\n";

// iterate through the trades
std::cout << "Trade: " << std::endl;
for(const pludux::backtest::TradeRecord& trade : result.closed_trades()) {
const auto entry_timestamp = trade.entry_timestamp();
const auto exit_timestamp = trade.exit_timestamp();
std::cout << "Entry date: " << std::ctime(&entry_timestamp) // NOLINT
<< "Exit date: " << std::ctime(&exit_timestamp) // NOLINT
<< "Reason: " << static_cast<int>(trade.status()) << std::endl
<< "Profit: " << trade.profit() << std::endl
<< std::endl;
std::cout << "Trades: " << std::endl;
const auto& backtest_history = backtest.history();
// auto is_in_trade = false;
for(int i = 0, ii = backtest_history.size(); i < ii; ++i) {
const auto& trade = backtest_history[i].trade_record();
const auto is_last_trade = i == ii - 1;

if(trade && trade->is_closed() || is_last_trade) {
const auto entry_timestamp = trade->entry_timestamp();
const auto exit_timestamp = trade->exit_timestamp();
std::cout << "Entry date: " << std::ctime(&entry_timestamp) // NOLINT
<< "Exit date: " << std::ctime(&exit_timestamp) // NOLINT
<< "Position size: " << trade->position_size() << std::endl
<< "Reason: " << static_cast<int>(trade->status()) << std::endl
<< "Profit: " << trade->profit() << std::endl
<< std::endl;
}
}

return 0;
Expand Down
2 changes: 1 addition & 1 deletion apps/backtest/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ else()
"-sUSE_GLFW=3"
"-sALLOW_MEMORY_GROWTH=1"
"-sSTACK_SIZE=128kB"
"-sEXPORTED_FUNCTIONS=[_main,stringToNewUTF8]"
"-sEXPORTED_FUNCTIONS=[_main,stringToNewUTF8,UTF8ToString]"
)

set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "index")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ void ChangeStrategyJsonFileAction::operator()(AppStateData& state) const

state.strategy_name = get_strategy_name();
state.backtest = backtest;
state.resource_changed = true;
}

auto ChangeStrategyJsonFileAction::get_strategy_name() const noexcept
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ void ChangeStrategyJsonStrAction::operator()(AppStateData& state) const

state.strategy_name = get_strategy_name();
state.backtest = std::move(backtest);
state.resource_changed = true;
}

auto ChangeStrategyJsonStrAction::get_strategy_name() const noexcept
Expand Down
5 changes: 4 additions & 1 deletion apps/backtest/gui/src/actions/load_asset_csv_file_action.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ void LoadAssetCsvFileAction::operator()(AppStateData& state) const

state.asset_data.emplace(quotes.begin(), quotes.end());
state.asset_name = get_asset_name();
state.resource_changed = true;

if(state.backtest.has_value()) {
state.backtest->reset();
}
}

auto LoadAssetCsvFileAction::get_asset_name() const noexcept -> std::string
Expand Down
5 changes: 4 additions & 1 deletion apps/backtest/gui/src/actions/load_asset_csv_str_action.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ void LoadAssetCsvStrAction::operator()(AppStateData& state) const

state.asset_data.emplace(quotes.begin(), quotes.end());
state.asset_name = get_asset_name();
state.resource_changed = true;

if(state.backtest.has_value()) {
state.backtest->reset();
}
}

auto LoadAssetCsvStrAction::get_asset_name() const noexcept
Expand Down
2 changes: 0 additions & 2 deletions apps/backtest/gui/src/app_state_data.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ struct AppStateData {

std::string asset_name{};

bool resource_changed{false};

std::queue<std::string> alert_messages{};

std::optional<Backtest> backtest{};
Expand Down
9 changes: 4 additions & 5 deletions apps/backtest/gui/src/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,12 @@ void Application::on_update()

try {
do {
if(!state_data_.backtest->run(asset_data)) {
if(!state_data_.backtest->should_run(asset_data)) {
break;
}

state_data_.backtest->run(asset_data);

current_time = std::chrono::high_resolution_clock::now();
time_diff = std::chrono::duration_cast<std::chrono::milliseconds>(
current_time - last_update_time)
Expand All @@ -93,7 +95,6 @@ void Application::on_update()
state_data_.backtest.reset();
state_data_.asset_data.reset();
state_data_.asset_name.clear();
state_data_.resource_changed = false;

const auto error_message = std::format("Error: {}", e.what());
state_data_.alert_messages.push(error_message);
Expand Down Expand Up @@ -123,9 +124,7 @@ void Application::on_update()

try {
dockspace_window_.render(app_state);

auto plot_data_window = PlotDataWindow{};
plot_data_window.render(app_state);
plot_data_window_.render(app_state);
auto backtesting_summary = BacktestSummaryWindow{};
backtesting_summary.render(app_state);
auto trade_journal = TradeJournalWindow{};
Expand Down
1 change: 1 addition & 0 deletions apps/backtest/gui/src/application.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class Application {
ImVec2 window_size_;

DockspaceWindow dockspace_window_;
PlotDataWindow plot_data_window_;

AppStateData state_data_;
std::queue<AppPolyAction> actions_;
Expand Down
18 changes: 15 additions & 3 deletions apps/backtest/gui/src/windows/backtesting_summary_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include <imgui.h>

#include <pludux/backtest/backtesting_summary.hpp>

#include "../app_state.hpp"

#include "./backtesting_summary_window.hpp"
Expand All @@ -28,9 +30,19 @@ void BacktestSummaryWindow::render(AppState& app_state)
ostream << "Asset: " << state.asset_name << std::endl;
ostream << std::endl;

if(backtest.has_value() &&
backtest->backtesting_summary().total_trades() > 0) {
const auto& summary = backtest->backtesting_summary();
if(backtest.has_value() && backtest->history().size() > 0) {
auto summary = backtest::BacktestingSummary{};

const auto& history = backtest->history();
for(int i = 0, ii = history.size(); i < ii; ++i) {
const auto& trade = history[i].trade_record();
const auto is_last_trade = i == ii - 1;

if(trade.has_value() && (trade->is_closed() || is_last_trade && trade->is_open())) {
summary.add_trade(*trade);
}
}

ostream << std::format("Risk per trade: {:.2f}\n",
backtest->capital_risk());
ostream << std::format("Total profit: {:.2f}\n", summary.total_profit());
Expand Down
21 changes: 19 additions & 2 deletions apps/backtest/gui/src/windows/dockspace_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,25 @@ void DockspaceWindow::render(AppState& app_state)
ImGui::Text("%s", "Pludux Backtest");
ImGui::Separator();
ImGui::Text("%s", std::format("Version: {}", PLUDUX_VERSION).c_str());
ImGui::Text(
"%s", std::format("Source Code: {}", PLUDUX_SOURCE_CODE_URL).c_str());
ImGui::Text("%s", "Source Code: ");
ImGui::SameLine();
ImGui::TextColored(ImVec4(0, 1, 1, 1), "%s", PLUDUX_SOURCE_CODE_URL);

#ifdef __EMSCRIPTEN__
EM_ASM(
{ document.body.style.cursor = $0 ? 'pointer' : 'default'; },
ImGui::IsItemHovered());
if(ImGui::IsItemClicked()) {
ImGui::CloseCurrentPopup();
EM_ASM(
{
var url = Module.UTF8ToString($0);
window.open(url, '_blank');
},
PLUDUX_SOURCE_CODE_URL);
}
#endif

ImGui::Separator();
ImGui::Text("%s", "This software is licensed under the MIT License.");
ImGui::Text("%s", "Copyright (c) 2025 Januar Andaria");
Expand Down
Loading

0 comments on commit 0ff12ac

Please sign in to comment.