diff --git a/.gitignore b/.gitignore index 9062042..99850c3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /*.idea */.idea *.pro.* +*.vscode #Build /build @@ -9,6 +10,7 @@ /cmake* Makefile *stash* +.env #Library @@ -19,3 +21,4 @@ Makefile +/.vscode/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 039fa70..f90cc12 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,13 +10,22 @@ message(status "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") if(MSVS) add_compile_options( /wd4127 /wd4250) else() -add_compile_options(-Wall -Wextra -Wno-unused-function ) #-Werror +add_compile_options(-Wall -Wextra -Wno-unused-function) #-Werror -Wpedantic +endif() + + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g") +elseif(CMAKE_BUILD_TYPE STREQUAL "Release") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -DNDEBUG") endif() add_subdirectory(lib/common) #find Package -find_package(Qt5 COMPONENTS Widgets Network Core Gui Charts REQUIRED) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network Core Gui Multimedia Charts) + find_package(Threads REQUIRED) #for QT @@ -25,12 +34,11 @@ set(CMAKE_AUTOMOC ON) #set(CMAKE_AUTORCC ON) #Standart -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_FLAGS -pthread) - #Version set(VERSION_MAJOR 0) set(VERSION_MINOR 2) @@ -51,30 +59,12 @@ set(CMAKE_AUTOUIC_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/common/) -#file( GLOB_RECURSE SOURCE_FILES *.cpp) -#file( GLOB_RECURSE HEADER_FILES *.h) +file( GLOB_RECURSE SOURCE_FILES common/*.cpp Model/acsmodel.cpp main.cpp) +file( GLOB_RECURSE HEADER_FILES common/*.h Model/acsmodel.h) + #file( GLOB_RECURSE MODEL_FILES Model/*.cpp) #file( GLOB_RECURSE API_FILES Model/*.h) -SET( SOURCE_FILES - Model/acsmodel.cpp - common/mainwindow.cpp - common/avgcoststocks.cpp - common/chart.cpp - common/csvmodel.cpp - common/tradelog.cpp - main.cpp) -SET( HEADER_FILES - Model/acsmodel.h - common/mainwindow.h - common/avgcoststocks.h - common/chart.h - common/threadpool.h - common/timer.h - common/csvreader.h - common/csvmodel.h - common/tradelog.h) - #file(GLOB CLIENT_FILE *.cpp *.h) set(UIFORM common/mainwindow.ui) @@ -83,12 +73,10 @@ set(UIFORM common/mainwindow.ui) QT5_WRAP_UI(UI_HEADERS ${UIFORM} ) add_executable(${PROJECT_NAME} ${HEADER_FILES} ${SOURCE_FILES} ${UI_HEADERS}) -target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets Qt5::Network Qt5::Core Qt5::Gui Qt5::Charts Threads::Threads stdc++fs lib::common) +target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Charts Threads::Threads stdc++fs lib::common) install (TARGETS ${PROJECT_NAME} DESTINATION bin) -#SET(CPACK_PACKAGE_VERSION "1.0.2") - set(CPACK_SYSTEM_NAME ${SO_VERSION}) #Generate debian package with cpack [or alternative make package] diff --git a/README.md b/README.md index 2e66495..32cffd9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## Service Stock Markets The service for calculate some parameters stock market ## Requirements -
-C++17 compiler support +
-C++20 compiler support
-Cmake
-QT
-Doxygen @@ -12,19 +12,19 @@ git clone https://github.com/silverstringer/ssm.git mkdir build && cd build cmake .. - -make -j8 +make -j12 ## Install make install ## Run - ## Usage 1) Simple Dollar Cost Average Digital Asset( on bull market) 2) Different percentage on month 3) Calculate average price of asset +4) Futures calc +5) Invest calc *Advanced. Save resultDCA on *.csv file, build graph diff --git a/common/chart.cpp b/common/chart.cpp index 8b7b7f1..9067611 100755 --- a/common/chart.cpp +++ b/common/chart.cpp @@ -76,8 +76,6 @@ void Graph::buildBarChartDiffDepo(const std::map &data) for (auto&& [key, value] : data) { set[i] = new QBarSet(key); *set[i] <append(set[i]); i++; } @@ -98,7 +96,7 @@ void Graph::buildBarChartDiffDepo(const std::map &data) series->attachAxis(axisX); QValueAxis *axisY = new QValueAxis(); - auto param = min_max_range_element(data); + [[maybe_unused]]auto param = min_max_range_element(data); // axisY->setRange(0, max->second + min->second); // axisY->setTickCount(data.size() * 1.5); @@ -117,8 +115,14 @@ void Graph::buildLineChart(const std::map &data) { QLineSeries *series = new QLineSeries(); + QVector points; + for (auto&& [key, value] : data) - series->append(key,value); + points.append(QPointF(key, value)); + series->replace(points); + + // for (auto&& [key, value] : data) + // series->append(key,value); QChart *chart = new QChart(); chart->legend()->hide(); diff --git a/common/mainwindow.cpp b/common/mainwindow.cpp index 1c58164..7155024 100644 --- a/common/mainwindow.cpp +++ b/common/mainwindow.cpp @@ -12,6 +12,11 @@ #include #include +#include +#include +#include + + #include @@ -70,8 +75,66 @@ MainWindow::MainWindow(QWidget *parent) : ui->spnGoalPrice->setValue(ui->spnFirstPrice->value()); }); + connect(ui->btnFuturesCalc, &QPushButton::clicked, []() { + + try { + QtConcurrent::run([]() { + FuturesCalculator calc; + calc.run(); + }); + } + catch(std::exception& e){ + qDebug() <btnInvestmentCalc, &QPushButton::clicked, []() { + + try { + QtConcurrent::run([]() { + + int currentLots; + double currentAvgPrice, targetAvgPrice, budget; + + std::cout << "\n Введите текущее количество лотов: "; + std::cin >> currentLots; + std::cout << "Введите текущую среднюю цену (руб.): "; + std::cin >> currentAvgPrice; + std::cout << "Введите целевую среднюю цену (руб.): "; + std::cin >> targetAvgPrice; + std::cout << "Введите бюджет (руб., или 0 для неограниченного): "; + std::cin >> budget; + budget = (budget <= 0) ? std::numeric_limits::max() : budget; + + InvestmentCalculator::compareStrategies(currentLots, currentAvgPrice, targetAvgPrice, budget); + + }); + } + catch(std::exception& e){ + qDebug() <btnCalculateDCA, &QPushButton::clicked, [this]() { - calculateDCA(); + + QFuture future = QtConcurrent::run(this, &MainWindow::calculateDCA); + QFutureWatcher * watcher_calculate = new QFutureWatcher (this); + connect(watcher_calculate, &QFutureWatcher::finished,this,[&](){ + emit calcDone(); + ui->btnCalculateDCA->setText("calculate"); + +// ui->spnGoalPrice->setStyleSheet("QDoubleSpinBox {color : red;}"); +// +// ui->spnRangeAsset->setStyleSheet("QDoubleSpinBox {color : green; }"); +// ui->spnRangePrice->setStyleSheet("QDoubleSpinBox {color : green; }"); +// +// ui->lblRangeSum->setStyleSheet("QLabel {color : green; }"); + }); + watcher_calculate->setFuture(future); + ui->btnCalculateDCA->setText("calculate..."); + + +// calculateDCA(); }); } @@ -263,26 +326,7 @@ void MainWindow::calculate(dca &res, int max_range) { } //set GUI - ui->lblTotalFirstSum->setText(QString::number(res.assets * res.price)); - - for (const auto value:res.goal_range) { - auto range_assets = value.first; - auto range_price = value.second; - - ui->spnRangeAsset->setValue(range_assets); - ui->spnRangePrice->setValue(range_price); - - ui->spnTotalAsset->setValue(range_assets + res.assets); - ui->lblTotalSum->setText(QString::number(range_price * range_assets + res.assets * res.price)); - - ui->spnGoalPrice->setStyleSheet("QDoubleSpinBox {color : red;}"); - - ui->spnRangeAsset->setStyleSheet("QDoubleSpinBox {color : green; }"); - ui->spnRangePrice->setStyleSheet("QDoubleSpinBox {color : green; }"); - - ui->lblRangeSum->setText(QString::number(range_assets * range_price)); - ui->lblRangeSum->setStyleSheet("QLabel {color : green; }"); - } + setGuiCalculateDca(res); // emit calcDone(); } @@ -434,12 +478,12 @@ void MainWindow::getDiffPercentDetails() { } std::unique_ptr graph = std::make_unique(); - graph->setType(Graph::TypeChart::BarChart); + graph->setType(Graph::TypeChart::LineChart); graph->setTitleGraph("Diff Percentage", "Month", "Depo"); - graph->buildBarChart(data_convert); +// graph->buildBarChart(data_convert); // graph->buildBarChartDiffDepo(data_convert); -// graph->buildLineChart(data_convert1); + graph->buildLineChart(data_convert1); //View csv data from file char delim = ';'; @@ -520,4 +564,26 @@ void MainWindow::setBackgroundMainWindow() { } +void MainWindow::setGuiCalculateDca(dca &res) { + + ui->lblTotalFirstSum->setText(QString::number(res.assets * res.price)); + + for (const auto value:res.goal_range) { + auto range_assets = value.first; + auto range_price = value.second; + + ui->spnRangeAsset->setValue(range_assets); + ui->spnRangePrice->setValue(range_price); + ui->spnTotalAsset->setValue(range_assets + res.assets); + ui->lblTotalSum->setText(QString::number(range_price * range_assets + res.assets * res.price)); +// +// ui->spnGoalPrice->setStyleSheet("QDoubleSpinBox {color : red;}"); +// +// ui->spnRangeAsset->setStyleSheet("QDoubleSpinBox {color : green; }"); +// ui->spnRangePrice->setStyleSheet("QDoubleSpinBox {color : green; }"); +// + ui->lblRangeSum->setText(QString::number(range_assets * range_price)); +// ui->lblRangeSum->setStyleSheet("QLabel {color : green; }"); + } +} diff --git a/common/mainwindow.h b/common/mainwindow.h index 01d208e..6ee2b6c 100644 --- a/common/mainwindow.h +++ b/common/mainwindow.h @@ -10,6 +10,10 @@ #include "chart.h" #include "csvreader.h" #include "csvmodel.h" +#include "futurescalculator.h" +#include "investmentcalculator.h" + +#include "storage.h" #include #include @@ -51,6 +55,7 @@ class MainWindow : public QMainWindow void calculateDiffPercentage(); void setHotKey(); void setBackgroundMainWindow(); + void setGuiCalculateDca(dca &res); private: bool isMoonTheme { false }; std::map resultDiffPercent; diff --git a/common/mainwindow.ui b/common/mainwindow.ui index e6af393..d47a11c 100755 --- a/common/mainwindow.ui +++ b/common/mainwindow.ui @@ -6,7 +6,7 @@ 0 0 - 560 + 582 410 @@ -478,6 +478,32 @@ Trade log + + + + 390 + 340 + 91 + 31 + + + + FuturesCalc + + + + + + 490 + 300 + 81 + 71 + + + + Invest Calc + + diff --git a/common/tradelog.cpp b/common/tradelog.cpp index 43e47be..7d59766 100755 --- a/common/tradelog.cpp +++ b/common/tradelog.cpp @@ -1 +1,10 @@ #include "tradelog.h" + +AnalyzeTradeLogSummary::AnalyzeTradeLogSummary( [[maybe_unused]]TradeLogSummary &log) { + +} + +void AnalyzeTradeLogSummary::calculate() { + + +} diff --git a/common/tradelog.h b/common/tradelog.h index c6903e6..8ca4f58 100755 --- a/common/tradelog.h +++ b/common/tradelog.h @@ -3,27 +3,35 @@ #include #include +#include #include +#pragma pop(1) + /** * @brief файл журнала сделок + * @future возможно отдельный модуль + * @note for example; + * @todo type data exchange; * @note usage in future struct from future - * //скорее всего выгрузка из таблицы с id trader [ или id можно внести в общий конфиг] + * @note скорее всего выгрузка из таблицы с id trader [ или id можно внести в общий конфиг] + * @note in [.csv -> grid] */ class TradeLog final { using string = std::string; public: - explicit TradeLog(ushort uid_trader) {}; + explicit TradeLog([[maybe_unused]] ushort uid_trader) {}; protected: - struct Config { - string timestampOpen; //dd-mm-yy + struct Record { //order + string timestampOpen; //dd-mm-yy [hh::mm::ss] string timestampClose; //dd-mm-yy string orderType; - string typePosition; //[long, short] + string typePosition; //[long, short,hold] string ticker; - string account_balance; - string entry_price; + string Market; //idmarket + double account_balance; + double entry_price; long long amount; string SL; //stop loss price string TP; //take profit price; @@ -31,20 +39,30 @@ class TradeLog final { string actual_exit_price; string trade_cost;//издержки сделки, стоимость сделки, [taxes, transaction_fee] double percentage_account_risked; //risk_level; - string closed_position_PL; // [profit - loss] - string account_change_in_percentage; - string pattern_usage; // why i entered deal + double closed_position_PL; // [profit - loss] + double account_change_in_symbol; + double account_change_in_percentage; + string pattern_usage_enter; // why i entered deal [pattern, formation , news ] + string pattern_usage_exit; // why i exit deal [line breakout month, by stop etc.] + string status; //for show status order [ win, loss, neutral] + string mistakes; //optional [fomo, overtrade] }; private: ushort uid_trader; }; - //PNL, profit and loss; - class AnalyzeTradeLogSummary final { + /** + * @brief PNL, profit and loss; + * + */ + + class AnalyzeTradeLogSummary final { using string_view = std::string_view; + using TradeLogSummary = std::vector; public: - explicit AnalyzeTradeLogSummary(); + explicit AnalyzeTradeLogSummary(TradeLogSummary &log); + void calculate(); protected: ushort total_number_winning_trades; ushort total_number_losing_trades; @@ -62,4 +80,7 @@ class TradeLog final { // usage on Month analysis, on Summary All; }; + +#pragma push(pop) + #endif //SSM_TRADELOG_H diff --git a/lib/common/CMakeLists.txt b/lib/common/CMakeLists.txt index 31c6c12..3488f5c 100644 --- a/lib/common/CMakeLists.txt +++ b/lib/common/CMakeLists.txt @@ -5,12 +5,29 @@ message("Lib for Service Stock Market") message( "Project name:" ${PROJECT_NAME}) set(CMAKE_DEFAULT_BUILD_TYPE "Release") -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_FLAGS -pthread) + +cmake_policy(SET CMP0071 NEW) + +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-function") +endif() + + +#optimization flags +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g") +elseif(CMAKE_BUILD_TYPE STREQUAL "Release") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -DNDEBUG") +endif() + add_compile_options(-Wall -Wextra -Wno-unused-function) -add_library(${PROJECT_NAME} SHARED dca.cpp grid.h logger.h future.h) +add_library(${PROJECT_NAME} SHARED dca.cpp grid.h logger.h future.h futurescalculator.cpp investmentcalculator.h storage.h) add_library(lib::common ALIAS common) target_include_directories( ${PROJECT_NAME} diff --git a/lib/common/dca.cpp b/lib/common/dca.cpp index 6e5d325..421642c 100644 --- a/lib/common/dca.cpp +++ b/lib/common/dca.cpp @@ -32,7 +32,7 @@ void DiffPercent::convertPercentage() { if(isBoundRange(i * 100 , (i + 1) *100 , (int)m_percent)) { - Coeff = 1 + m_percent /100; + Coeff = 1 + m_percent / 100; } } }; @@ -41,13 +41,13 @@ void DiffPercent::convertPercentage() if(isBoundRange(0, 99, static_cast(m_percent))) - m_percent = 1 + m_percent/100; + m_percent = 1 + m_percent / 100; if(m_percent == 100) - m_percent = 2; + m_percent = 2; if(Coeff != 0 ) - m_percent= Coeff; + m_percent= Coeff; -// assert(m_percent >100 && "Error!Percent can't larger 100"); +// assert(percent >100 && "Error!Percent can't larger 100"); } diff --git a/lib/common/dca.h b/lib/common/dca.h index 61b3692..03e8ec8 100644 --- a/lib/common/dca.h +++ b/lib/common/dca.h @@ -47,9 +47,9 @@ namespace Strategy { //namespace container{} struct dca { - double assets{0}; + double assets{0}; // double price{0}; - double goal_price{0}; + double goal_price{0}; //target std::map goal_range; dca() = default; @@ -64,15 +64,14 @@ namespace Strategy { * @note calculate for month */ struct DiffPercent { - DiffPercent(double depo, double percent, double counterMonth, double refill = 0.00L); double deposit; double m_percent; double step; double m_refill; // add advanced asset + DiffPercent(double depo, double percent, double counterMonth, double refill = 0.00L); void convertPercentage(); - void clear(); }; @@ -80,7 +79,7 @@ namespace Strategy { double static calculateDiffPercent(DiffPercent &dps) { dps.convertPercentage(); double result = dps.deposit * pow(dps.m_percent, dps.step - 1); -// double resultDCA = dps.deposit * pow(1 + dps.m_percent / dps.step, dps.step); +// double resultDCA = dps.deposit * pow(1 + dps.percent / dps.step, dps.step); return result; } @@ -89,7 +88,7 @@ namespace Strategy { double static calculateDiffPercentAddStock(DiffPercent &dps) { dps.convertPercentage(); double result = (dps.deposit + dps.m_refill) * pow(dps.m_percent, dps.step - 1); -// double refill = dps.m_refill * pow(dps.m_percent, dps.step - 1); +// double refill = dps.refill * pow(dps.percent, dps.step - 1); // std::cout<<"Refill calc: " << refill <<"\n"; // return (double)(resultDCA + refill); @@ -99,6 +98,10 @@ namespace Strategy { std::map static calculateDiffPercentPeriod(const double depo, const double percentage, const int period) { + if (period <= 0) { + std::map empty_map; + return empty_map; + } std::map resultDiffPercent; @@ -129,6 +132,13 @@ namespace Strategy { return resultDiffPercent; } + /** + * + * @param dca res + * @param min_range + * @param max_range + * @note calculate only exactly result by range [slowly] + */ void static calculateDCA(dca &res, int min_range = 0, int max_range = 0) { auto first_total_sum = res.assets * res.price; diff --git a/lib/common/future.h b/lib/common/future.h index f2e3dc6..716e577 100644 --- a/lib/common/future.h +++ b/lib/common/future.h @@ -43,10 +43,10 @@ // calculate SMA usage history data [json, etc.] and period {example 20, 50, 100 } // usage price closing - float sma(std::vector price_closing, int period = 0) { - if (period > price_closing.size() || period < 0) + float sma(std::vector &price_closing, int period = 0) { + if (static_cast(period) > price_closing.size() || period < 0) return 0L; - if (period != price_closing.size() && period != 0) + if (static_cast(period) != price_closing.size() && period != 0) price_closing.resize(period); return (float) std::accumulate(price_closing.begin(), price_closing.end(), 0) / price_closing.size(); } diff --git a/lib/common/futurescalculator.cpp b/lib/common/futurescalculator.cpp new file mode 100644 index 0000000..fb4752e --- /dev/null +++ b/lib/common/futurescalculator.cpp @@ -0,0 +1,161 @@ +#include "futurescalculator.h" + + + +FuturesCalculator::FuturesCalculator() noexcept { + m_instrumentDb = { + {"SBERF", InstrumentParams(100, 0.01, 1.0)}, // Фьючерс на Сбербанк + {"GAZPF", InstrumentParams(100, 0.01, 1.0)}, // Фьючерс на Газпром + {"BRF4", InstrumentParams(10, 0.1, 10.0)}, // Фьючерс на нефть Brent + {"RTSF", InstrumentParams(1, 10, 1.0)}, // Фьючерс на индекс РТС + {"SIH4", InstrumentParams(1000, 0.01, 1.0)}, // Фьючерс на серебро + {"USDRUBF", InstrumentParams(1000, 0.01, 10)},// Фьючерс на доллар однодневный + {"USDRUBFK", InstrumentParams(1000, 1, 1)}, // Фьючерс на доллар квартальный + {"MTLRFK", InstrumentParams(100, 1, 1)}, // Фьючерс на мечел квартальный + {"SBRFK", InstrumentParams(100, 1, 1)} // Фьючерс на сбербанк квартальный + }; +} + +FuturesCalculator::InstrumentParams FuturesCalculator::getInstrumentParams(const std::string& ticker) +{ + + auto it = m_instrumentDb.find(ticker); + if (it != m_instrumentDb.end()) { + printInstrumentParams(ticker); + return it->second; + } + + cout << "Tool not found. Please enter parameters manually:\n"; + int lot_size; + double price_step, step_value; + + cout << "Size of lots: "; + cin >> lot_size; + cout << "Price step: "; + cin >> price_step; + cout << "Cost of price step: "; + cin >> step_value; + + return InstrumentParams(lot_size, price_step, step_value); +} + + + +double FuturesCalculator::calculatePnl(const Trade& trade) const { + const double price_diff = trade.is_long + ? trade.close_price - trade.open_price + : trade.open_price - trade.close_price; + + const double steps = price_diff / trade.params.price_step; + const double gross_profit = steps * trade.params.step_value * trade.quantity; + const double total_fees = (trade.exchange_fee + trade.broker_fee) * 2 * trade.quantity; + + return gross_profit - total_fees; +} + +FuturesCalculator::Trade FuturesCalculator::inputTradeData() { + std::string ticker; + cout << "\nВведите тикер фьючерса (например, SBERF): "; + cin >> ticker; + + InstrumentParams params = getInstrumentParams(ticker); + + char direction; + cout << "Тип позиции (L - лонг, S - шорт): "; + cin >> direction; + + double open_price, close_price; + cout << "Цена открытия: "; + cin >> open_price; + cout << "Цена закрытия: "; + cin >> close_price; + + int quantity; + cout << "Количество контрактов: "; + cin >> quantity; + + double exchange_fee, broker_fee; + cout << "Комиссия биржи за контракт: "; + cin >> exchange_fee; + cout << "Комиссия брокера за контракт: "; + cin >> broker_fee; + + return Trade(ticker, toupper(direction) == 'L', open_price, close_price, + quantity, exchange_fee, broker_fee, params); +} + + + +void FuturesCalculator::printInstrumentParams(const std::string& ticker) const { + + const auto& params = m_instrumentDb.at(ticker); + cout << "Tool parameters:\n"; + cout << " - Size of lots: " << params.lot_size << "\n"; + cout << " - Step of price: " << params.price_step << "\n"; + cout << " - Cost per step: " << params.step_value << " ₽\n"; +} + + +void FuturesCalculator::printTradeReport(const Trade& trade) const { + + + const double profit = calculatePnl(trade); + + cout << "\n=== Transaction Report ===\n"; + cout << "Тикер: " << trade.ticker << "\n"; + cout << "Позиция: " << (trade.is_long ? "Лонг" : "Шорт") << "\n"; + cout << "Количество контрактов: " << trade.quantity << "\n"; + cout << std::fixed << std::setprecision(2); + cout << "Цена открытия: " << trade.open_price << " ₽\n"; + cout << "Цена закрытия: " << trade.close_price << " ₽\n"; + cout << "Размер лота: " << trade.params.lot_size << "\n"; + cout << "Шаг цены: " << trade.params.price_step << "\n"; + cout << "Стоимость шага: " << trade.params.step_value << " ₽\n"; + cout << "Комиссия биржи: " << trade.exchange_fee << " ₽ за контракт\n"; + cout << "Комиссия брокера: " << trade.broker_fee << " ₽ за контракт\n"; + cout << "-------------------------\n"; + cout << "Результат: " << (profit >= 0 ? "Прибыль" : "Убыток") + << ": " << std::abs(profit) << " ₽\n"; + cout << "=========================\n\n"; +} + +void FuturesCalculator::printSummaryReport() const { + double total_profit = 0.0; + cout << "\n=== Final report ===\n"; + + for (const auto& trade : m_trades) { + const double profit = calculatePnl(trade); + total_profit += profit; + cout << trade.ticker << ": " << (profit >= 0 ? "+" : "") + << profit << " ₽\n"; + } + + cout << "-----------------------\n"; + cout << "Common result: " << (total_profit >= 0 ? "+" : "") + << total_profit << " ₽\n"; +} + +void FuturesCalculator::run() { + cout << " \nCalculate pnl by futures\n"; + cout << "----------------------------------------\n"; + + while (true) { + Trade trade = inputTradeData(); + m_trades.push_back(trade); + printTradeReport(trade); + + if (!askToContinue()) break; + } + + printSummaryReport(); +} + + +bool FuturesCalculator::askToContinue() const { + char choice; + cout << "Add another deal? (Y/N): "; + cin >> choice; + return toupper(choice) == 'Y'; +} + + diff --git a/lib/common/futurescalculator.h b/lib/common/futurescalculator.h new file mode 100644 index 0000000..9a60b7f --- /dev/null +++ b/lib/common/futurescalculator.h @@ -0,0 +1,60 @@ +#ifndef SSM_FUTURESCALCULATOR_H +#define SSM_FUTURESCALCULATOR_H + +#include +#include +#include +#include +#include + +using namespace std; +using std::cout; +using std::cin; + +class FuturesCalculator final { +public: + FuturesCalculator() noexcept; + + struct InstrumentParams { + int lot_size; // Размер лота (количество базового актива) + double price_step; // Минимальный шаг цены + double step_value; // Стоимость одного шага цены в рублях + + InstrumentParams(int lot, double step, double value) + : lot_size(lot), price_step(step), step_value(value) {} + }; + + struct Trade { + std::string ticker; + bool is_long; // false for short + double open_price; + double close_price; + int quantity; + double exchange_fee; + double broker_fee; + InstrumentParams params; + + Trade(const std::string &ticker, bool is_long, double open, double close, + int qty, double exch_fee, double brk_fee, const InstrumentParams &ip) + : ticker(ticker), is_long(is_long), open_price(open), close_price(close), + quantity(qty), exchange_fee(exch_fee), broker_fee(brk_fee), + params(ip) {} + }; + + void run(); + +private: + Trade inputTradeData(); + InstrumentParams getInstrumentParams(const std::string &ticker); + double calculatePnl(const Trade &trade) const; + void printInstrumentParams(const std::string &ticker) const; + void printTradeReport(const Trade &trade) const; + void printSummaryReport() const; + bool askToContinue() const; + + std::map m_instrumentDb; + std::vector m_trades; + +}; + +#endif //SSM_FUTURESCALCULATOR_H diff --git a/lib/common/investmentcalculator.h b/lib/common/investmentcalculator.h new file mode 100644 index 0000000..48da222 --- /dev/null +++ b/lib/common/investmentcalculator.h @@ -0,0 +1,201 @@ +#ifndef SSM_INVESTMENTCALCULATOR_H +#define SSM_INVESTMENTCALCULATOR_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define MINSTEP 10 +#define MAXSTEP 20 + +class InvestmentStrategy { +private: + int currentLots; + double currentAvgPrice; + double targetAvgPrice; + double availableBudget; + double priceStep; + double maxPurchasePercent; + bool autoStepEnabled; + +public: + InvestmentStrategy(int lots, double avgPrice, double targetPrice, + double budget = std::numeric_limits::max(), + double step = 0.0, double maxPercent = 0.3) + : currentLots(lots), currentAvgPrice(avgPrice), targetAvgPrice(targetPrice), + availableBudget(budget), priceStep(step), maxPurchasePercent(maxPercent) { + autoStepEnabled = (priceStep <= 0.0); + if (autoStepEnabled) { + priceStep = std::max(0.01, (currentAvgPrice - targetAvgPrice) * 0.05); + } + } + + struct Result { + std::vector> purchases; + double finalAvgPrice; + double totalCost; + int totalLots; + double priceStepUsed; + }; + + Result calculate() const { + Result result; + result.priceStepUsed = priceStep; + double remainingBudget = availableBudget; + double tempLots = currentLots; + double tempAvgPrice = currentAvgPrice; + double currentMarketPrice = currentAvgPrice * 0.95; + bool hasBudget = (availableBudget < std::numeric_limits::max()); + + int maxSteps = autoStepEnabled ? MINSTEP : static_cast((currentAvgPrice - targetAvgPrice) / priceStep) + 1; + maxSteps = std::min(maxSteps, MAXSTEP); + + for (int step = 0; step < maxSteps; ++step) { + if (tempAvgPrice <= targetAvgPrice + 0.001) break; + if (hasBudget && remainingBudget <= 0) break; + if (currentMarketPrice <= 0) break; + + auto [lotsToBuy, cost] = calculateOptimalLots(tempLots, tempAvgPrice, + currentMarketPrice, remainingBudget); + + if (lotsToBuy > 0) { + result.purchases.emplace_back(currentMarketPrice, lotsToBuy, cost); + remainingBudget -= cost; + tempAvgPrice = (tempLots * tempAvgPrice + lotsToBuy * currentMarketPrice) / (tempLots + lotsToBuy); + tempLots += lotsToBuy; + } + + currentMarketPrice -= priceStep; + if (currentMarketPrice < targetAvgPrice * 0.9) { + currentMarketPrice = targetAvgPrice * 0.9; + } + } + + result.finalAvgPrice = tempAvgPrice; + result.totalCost = availableBudget - remainingBudget; + result.totalLots = tempLots; + return result; + } + +private: + std::pair calculateOptimalLots(double currentLots, double currentAvg, + double price, double remainingBudget) const { + if (price >= currentAvg) return {0, 0}; + + bool hasBudget = (remainingBudget < std::numeric_limits::max()); + double maxPossibleByBudget = hasBudget ? remainingBudget / price : std::numeric_limits::max(); + + double maxByPortfolioSize = currentLots * maxPurchasePercent; + double requiredForTarget = currentLots * (currentAvg - targetAvgPrice) / (targetAvgPrice - price); + + int lotsToBuy = static_cast(std::min({ + maxPossibleByBudget, + maxByPortfolioSize, + std::max(1.0, requiredForTarget * 0.8) + })); + + if (lotsToBuy <= 0) return {0, 0}; + + double totalCost = lotsToBuy * price; + return {lotsToBuy, totalCost}; + } +}; + +class InvestmentCalculator { +public: + static void compareStrategies(int currentLots, double currentAvgPrice, + double targetAvgPrice, double budget) { + std::vector> futures; + std::vector stepsToTest = {0.0, 1.0, 2.0,3.0, 4.0, 5.0, 6.0 }; // 0.0 - автошаг + + for (double step : stepsToTest) { + futures.push_back(std::async(std::launch::async, [=]() { + InvestmentStrategy strategy(currentLots, currentAvgPrice, + targetAvgPrice, budget, step); + return strategy.calculate(); + })); + } + + std::vector results; + for (auto& future : futures) { + results.push_back(future.get()); + } + + printComparison(results, currentAvgPrice, targetAvgPrice, budget); + } + +private: + static void printComparison(const std::vector& results, + double currentAvgPrice, double targetAvgPrice, + double budget) { + std::cout << "\nСравнение стратегий покупки:\n"; + std::cout << "====================================================================\n"; + std::cout << "| Шаг цены | Лотов | Ср.цена | Затраты | Близость к цели | Эффект.\n"; + std::cout << "====================================================================\n"; + + for (const auto& result : results) { + double distanceToTarget = std::abs(result.finalAvgPrice - targetAvgPrice); + double efficiency = (currentAvgPrice - result.finalAvgPrice) / result.totalCost * 1000; + + std::cout << "| " << std::setw(8) << std::fixed << std::setprecision(2) + << (result.priceStepUsed == 0.0 ? "авто" : std::to_string(result.priceStepUsed)) + << " | " << std::setw(5) << result.totalLots + << " | " << std::setw(7) << result.finalAvgPrice + << " | " << std::setw(7) << result.totalCost + << " | " << std::setw(14) << distanceToTarget + << " | " << std::setw(7) << efficiency << "\n"; + } + std::cout << "====================================================================\n"; + + // best strategy + auto best = std::min_element(results.begin(), results.end(), + [targetAvgPrice](const auto& a, const auto& b) { + return std::abs(a.finalAvgPrice - targetAvgPrice) < + std::abs(b.finalAvgPrice - targetAvgPrice); + }); + + std::cout << "\nЛучшая стратегия: шаг " + << (best->priceStepUsed == 0.0 ? "авто" : std::to_string(best->priceStepUsed)) + << " руб.\n"; + printDetailedResult(*best); + } + + static void printDetailedResult(const InvestmentStrategy::Result& result) { + std::cout << "\nДетализация лучшей стратегии:\n"; + std::cout << "==================================================\n"; + std::cout << std::setw(12) << "Цена" << std::setw(15) << "Лоты" + << std::setw(15) << "Стоимость" << std::setw(20) << "Ср. цена после\n"; + + double tempLots = 0; + double tempAvgPrice = 0; + double totalLots = 0; + double totalCost = 0; + + for (const auto& [price, lots, cost] : result.purchases) { + tempAvgPrice = (tempLots * tempAvgPrice + lots * price) / (tempLots + lots); + tempLots += lots; + totalCost += cost; + + std::cout << std::setw(12) << std::fixed << std::setprecision(2) << price + << std::setw(15) << lots + << std::setw(15) << cost + << std::setw(20) << tempAvgPrice << "\n"; + } + + std::cout << "==================================================\n"; + std::cout << "Итоговая средняя цена: " << result.finalAvgPrice << " руб.\n"; + std::cout << "Общие затраты: " << result.totalCost << " руб.\n"; + std::cout << "Общее количество лотов: " << result.totalLots << "\n"; + } +}; + + + +#endif //SSM_INVESTMENTCALCULATOR_H diff --git a/lib/common/logger.h b/lib/common/logger.h index 49d759e..4542c4a 100644 --- a/lib/common/logger.h +++ b/lib/common/logger.h @@ -16,14 +16,15 @@ #include #include + #define DEFAULT_DIR "/.ssm" template< typename VALUE, typename SFINAE = void > struct StrongTypedefValidator {}; -template<> [[maybe_unused]] -struct StrongTypedefValidator< std::string, std::enable_if< true >::type > final +template<> +struct [[maybe_unused]] StrongTypedefValidator< std::string, std::enable_if< true >::type > final { bool operator ()( const std::string & value ) const noexcept { @@ -32,8 +33,8 @@ struct StrongTypedefValidator< std::string, std::enable_if< true >::type > final }; -template<> [[maybe_unused]] -struct StrongTypedefValidator< QString, std::enable_if< true >::type > final +template<> +struct [[maybe_unused]] StrongTypedefValidator< QString, std::enable_if< true >::type > final { bool operator ()( const QString & value ) const noexcept { @@ -220,7 +221,7 @@ class CSVLogger final : public Logger public: explicit CSVLogger(string directory = (getenv("HOME") != nullptr ? std::string(getenv("HOME")) : "/tmp") + std::string(DEFAULT_DIR), - string delm = "; ", bool write = true, bool timestamp = false ) : + string delm = ";", bool write = true, bool timestamp = false ) : path(directory), delimeter(delm), write_enabled(write), timestamp_enabled(timestamp) {} diff --git a/lib/common/storage.h b/lib/common/storage.h new file mode 100644 index 0000000..898bc0c --- /dev/null +++ b/lib/common/storage.h @@ -0,0 +1,218 @@ +#ifndef SSM_STORAGE_H +#define SSM_STORAGE_H + + +struct CandleType { + +}; + +namespace CON { + + namespace REALTIME { + + struct Candles { + }; + + namespace SHARES { + struct Trades { + + }; + } + namespace FUTURES { + struct Trade { + + }; + } + } + + + namespace SUPERCANDLES { + struct TradeStatsInfo { + + }; + + } + +} + + +template +class Storage { +public: + Storage() = default; + + void add(T item) { + m_data.emplace_back(item); + } + + void remove(T item) { + + } + + bool isContains(T item) { + auto result = find(first(), last(), item) != last(); + return result; + } + + std::vector &get() { + return m_data; + } + + auto size() { + return m_data.size(); + } + + auto inline first() const { + return m_data.begin(); + } + + auto inline last() const { + return m_data.end(); + } + +private: + std::vector m_data; +}; + +class TradesInfo { +public: + explicit TradesInfo(const QString ticker, const QString typeTrades, const QString date) noexcept; + + bool operator==(const TradesInfo &lsh) const {}; + + bool operator<(const TradesInfo &lsh) const {}; + + bool operator>(const TradesInfo &lsh) const {}; +private: + QString m_date; // date trades write to storage + QString m_ticker; // ticker + QString m_typeTrades; // sell or buy +}; + +class CandleInfo { +public: + explicit CandleInfo(const QString ticker, const CandleType typeCandle, const QString date) noexcept; + + bool operator==(const CandleInfo &lsh) const {}; + + bool operator<(const CandleInfo &lsh) const {}; + + bool operator>(const CandleInfo &lsh) const {}; +private: + QString m_date; //date candle write to storage + QString m_ticker; + CandleType m_type; +}; + + +namespace API::V1 { + + template + concept CandleType = std::is_same_v || + std::is_same_v; + + template + concept TradeType = std::is_same_v || + std::is_same_v; + + template + concept ItemWithInfo = requires(T item, Info info) { + { info == info } -> std::convertible_to; + { info < info } -> std::convertible_to; + }; + + template requires ItemWithInfo + class UniversalManager { + public: + using ItemT = T; + using InfoT = InfoType; + using StoragePtr = std::shared_ptr>; + using Container = std::map; + + UniversalManager() = default; + + virtual ~UniversalManager() = default; + + void add(const InfoType &info, const T &item) { + auto it = m_storage.find(info); + if (it == m_storage.end()) { + auto storage = std::make_shared>(); + storage->add(item); + m_storage.emplace(info, storage); + } else { + if (!it->second->isContains(item)) { + it->second->add(item); + } + } + } + + bool remove(const InfoType &info) { + return m_storage.erase(info) > 0; + } + + std::optional get(const InfoType &info) const { + auto it = m_storage.find(info); + return it != m_storage.end() ? std::make_optional(it->second) : std::nullopt; + } + + auto begin(const InfoType &info) const { + if (auto storage = get(info); storage && !storage.value()->empty()) { + return storage.value()->first(); + } + throw std::runtime_error("Storage is empty or not found"); + } + + auto end(const InfoType &info) const { + if (auto storage = get(info); storage && !storage.value()->empty()) { + return storage.value()->last(); + } + throw std::runtime_error("Storage is empty or not found"); + } + + const Container &getAll() const { return m_storage; } + + size_t count() const { return m_storage.size(); } + + bool empty() const { return m_storage.empty(); } + + private: + Container m_storage; + }; + + using CandleManager = UniversalManager< + CON::REALTIME::Candles, + CandleInfo + >; + + using SuperCandleManager = UniversalManager< + CON::SUPERCANDLES::TradeStatsInfo, + CandleInfo + >; + + using ShareTradesManager = UniversalManager< + CON::REALTIME::SHARES::Trades, + TradesInfo + >; + + using FuturesTradesManager = UniversalManager< + CON::REALTIME::FUTURES::Trade, + TradesInfo + >; + + + template + class CandleManagerSep { + public: + static_assert(CandleType, "This manager is only for candle types"); + // release as UniversalManager + }; + + template + class TradeManagerSep { + public: + static_assert(TradeType, "This manager is only for trade types"); + //release as UniversalManager + }; +} + +#endif //SSM_STORAGE_H diff --git a/main.cpp b/main.cpp index 829173b..4ad32f3 100644 --- a/main.cpp +++ b/main.cpp @@ -10,7 +10,6 @@ int main(int argc, char *argv[]) { QApplication a(argc, argv); - MainWindow app; app.show(); @@ -19,26 +18,20 @@ int main(int argc, char *argv[]) Strategy::dca res_test(10, 100, 90); std::vector> ranges = Strategy::make_sub_range(MAX_ASSETS); - ThreadPool pool(4); - pool.setJoin(true); - timer.start(); - for(auto &item_range : ranges) - { -// std::cout<<"Try add task: " << item_range.first <<" to " << item_range.second <<"\n"; - pool.addTask([&]() { Strategy::calculateDCA(res_test, item_range.first, item_range.second); }); -// pool.addTask( std::ref(Strategy::calculateDCA), std::ref(res_test) , std::ref(item_range.first), std::ref(item_range.second)); -// Strategy::calculateDCA(res_test, item_range.first, item_range.second); - - } - timer.stop().print(); - -//// c++17 struct_binding -// for(const auto [range_assets, range_price] : res_test.goal_range) + //for thread pool +// ThreadPool pool(4); +// pool.setJoin(true); +// timer.start(); +// for(auto &item_range : ranges) // { -// qDebug() <<"Range assets main" <init(); + return a.exec(); }