├── feed ├── src │ ├── test │ │ ├── resources │ │ │ ├── cucumber.properties │ │ │ ├── module-info.test │ │ │ └── feed │ │ │ │ └── signal.feature │ │ └── java │ │ │ └── moduletest │ │ │ ├── FeedComponentTest.java │ │ │ ├── DoesntExistSignal.java │ │ │ └── ExistSignal.java │ └── main │ │ └── java │ │ ├── com │ │ └── apssouza │ │ │ └── mytrade │ │ │ └── feed │ │ │ ├── api │ │ │ ├── SignalDto.java │ │ │ ├── PriceDto.java │ │ │ ├── FeedModule.java │ │ │ └── FeedBuilder.java │ │ │ └── domain │ │ │ ├── signal │ │ │ ├── SignalDao.java │ │ │ ├── SignalHandler.java │ │ │ ├── SqlSignalDao.java │ │ │ └── MemorySignalDao.java │ │ │ └── price │ │ │ ├── PriceDao.java │ │ │ ├── PriceHandler.java │ │ │ ├── SqlPriceDao.java │ │ │ └── MemoryPriceDao.java │ │ └── module-info.java └── pom.xml ├── forex ├── src │ ├── test │ │ ├── resources │ │ │ ├── cucumber.properties │ │ │ ├── trading │ │ │ │ └── forex.feature │ │ │ └── module-info.test │ │ └── java │ │ │ ├── moduletest │ │ │ ├── CucumberTest.java │ │ │ ├── FeedMockBuilder.java │ │ │ └── HistoryWithTransactions.java │ │ │ └── com │ │ │ └── apssouza │ │ │ └── mytrade │ │ │ └── trading │ │ │ └── domain │ │ │ └── forex │ │ │ ├── feed │ │ │ ├── SignalBuilder.java │ │ │ └── PriceBuilder.java │ │ │ ├── riskmanagement │ │ │ └── stopordercreation │ │ │ │ └── RiskManagementBuilder.java │ │ │ ├── session │ │ │ └── LoopEventBuilder.java │ │ │ ├── portfolio │ │ │ ├── PortfolioHandlerBuilder.java │ │ │ ├── FilledOrderBuilder.java │ │ │ └── PositionBuilder.java │ │ │ └── order │ │ │ ├── StopOrderBuilder.java │ │ │ └── OrderBuilder.java │ └── main │ │ └── java │ │ ├── com │ │ └── apssouza │ │ │ └── mytrade │ │ │ └── trading │ │ │ ├── api │ │ │ ├── SessionType.java │ │ │ ├── ExecutionType.java │ │ │ ├── CycleHistoryDto.java │ │ │ ├── ForexDto.java │ │ │ ├── TransactionDto.java │ │ │ ├── ForexBuilder.java │ │ │ └── ForexEngine.java │ │ │ └── domain │ │ │ └── forex │ │ │ ├── riskmanagement │ │ │ ├── PositionSizerFixed.java │ │ │ ├── stopordercreation │ │ │ │ ├── StopOrderCreationFactory.java │ │ │ │ ├── CreatorStrategy.java │ │ │ │ ├── StopOrderCreator.java │ │ │ │ ├── StopOrderFilledEvent.java │ │ │ │ ├── StopOrderConfigDto.java │ │ │ │ ├── StopOrderDto.java │ │ │ │ ├── StopOrderCreatorFixed.java │ │ │ │ ├── ShortPositionStrategy.java │ │ │ │ ├── LongPositionStrategy.java │ │ │ │ └── CreatorContext.java │ │ │ ├── RiskManagementFactory.java │ │ │ ├── RiskManagementService.java │ │ │ └── PositionExitChecker.java │ │ │ ├── common │ │ │ ├── ForexException.java │ │ │ ├── observerinfra │ │ │ │ ├── Observer.java │ │ │ │ ├── Subject.java │ │ │ │ └── EventNotifier.java │ │ │ ├── events │ │ │ │ ├── Event.java │ │ │ │ ├── EndedTradingDayEvent.java │ │ │ │ ├── PositionClosedEvent.java │ │ │ │ ├── SignalCreatedEvent.java │ │ │ │ ├── OrderFilledEvent.java │ │ │ │ ├── AbstractEvent.java │ │ │ │ ├── OrderCreatedEvent.java │ │ │ │ ├── OrderFoundEvent.java │ │ │ │ ├── PortfolioChangedEvent.java │ │ │ │ ├── SessionFinishedEvent.java │ │ │ │ └── PriceChangedEvent.java │ │ │ ├── NumberHelper.java │ │ │ ├── TradingHelper.java │ │ │ ├── TradingParams.java │ │ │ └── Currency.java │ │ │ ├── portfolio │ │ │ ├── PortfolioException.java │ │ │ ├── ReconciliationException.java │ │ │ ├── FilledOrderDto.java │ │ │ ├── EndedTradingDayListener.java │ │ │ ├── PortfolioService.java │ │ │ ├── PortfolioFactory.java │ │ │ ├── StopOrderFilledListener.java │ │ │ ├── PositionCollection.java │ │ │ ├── PortfolioChecker.java │ │ │ └── PositionDto.java │ │ │ ├── feed │ │ │ ├── FeedFactory.java │ │ │ ├── signalfeed │ │ │ │ ├── SignalFeedFactory.java │ │ │ │ └── SignalFeedHandler.java │ │ │ ├── pricefeed │ │ │ │ ├── PriceStream.java │ │ │ │ ├── PriceFeedHandler.java │ │ │ │ ├── PriceStreamFactory.java │ │ │ │ ├── RealtimePriceStream.java │ │ │ │ └── HistoricalPriceStream.java │ │ │ ├── FeedService.java │ │ │ └── TradingFeed.java │ │ │ ├── order │ │ │ ├── OrderDao.java │ │ │ ├── OrderService.java │ │ │ ├── PositionClosedListener.java │ │ │ ├── OrderDto.java │ │ │ ├── OrderFactory.java │ │ │ ├── MemoryOrderDao.java │ │ │ ├── SignalCreatedListener.java │ │ │ ├── OrderServiceImpl.java │ │ │ └── OrderFoundListener.java │ │ │ ├── brokerintegration │ │ │ ├── BrokerIntegrationFactory.java │ │ │ ├── MultiPositionPerCPairHandler.java │ │ │ ├── InteractiveBrokerOrderExecution.java │ │ │ ├── BrokerIntegrationService.java │ │ │ └── StopOrderPriceMonitor.java │ │ │ ├── orderbook │ │ │ ├── HistoryPortfolioChangedListener.java │ │ │ ├── OrderCreatedListener.java │ │ │ ├── OrderBookService.java │ │ │ ├── OrderBookFactory.java │ │ │ ├── HistoryFilledOrderListener.java │ │ │ ├── SessionFinishedListener.java │ │ │ ├── HistoryStopOrderFilledListener.java │ │ │ ├── CycleHistoryDto.java │ │ │ ├── OrderBookServiceImpl.java │ │ │ ├── TransactionDto.java │ │ │ └── TransactionsExporter.java │ │ │ └── session │ │ │ └── QueueConsumer.java │ │ └── module-info.java └── pom.xml ├── common ├── src │ ├── test │ │ ├── resources │ │ │ ├── properties.test.yml │ │ │ └── logback-test.xml │ │ └── java │ │ │ └── com │ │ │ └── apssouza │ │ │ └── mytrade │ │ │ └── common │ │ │ ├── appconfig │ │ │ └── ServiceConfigTest.java │ │ │ └── encrypt │ │ │ └── AesPBEStringEncryptorTest.java │ └── main │ │ ├── resources │ │ └── properties.yml │ │ └── java │ │ ├── module-info.java │ │ └── com │ │ └── apssouza │ │ └── mytrade │ │ └── common │ │ ├── appconfig │ │ ├── ServerConfigDto.java │ │ └── ServiceConfig.java │ │ ├── file │ │ ├── WriteFileHelper.java │ │ ├── ReadFileHelper.java │ │ ├── FileDiskHelper.java │ │ └── CSVHelper.java │ │ ├── time │ │ ├── DateTimeConverter.java │ │ ├── DateRangeHelper.java │ │ ├── Interval.java │ │ ├── DayHelper.java │ │ ├── MarketTimeHelper.java │ │ └── DateTimeHelper.java │ │ └── encrypt │ │ └── AESPBEStringEncryptor.java └── pom.xml ├── assets ├── trading-system.png └── trading-system.drawio ├── runner ├── src │ └── main │ │ ├── resources │ │ ├── config │ │ │ └── application.properties │ │ ├── public │ │ │ └── test.css │ │ └── logback.xml │ │ └── java │ │ ├── module-info.java │ │ └── com │ │ └── apssouza │ │ └── mytrade │ │ └── runner │ │ └── Application.java └── pom.xml ├── .sdkmanrc ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── Dockerfile ├── .gitignore └── README.md /feed/src/test/resources/cucumber.properties: -------------------------------------------------------------------------------- 1 | cucumber.publish.quiet=true 2 | -------------------------------------------------------------------------------- /forex/src/test/resources/cucumber.properties: -------------------------------------------------------------------------------- 1 | cucumber.publish.quiet=true 2 | -------------------------------------------------------------------------------- /common/src/test/resources/properties.test.yml: -------------------------------------------------------------------------------- 1 | grpc: 2 | server: 3 | port: 50051 4 | -------------------------------------------------------------------------------- /assets/trading-system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apssouza22/trading-system/HEAD/assets/trading-system.png -------------------------------------------------------------------------------- /common/src/main/resources/properties.yml: -------------------------------------------------------------------------------- 1 | grpc: 2 | server: 3 | port: 50051 4 | timeout: 1000 5 | 6 | -------------------------------------------------------------------------------- /runner/src/main/resources/config/application.properties: -------------------------------------------------------------------------------- 1 | debug=false 2 | spring.application.name=mytrade-runner 3 | -------------------------------------------------------------------------------- /runner/src/main/resources/public/test.css: -------------------------------------------------------------------------------- 1 | my-test { 2 | width: 500px; 3 | height: 501px; 4 | color: red; 5 | } 6 | -------------------------------------------------------------------------------- /.sdkmanrc: -------------------------------------------------------------------------------- 1 | # Enable auto-env through the sdkman_auto_env config 2 | # Add key=value pairs of SDKs to use below 3 | java=17.0.6-zulu 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | patreon: apssouza 3 | custom: ["https://www.buymeacoffee.com/apssouza"] 4 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/api/SessionType.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.api; 2 | 3 | public enum SessionType { 4 | LIVE, BACK_TEST 5 | } 6 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/api/ExecutionType.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.api; 2 | 3 | public enum ExecutionType { 4 | BROKER, SIMULATED 5 | } 6 | -------------------------------------------------------------------------------- /forex/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module com.apssouza.mytrade.trading.forex { 2 | requires java.logging; 3 | requires java.sql; 4 | requires com.apssouza.mytrade.feed; 5 | exports com.apssouza.mytrade.trading.api; 6 | } -------------------------------------------------------------------------------- /feed/src/main/java/com/apssouza/mytrade/feed/api/SignalDto.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.feed.api; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public record SignalDto(LocalDateTime createdAt, String action, String symbol, String sourceName) {} 6 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/api/CycleHistoryDto.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.api; 2 | 3 | 4 | import java.time.LocalDateTime; 5 | import java.util.List; 6 | 7 | public record CycleHistoryDto(LocalDateTime time, List events) { 8 | } 9 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/riskmanagement/PositionSizerFixed.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.riskmanagement; 2 | 3 | class PositionSizerFixed{ 4 | 5 | public Integer getQuantity() { 6 | return 10000; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /common/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.apssouza.mytrade.common { 2 | requires java.logging; 3 | requires java.sql; 4 | requires cfg4j.core; 5 | requires org.slf4j; 6 | exports com.apssouza.mytrade.common.time; 7 | exports com.apssouza.mytrade.common.file; 8 | } -------------------------------------------------------------------------------- /runner/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module com.apssouza.mytrade.runner { 2 | requires java.sql; 3 | requires com.apssouza.mytrade.trading.forex; 4 | requires spring.boot; 5 | requires spring.boot.autoconfigure; 6 | requires spring.context; 7 | requires spring.web; 8 | } -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/ForexException.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common; 2 | 3 | public class ForexException extends RuntimeException { 4 | 5 | public ForexException(Exception ex) { 6 | super(ex); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/portfolio/PortfolioException.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.portfolio; 2 | 3 | public class PortfolioException extends Exception { 4 | public PortfolioException( String msg ) { 5 | super(msg); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/observerinfra/Observer.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common.observerinfra; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 4 | 5 | public interface Observer 6 | { 7 | void update(Event e); 8 | } -------------------------------------------------------------------------------- /forex/src/test/java/moduletest/CucumberTest.java: -------------------------------------------------------------------------------- 1 | package moduletest; 2 | 3 | 4 | import org.junit.runner.RunWith; 5 | 6 | import io.cucumber.junit.Cucumber; 7 | import io.cucumber.junit.CucumberOptions; 8 | 9 | @RunWith(Cucumber.class) 10 | @CucumberOptions(plugin = {"pretty"}, features = {"src/test/resources/trading"}) 11 | public class CucumberTest { 12 | } 13 | -------------------------------------------------------------------------------- /feed/src/test/java/moduletest/FeedComponentTest.java: -------------------------------------------------------------------------------- 1 | package moduletest; 2 | 3 | 4 | import org.junit.runner.RunWith; 5 | 6 | import io.cucumber.junit.Cucumber; 7 | import io.cucumber.junit.CucumberOptions; 8 | 9 | @RunWith(Cucumber.class) 10 | @CucumberOptions(plugin = {"pretty"}, features = {"src/test/resources/feed"}) 11 | public class FeedComponentTest { 12 | } 13 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/feed/FeedFactory.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.feed; 2 | 3 | import com.apssouza.mytrade.feed.api.FeedModule; 4 | 5 | public class FeedFactory { 6 | 7 | public static FeedService create(final FeedModule feedModule) { 8 | return new TradingFeed(feedModule); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /common/src/main/java/com/apssouza/mytrade/common/appconfig/ServerConfigDto.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.common.appconfig; 2 | 3 | /** 4 | * Interface used to load the gRPC property data 5 | */ 6 | public interface ServerConfigDto { 7 | 8 | int port(); 9 | 10 | int numExecutors(); 11 | 12 | boolean reflectionEnabled(); 13 | 14 | String serviceName(); 15 | } 16 | -------------------------------------------------------------------------------- /feed/src/main/java/com/apssouza/mytrade/feed/domain/signal/SignalDao.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.feed.domain.signal; 2 | 3 | import com.apssouza.mytrade.feed.api.SignalDto; 4 | 5 | import java.time.LocalDateTime; 6 | import java.util.List; 7 | 8 | public interface SignalDao { 9 | 10 | List getSignal(String systemName, LocalDateTime currentTime); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/observerinfra/Subject.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common.observerinfra; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 4 | 5 | public interface Subject 6 | { 7 | void attach(Observer o); 8 | void detach(Observer o); 9 | void notify(Event e); 10 | } 11 | -------------------------------------------------------------------------------- /forex/src/test/resources/trading/forex.feature: -------------------------------------------------------------------------------- 1 | Feature: Get session history 2 | Test the ability to retrieve the trade session history 3 | 4 | Scenario: Test get history when exists transactions 5 | Given 2 buy signals and 1 sell signal 6 | When retrieving session history 7 | Then return a history with 3 transactions 8 | Then history should contain 2 buys and 1 sell orders 9 | 10 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/order/OrderDao.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.order; 2 | 3 | import java.util.List; 4 | 5 | interface OrderDao { 6 | 7 | OrderDto persist(OrderDto order); 8 | 9 | boolean updateStatus(Integer id, OrderDto.OrderStatus status); 10 | 11 | List getOrderByStatus(OrderDto.OrderStatus status); 12 | } 13 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/events/Event.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common.events; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | 5 | import java.time.LocalDateTime; 6 | import java.util.Map; 7 | 8 | public interface Event { 9 | 10 | LocalDateTime getTimestamp(); 11 | 12 | Map getPrice(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /common/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/feed/signalfeed/SignalFeedFactory.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.feed.signalfeed; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.feed.FeedService; 4 | 5 | public class SignalFeedFactory { 6 | 7 | public static SignalFeedHandler create(FeedService feed) { 8 | return new SignalFeedHandler(feed); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /feed/src/main/java/com/apssouza/mytrade/feed/api/PriceDto.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.feed.api; 2 | 3 | import java.math.BigDecimal; 4 | import java.sql.Date; 5 | import java.time.LocalDateTime; 6 | 7 | public record PriceDto( 8 | LocalDateTime timestamp, 9 | BigDecimal open, 10 | BigDecimal close, 11 | BigDecimal high, 12 | BigDecimal low, 13 | String symbol 14 | ) { 15 | } 16 | -------------------------------------------------------------------------------- /feed/src/main/java/com/apssouza/mytrade/feed/domain/price/PriceDao.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.feed.domain.price; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | 5 | import java.time.LocalDateTime; 6 | import java.util.List; 7 | 8 | public interface PriceDao { 9 | 10 | List getPriceInterval(LocalDateTime start, LocalDateTime end); 11 | 12 | List getClosestPrice(LocalDateTime time); 13 | } 14 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/api/ForexDto.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.api; 2 | 3 | import java.math.BigDecimal; 4 | import java.time.LocalDateTime; 5 | 6 | public record ForexDto( 7 | String systemName, 8 | LocalDateTime startDay, 9 | LocalDateTime endDay, 10 | BigDecimal equity, 11 | SessionType sessionType, 12 | ExecutionType executionType 13 | ) { 14 | } 15 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/riskmanagement/stopordercreation/StopOrderCreationFactory.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation; 2 | 3 | public class StopOrderCreationFactory { 4 | 5 | public static StopOrderCreator factory( 6 | StopOrderConfigDto stopOrderDto 7 | ) { 8 | return new StopOrderCreatorFixed(stopOrderDto); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /feed/src/test/resources/module-info.test: -------------------------------------------------------------------------------- 1 | // 2 | // Patch, i.e. configure, Java module system at test-runtime 3 | // https://sormuras.github.io/blog/2018-09-11-testing-in-the-modular-world.html 4 | // 5 | 6 | // Allow deep reflection for test discovery - repeat for each test library you need 7 | //"open module com.apssouza.mytrade" 8 | --add-opens 9 | com.apssouza.mytrade=org.mockito 10 | 11 | // "requires org.mockito" 12 | --add-reads 13 | com.apssouza.mytrade.feed=org.mockito -------------------------------------------------------------------------------- /forex/src/test/resources/module-info.test: -------------------------------------------------------------------------------- 1 | // 2 | // Patch, i.e. configure, Java module system at test-runtime 3 | // https://sormuras.github.io/blog/2018-09-11-testing-in-the-modular-world.html 4 | // 5 | 6 | // Allow deep reflection for test discovery - repeat for each test library you need 7 | //"open module com.apssouza.mytrade" 8 | --add-opens 9 | com.apssouza.mytrade=org.mockito 10 | 11 | // "requires org.mockito" 12 | --add-reads 13 | com.apssouza.mytrade.trading.forex=org.mockito -------------------------------------------------------------------------------- /feed/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | //open indicates that all packages are accessible to code in other modules at runtime only. 2 | //Also, all the types in the specified package (and all of the types’ members) are accessible via reflection. 3 | // It is required to work with Cucumber 4 | open module com.apssouza.mytrade.feed { 5 | exports com.apssouza.mytrade.feed.api; 6 | requires java.sql; 7 | // transitive is to say that modules that depend on feed also depend on common 8 | requires transitive com.apssouza.mytrade.common; 9 | } -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/feed/pricefeed/PriceStream.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.feed.pricefeed; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | 5 | import java.time.LocalDateTime; 6 | import java.util.Map; 7 | 8 | 9 | /** 10 | * Price feed stream API for the Forex system 11 | */ 12 | public interface PriceStream { 13 | 14 | void start(LocalDateTime start, LocalDateTime end); 15 | 16 | Map getPriceSymbolMapped(LocalDateTime current); 17 | } 18 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/feed/FeedService.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.feed; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.feed.api.SignalDto; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public interface FeedService { 11 | 12 | List getSignal(String systemName, final LocalDateTime currentTime); 13 | 14 | Map getPriceSymbolMapped(LocalDateTime time); 15 | } 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM adoptopenjdk/openjdk14:armv7l-ubuntu-jre-14.0.2_12 as builder 2 | 3 | ARG JAR_FILE=runner/target/*.jar 4 | COPY ${JAR_FILE} application.jar 5 | RUN java -Djarmode=layertools -jar application.jar extract 6 | 7 | FROM adoptopenjdk/openjdk14:armv7l-ubuntu-jre-14.0.2_12 8 | COPY --from=builder dependencies/ ./ 9 | COPY --from=builder snapshot-dependencies/ ./ 10 | COPY --from=builder spring-boot-loader/ ./ 11 | COPY --from=builder application/ ./ 12 | 13 | EXPOSE 8080 14 | ENTRYPOINT ["java","--enable-preview","org.springframework.boot.loader.JarLauncher"] 15 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/events/EndedTradingDayEvent.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common.events; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.AbstractEvent; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.Map; 8 | 9 | public class EndedTradingDayEvent extends AbstractEvent { 10 | 11 | public EndedTradingDayEvent(LocalDateTime time, Map price) { 12 | super(time, price); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/riskmanagement/RiskManagementFactory.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.riskmanagement; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation.StopOrderCreator; 4 | 5 | public class RiskManagementFactory { 6 | 7 | public static RiskManagementService create(StopOrderCreator stopOrderCreator) { 8 | return new RiskManagementServiceImpl( 9 | new PositionExitChecker(), 10 | stopOrderCreator 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /feed/src/main/java/com/apssouza/mytrade/feed/domain/signal/SignalHandler.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.feed.domain.signal; 2 | 3 | import com.apssouza.mytrade.feed.api.SignalDto; 4 | 5 | import java.time.LocalDateTime; 6 | import java.util.List; 7 | 8 | public class SignalHandler { 9 | private SignalDao signalDao; 10 | 11 | public SignalHandler(SignalDao signalDao) { 12 | this.signalDao = signalDao; 13 | } 14 | 15 | public List getSignal(String sourceName, LocalDateTime currentTime) { 16 | return this.signalDao.getSignal(sourceName, currentTime); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/NumberHelper.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common; 2 | 3 | import java.math.BigDecimal; 4 | import java.math.MathContext; 5 | import java.math.RoundingMode; 6 | 7 | public class NumberHelper { 8 | 9 | public static BigDecimal roundSymbolPrice(String symbol, BigDecimal price){ 10 | MathContext mc = new MathContext( 11 | TradingParams.currency_pair_significant_digits_in_price.get(symbol), 12 | RoundingMode.HALF_UP 13 | ); 14 | return price.round(mc); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /feed/src/test/resources/feed/signal.feature: -------------------------------------------------------------------------------- 1 | Feature: Get trade signals 2 | Test the ability to retrieve trade signals by date time 3 | 4 | Scenario: Test get signals when exists signals 5 | Given that exists signal to a give time and system name 6 | When try to retrieve signal to "system-test" and 2018-8-21 0:1:0 7 | Then return 1 signal 8 | Then returned signal is "Sell" 9 | 10 | Scenario: Test get signals when doesn't exist signal to the given system name 11 | Given that exists signal to the given time but not for the given system 12 | When try to retrieve signal to system "system-name" and 2020-05-01 10:10:11 13 | Then return no signal 14 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/riskmanagement/stopordercreation/CreatorStrategy.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 4 | 5 | import java.math.BigDecimal; 6 | import java.util.Optional; 7 | 8 | interface CreatorStrategy { 9 | 10 | BigDecimal getEntryStopPrice(PositionDto position, BigDecimal priceClose); 11 | 12 | BigDecimal getHardStopPrice(PositionDto position); 13 | 14 | Optional getTrailingStopPrice(PositionDto position, BigDecimal last_close); 15 | 16 | BigDecimal getProfitStopPrice(PositionDto position); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /forex/src/test/java/com/apssouza/mytrade/trading/domain/forex/feed/SignalBuilder.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.feed; 2 | 3 | import com.apssouza.mytrade.feed.api.SignalDto; 4 | 5 | import java.time.LocalDateTime; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class SignalBuilder { 10 | List signals = new ArrayList<>(); 11 | 12 | public SignalBuilder addSignal(LocalDateTime time, String action){ 13 | signals.add(new SignalDto( 14 | time, action,"AUDUSD", "test" 15 | )); 16 | return this; 17 | } 18 | 19 | public List buildList(){ 20 | return signals; 21 | } 22 | 23 | public SignalDto build(){ 24 | return signals.get(0); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/feed/pricefeed/PriceFeedHandler.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.feed.pricefeed; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.feed.FeedService; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.Map; 8 | 9 | /** 10 | * PriceFeedHandler is responsible for providing ticker prices 11 | */ 12 | class PriceFeedHandler { 13 | 14 | private FeedService feedModule; 15 | 16 | public PriceFeedHandler(FeedService feedModule) { 17 | this.feedModule = feedModule; 18 | } 19 | 20 | public Map getPriceSymbolMapped(LocalDateTime time) { 21 | return feedModule.getPriceSymbolMapped(time); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/observerinfra/EventNotifier.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common.observerinfra; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class EventNotifier implements Subject { 9 | private List observers = new ArrayList<>(); 10 | 11 | @Override 12 | public void attach(Observer o) { 13 | observers.add(o); 14 | } 15 | 16 | @Override 17 | public void detach(Observer o) { 18 | observers.remove(o); 19 | } 20 | 21 | @Override 22 | public void notify(Event e) { 23 | for(Observer o: observers) { 24 | o.update(e); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/events/PositionClosedEvent.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common.events; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.AbstractEvent; 5 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.Map; 9 | 10 | public class PositionClosedEvent extends AbstractEvent { 11 | private OrderDto order; 12 | 13 | public PositionClosedEvent(LocalDateTime timestamp, Map price, OrderDto order) { 14 | super(timestamp, price); 15 | this.order = order; 16 | } 17 | 18 | public OrderDto getOrder() { 19 | return order; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/order/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.order; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.events.SignalCreatedEvent; 4 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.List; 8 | 9 | public interface OrderService { 10 | 11 | 12 | OrderDto createOrderFromClosedPosition(PositionDto position, LocalDateTime time) ; 13 | 14 | OrderDto persist(OrderDto order) ; 15 | 16 | boolean updateOrderStatus(Integer id, OrderDto.OrderStatus status); 17 | 18 | OrderDto createOrderFromSignal(SignalCreatedEvent event) ; 19 | 20 | List getOrderByStatus(OrderDto.OrderStatus status); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/brokerintegration/BrokerIntegrationFactory.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.brokerintegration; 2 | 3 | import com.apssouza.mytrade.trading.api.ExecutionType; 4 | import com.apssouza.mytrade.trading.domain.forex.common.TradingParams; 5 | 6 | public class BrokerIntegrationFactory { 7 | 8 | public static BrokerIntegrationService factory(ExecutionType executionType){ 9 | if (executionType == ExecutionType.BROKER) { 10 | return new InteractiveBrokerOrderExecution( 11 | TradingParams.brokerHost, 12 | TradingParams.brokerPort, 13 | TradingParams.brokerClientId 14 | ); 15 | } 16 | return new SimulatedOrderExecution(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/riskmanagement/stopordercreation/StopOrderCreator.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 5 | 6 | import java.util.Optional; 7 | 8 | public interface StopOrderCreator { 9 | 10 | void createContext(PositionDto.PositionType type); 11 | 12 | StopOrderDto getHardStopLoss(PositionDto position); 13 | 14 | StopOrderDto getProfitStopOrder(PositionDto position); 15 | 16 | Optional getEntryStopOrder(PositionDto position, Event event); 17 | 18 | Optional getTrailingStopOrder(PositionDto position, Event event); 19 | } 20 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/events/SignalCreatedEvent.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common.events; 2 | 3 | 4 | import com.apssouza.mytrade.feed.api.PriceDto; 5 | import com.apssouza.mytrade.feed.api.SignalDto; 6 | import com.apssouza.mytrade.trading.domain.forex.common.events.AbstractEvent; 7 | 8 | import java.time.LocalDateTime; 9 | import java.util.Map; 10 | 11 | public class SignalCreatedEvent extends AbstractEvent { 12 | 13 | 14 | private final SignalDto signal; 15 | 16 | public SignalCreatedEvent(LocalDateTime timestamp, Map price, SignalDto signalDto) { 17 | super(timestamp, price); 18 | this.signal = signalDto; 19 | } 20 | 21 | public SignalDto getSignal() { 22 | return signal; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/feed/signalfeed/SignalFeedHandler.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.feed.signalfeed; 2 | 3 | import com.apssouza.mytrade.feed.api.SignalDto; 4 | import com.apssouza.mytrade.trading.domain.forex.feed.FeedService; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.List; 8 | 9 | /** 10 | * SignalFeedHandler is responsible for handle trading signals 11 | */ 12 | public class SignalFeedHandler { 13 | 14 | private FeedService feedModule; 15 | 16 | public SignalFeedHandler(FeedService feedModule) { 17 | this.feedModule = feedModule; 18 | } 19 | 20 | public List getSignal(String systemName, final LocalDateTime currentTime) { 21 | return feedModule.getSignal(systemName, currentTime); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/events/OrderFilledEvent.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common.events; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.AbstractEvent; 5 | import com.apssouza.mytrade.trading.domain.forex.portfolio.FilledOrderDto; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.Map; 9 | 10 | public class OrderFilledEvent extends AbstractEvent { 11 | private final FilledOrderDto filledOrder; 12 | 13 | public OrderFilledEvent(LocalDateTime time, Map price, FilledOrderDto filledOrder) { 14 | super(time, price); 15 | this.filledOrder = filledOrder; 16 | } 17 | 18 | public FilledOrderDto getFilledOrder() { 19 | return filledOrder; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/riskmanagement/stopordercreation/StopOrderFilledEvent.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.AbstractEvent; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.Map; 8 | 9 | public class StopOrderFilledEvent extends AbstractEvent { 10 | private final StopOrderDto order; 11 | 12 | public StopOrderFilledEvent( 13 | LocalDateTime timestamp, 14 | Map price, 15 | StopOrderDto order 16 | ) { 17 | super(timestamp, price); 18 | this.order = order; 19 | } 20 | 21 | public StopOrderDto getStopOrder() { 22 | return order; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.apssouza.mytrade 9 | trading-system-parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | com.apssouza.mytrade 14 | common 15 | 1.0-SNAPSHOT 16 | 17 | 18 | 19 | org.cfg4j 20 | cfg4j-core 21 | 22 | 23 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/events/AbstractEvent.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common.events; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | 5 | import java.time.LocalDateTime; 6 | import java.util.Map; 7 | 8 | public class AbstractEvent implements Event { 9 | 10 | protected final LocalDateTime timestamp; 11 | private final Map priceDtoMap; 12 | 13 | public AbstractEvent( LocalDateTime timestamp, Map priceDtoMap) { 14 | this.timestamp = timestamp; 15 | this.priceDtoMap = priceDtoMap; 16 | } 17 | 18 | @Override 19 | public LocalDateTime getTimestamp() { 20 | return timestamp; 21 | } 22 | 23 | @Override 24 | public Map getPrice() { 25 | return priceDtoMap; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/events/OrderCreatedEvent.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common.events; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.AbstractEvent; 5 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.Map; 9 | 10 | public class OrderCreatedEvent extends AbstractEvent { 11 | private final OrderDto order; 12 | 13 | public OrderCreatedEvent( 14 | LocalDateTime timestamp, 15 | Map price, 16 | OrderDto order 17 | ) { 18 | super(timestamp, price); 19 | this.order = order; 20 | } 21 | 22 | public OrderDto getOrder() { 23 | return order; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/events/OrderFoundEvent.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common.events; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.AbstractEvent; 5 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | public class OrderFoundEvent extends AbstractEvent { 12 | 13 | private final List orders; 14 | 15 | public OrderFoundEvent(LocalDateTime timestamp, Map priceDtoMap, List orders) { 16 | super(timestamp, priceDtoMap); 17 | this.orders = orders; 18 | } 19 | 20 | public List getOrders() { 21 | return orders; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /common/src/test/java/com/apssouza/mytrade/common/appconfig/ServiceConfigTest.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.common.appconfig; 2 | 3 | import org.cfg4j.provider.ConfigurationProvider; 4 | import org.junit.Test; 5 | import static org.junit.Assert.*; 6 | 7 | import java.net.URI; 8 | import java.net.URISyntaxException; 9 | import java.nio.file.Paths; 10 | import java.util.Properties; 11 | 12 | 13 | public class ServiceConfigTest { 14 | 15 | @Test 16 | public void test_load() throws URISyntaxException { 17 | URI uri = ServiceConfigTest.class.getProtectionDomain().getCodeSource().getLocation().toURI(); 18 | ConfigurationProvider provider = ServiceConfig.load(Paths.get(uri).toString(), "test"); 19 | Properties properties = provider.allConfigurationAsProperties(); 20 | assertEquals(50051, properties.get("grpc.server.port")); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/order/PositionClosedListener.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.order; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 4 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.Observer; 5 | import com.apssouza.mytrade.trading.domain.forex.common.events.PositionClosedEvent; 6 | 7 | 8 | class PositionClosedListener implements Observer { 9 | 10 | private final OrderService orderService; 11 | 12 | public PositionClosedListener(OrderService orderService) { 13 | this.orderService = orderService; 14 | } 15 | 16 | @Override 17 | public void update(final Event e) { 18 | if (!(e instanceof PositionClosedEvent event)) { 19 | return; 20 | } 21 | this.orderService.persist(event.getOrder()); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/events/PortfolioChangedEvent.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common.events; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.AbstractEvent; 5 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.Map; 9 | 10 | public class PortfolioChangedEvent extends AbstractEvent { 11 | private final PositionDto position; 12 | 13 | public PortfolioChangedEvent( 14 | LocalDateTime timestamp, 15 | Map price, 16 | PositionDto position 17 | ) { 18 | super(timestamp, price); 19 | this.position = position; 20 | } 21 | 22 | public PositionDto getPosition() { 23 | return position; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.rar 19 | *.tar 20 | *.zip 21 | 22 | # Logs and databases # 23 | ###################### 24 | *.log 25 | *.sqlite 26 | 27 | # OS generated files # 28 | ###################### 29 | .DS_Store 30 | .DS_Store? 31 | ._* 32 | .Spotlight-V100 33 | .Trashes 34 | ehthumbs.db 35 | Thumbs.db 36 | .cache 37 | .settings 38 | .tmproj 39 | .idea 40 | *nbprojects 41 | *nbproject 42 | *nbactions.xml 43 | *nb-configuration.xml 44 | *.svn 45 | nb_jr_remoting.cfg 46 | 47 | .project 48 | .classpath 49 | .idea 50 | *.iml 51 | atlassian-ide-plugin.xml 52 | target 53 | *rebel* 54 | *mvn* 55 | /data 56 | /notebooks 57 | /book-history.csv 58 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/orderbook/HistoryPortfolioChangedListener.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.orderbook; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 4 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.Observer; 5 | import com.apssouza.mytrade.trading.domain.forex.common.events.PortfolioChangedEvent; 6 | 7 | class HistoryPortfolioChangedListener implements Observer { 8 | 9 | private final OrderBookService bookHandler; 10 | 11 | public HistoryPortfolioChangedListener(OrderBookService bookHandler) { 12 | this.bookHandler = bookHandler; 13 | } 14 | 15 | @Override 16 | public void update(final Event e) { 17 | if (!(e instanceof PortfolioChangedEvent event)) { 18 | return; 19 | } 20 | bookHandler.addPosition(event.getPosition()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/events/SessionFinishedEvent.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common.events; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.Map; 8 | 9 | public class SessionFinishedEvent implements Event { 10 | 11 | private final LocalDateTime timestamp; 12 | private final Map price; 13 | 14 | public SessionFinishedEvent(LocalDateTime timestamp, Map price) { 15 | this.timestamp = timestamp; 16 | this.price = price; 17 | } 18 | 19 | @Override 20 | public LocalDateTime getTimestamp() { 21 | return timestamp; 22 | } 23 | 24 | @Override 25 | public Map getPrice() { 26 | return price; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/feed/TradingFeed.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.feed; 2 | 3 | import com.apssouza.mytrade.feed.api.FeedModule; 4 | import com.apssouza.mytrade.feed.api.PriceDto; 5 | import com.apssouza.mytrade.feed.api.SignalDto; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | class TradingFeed implements FeedService { 12 | private FeedModule feed; 13 | 14 | public TradingFeed(FeedModule feed) { 15 | this.feed = feed; 16 | } 17 | 18 | @Override 19 | public List getSignal(final String systemName, final LocalDateTime currentTime) { 20 | return feed.getSignal(systemName, currentTime); 21 | } 22 | 23 | @Override 24 | public Map getPriceSymbolMapped(final LocalDateTime time) { 25 | return feed.getPriceSymbolMapped(time); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/orderbook/OrderCreatedListener.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.orderbook; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 4 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.Observer; 5 | import com.apssouza.mytrade.trading.domain.forex.common.events.OrderCreatedEvent; 6 | 7 | class OrderCreatedListener implements Observer { 8 | private final OrderBookService historyHandler; 9 | 10 | public OrderCreatedListener(OrderBookService historyHandler) { 11 | this.historyHandler = historyHandler; 12 | } 13 | 14 | @Override 15 | public void update(final Event e) { 16 | if (!(e instanceof OrderCreatedEvent event)) { 17 | return; 18 | } 19 | var order = event.getOrder(); 20 | this.historyHandler.addOrder(order); 21 | 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/events/PriceChangedEvent.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common.events; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.Map; 8 | 9 | public class PriceChangedEvent implements Event { 10 | private final LocalDateTime time; 11 | private final Map priceSymbolMapped; 12 | 13 | public PriceChangedEvent(LocalDateTime time, Map priceSymbolMapped) { 14 | this.time = time; 15 | this.priceSymbolMapped = priceSymbolMapped; 16 | } 17 | 18 | @Override 19 | public LocalDateTime getTimestamp() { 20 | return time; 21 | } 22 | 23 | @Override 24 | public Map getPrice() { 25 | return priceSymbolMapped; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/orderbook/OrderBookService.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.orderbook; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 4 | import com.apssouza.mytrade.trading.domain.forex.portfolio.FilledOrderDto; 5 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 6 | 7 | import java.io.IOException; 8 | import java.time.LocalDateTime; 9 | import java.util.List; 10 | 11 | public interface OrderBookService { 12 | List getTransactions(); 13 | 14 | void startCycle(LocalDateTime time); 15 | 16 | void endCycle(); 17 | 18 | void setState(TransactionDto.TransactionState exit, String identifier); 19 | 20 | void addPosition(PositionDto ps); 21 | 22 | void addOrderFilled(FilledOrderDto order); 23 | 24 | void addOrder(OrderDto order); 25 | 26 | void export(String filepath) throws IOException; 27 | } 28 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/riskmanagement/stopordercreation/StopOrderConfigDto.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record StopOrderConfigDto ( 6 | BigDecimal hardStopDistance, 7 | BigDecimal takeProfitDistance, 8 | BigDecimal entryStopDistance, 9 | BigDecimal traillingStopDistance 10 | ){ 11 | public StopOrderConfigDto( 12 | double hardStopDistance, 13 | double takeProfitDistance, 14 | double entryStopDistance, 15 | double traillingStopDistance 16 | ) { 17 | this( 18 | BigDecimal.valueOf(hardStopDistance), 19 | BigDecimal.valueOf(takeProfitDistance), 20 | BigDecimal.valueOf(entryStopDistance), 21 | BigDecimal.valueOf(traillingStopDistance) 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/portfolio/ReconciliationException.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.portfolio; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | class ReconciliationException extends Exception { 7 | 8 | private final List localPositions; 9 | private final Map remotePositions; 10 | 11 | public ReconciliationException( 12 | String msg, 13 | List localPositions, 14 | Map remotePositions 15 | ) { 16 | super(msg); 17 | this.localPositions = localPositions; 18 | this.remotePositions = remotePositions; 19 | } 20 | 21 | public List getLocalPositions() { 22 | return localPositions; 23 | } 24 | 25 | public Map getRemotePositions() { 26 | return remotePositions; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /forex/src/test/java/com/apssouza/mytrade/trading/domain/forex/riskmanagement/stopordercreation/RiskManagementBuilder.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.TradingParams; 4 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.RiskManagementFactory; 5 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.RiskManagementService; 6 | 7 | public class RiskManagementBuilder { 8 | 9 | public RiskManagementService build() { 10 | var stopOrder = new StopOrderConfigDto( 11 | TradingParams.hard_stop_loss_distance, 12 | TradingParams.take_profit_distance_fixed, 13 | TradingParams.entry_stop_loss_distance_fixed, 14 | TradingParams.trailing_stop_loss_distance 15 | ); 16 | return RiskManagementFactory.create(new StopOrderCreatorFixed(stopOrder)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /feed/src/main/java/com/apssouza/mytrade/feed/api/FeedModule.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.feed.api; 2 | 3 | import com.apssouza.mytrade.feed.domain.price.PriceHandler; 4 | import com.apssouza.mytrade.feed.domain.signal.SignalHandler; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public class FeedModule { 11 | 12 | private final SignalHandler signalHandler; 13 | private final PriceHandler priceHandler; 14 | 15 | public FeedModule(SignalHandler signalHandler, PriceHandler priceHandler) { 16 | this.signalHandler = signalHandler; 17 | this.priceHandler = priceHandler; 18 | } 19 | 20 | public List getSignal(String systemName, final LocalDateTime currentTime) { 21 | return signalHandler.getSignal(systemName, currentTime); 22 | } 23 | 24 | public Map getPriceSymbolMapped(LocalDateTime time) { 25 | return priceHandler.getPriceSymbolMapped(time); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /feed/src/main/java/com/apssouza/mytrade/feed/domain/price/PriceHandler.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.feed.domain.price; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | 5 | import java.time.LocalDateTime; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public class PriceHandler { 11 | private final PriceDao priceDao; 12 | 13 | public PriceHandler(PriceDao priceDao) { 14 | this.priceDao = priceDao; 15 | } 16 | 17 | public List getClosestPrice(LocalDateTime time) { 18 | return this.priceDao.getClosestPrice(time); 19 | } 20 | 21 | public Map getPriceSymbolMapped(LocalDateTime time){ 22 | List prices = this.getClosestPrice(time); 23 | Map priceDtoMap = new HashMap<>(); 24 | for (PriceDto price : prices) { 25 | priceDtoMap.put(price.symbol(), price); 26 | } 27 | return priceDtoMap; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/portfolio/FilledOrderDto.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.portfolio; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 4 | 5 | import java.math.BigDecimal; 6 | import java.time.LocalDateTime; 7 | 8 | public record FilledOrderDto ( 9 | LocalDateTime time, 10 | String symbol, 11 | OrderDto.OrderAction action, 12 | int quantity, 13 | BigDecimal priceWithSpread, 14 | String identifier, 15 | int id 16 | ){ 17 | 18 | public FilledOrderDto(int quantity, FilledOrderDto filledOrderDto) { 19 | this( 20 | filledOrderDto.time(), 21 | filledOrderDto.symbol(), 22 | filledOrderDto.action(), 23 | quantity, 24 | filledOrderDto.priceWithSpread(), 25 | filledOrderDto.identifier(), 26 | filledOrderDto.id() 27 | ); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: build & tests 2 | on: [ push ] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - name: Set up JDK 17 9 | uses: actions/setup-java@v2 10 | with: 11 | distribution: 'zulu' 12 | java-version: '17' 13 | 14 | # - name: Run sonar scanner 15 | # run: mvn verify sonar:sonar -Dsonar.java.binaries=. -Dsonar.login=${{ secrets.SONAR_TOKEN }} 16 | # env: 17 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 19 | # SONAR_ORGANIZATION: apssouza22-github 20 | # SONAR_HOST_URL: https://sonarcloud.io 21 | # SONAR_PROJECTKEY: apssouza22_trading-system 22 | 23 | - name: Build with Maven 24 | run: mvn verify 25 | 26 | - name: Publish to GitHub Packages Apache Maven 27 | run: mvn --batch-mode deploy 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /common/src/main/java/com/apssouza/mytrade/common/file/WriteFileHelper.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.common.file; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.FileWriter; 5 | import java.io.IOException; 6 | 7 | public class WriteFileHelper { 8 | 9 | public static void write(String filepath, String content) { 10 | try { 11 | BufferedWriter writer = new BufferedWriter(new FileWriter(filepath)); 12 | writer.write(content); 13 | writer.close(); 14 | } catch (IOException e) { 15 | throw new RuntimeException(e); 16 | } 17 | } 18 | 19 | public static void append(String filepath, String content) { 20 | BufferedWriter writer = null; 21 | try { 22 | writer = new BufferedWriter(new FileWriter(filepath, true)); 23 | writer.append(content); 24 | writer.close(); 25 | } catch (IOException e) { 26 | throw new RuntimeException(e); 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /forex/src/test/java/com/apssouza/mytrade/trading/domain/forex/feed/PriceBuilder.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.feed; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | 5 | import java.math.BigDecimal; 6 | import java.time.LocalDateTime; 7 | import java.util.HashMap; 8 | 9 | public class PriceBuilder { 10 | 11 | private String pair = "AUDUSD"; 12 | private BigDecimal price = BigDecimal.valueOf(1.305); 13 | 14 | 15 | public PriceBuilder withPrice(String pair, BigDecimal price) { 16 | this.pair = pair; 17 | this.price = price; 18 | return this; 19 | } 20 | 21 | public HashMap builderMap() { 22 | PriceDto priceDto = build(); 23 | HashMap priceMap = new HashMap<>(); 24 | priceMap.put("AUDUSD", priceDto); 25 | return priceMap; 26 | } 27 | 28 | public PriceDto build() { 29 | return new PriceDto(LocalDateTime.now(), price, price, price, price, pair); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/orderbook/OrderBookFactory.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.orderbook; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.Observer; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class OrderBookFactory { 9 | 10 | public static OrderBookService create() { 11 | return new OrderBookServiceImpl(new TransactionsExporter()); 12 | } 13 | 14 | public static List createListeners( 15 | OrderBookService bookHandler 16 | ) { 17 | var listeners = new ArrayList(); 18 | listeners.add(new HistoryStopOrderFilledListener(bookHandler)); 19 | listeners.add(new HistoryFilledOrderListener(bookHandler)); 20 | listeners.add(new OrderCreatedListener(bookHandler)); 21 | listeners.add(new SessionFinishedListener(bookHandler)); 22 | listeners.add(new HistoryPortfolioChangedListener(bookHandler)); 23 | return listeners; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/orderbook/HistoryFilledOrderListener.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.orderbook; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 4 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.Observer; 5 | import com.apssouza.mytrade.trading.domain.forex.common.events.OrderFilledEvent; 6 | import com.apssouza.mytrade.trading.domain.forex.portfolio.FilledOrderDto; 7 | 8 | 9 | class HistoryFilledOrderListener implements Observer { 10 | 11 | private final OrderBookService historyHandler; 12 | 13 | public HistoryFilledOrderListener(OrderBookService historyHandler) { 14 | this.historyHandler = historyHandler; 15 | } 16 | 17 | @Override 18 | public void update(final Event e) { 19 | if (!(e instanceof OrderFilledEvent event)) { 20 | return; 21 | } 22 | FilledOrderDto filledOrder = event.getFilledOrder(); 23 | this.historyHandler.addOrderFilled(filledOrder); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/feed/pricefeed/PriceStreamFactory.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.feed.pricefeed; 2 | 3 | import com.apssouza.mytrade.trading.api.SessionType; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 5 | import com.apssouza.mytrade.trading.domain.forex.feed.FeedService; 6 | 7 | import java.util.concurrent.BlockingQueue; 8 | 9 | 10 | /** 11 | * Price feed stream API factory 12 | */ 13 | public class PriceStreamFactory { 14 | 15 | public static PriceStream create( 16 | SessionType sessionType, 17 | BlockingQueue eventQueue, 18 | FeedService feed 19 | ) { 20 | var priceFeedHandler = new PriceFeedHandler(feed); 21 | if (sessionType == SessionType.LIVE) { 22 | return new RealtimePriceStream( 23 | eventQueue, 24 | priceFeedHandler 25 | ); 26 | } 27 | return new HistoricalPriceStream(eventQueue, priceFeedHandler); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/riskmanagement/RiskManagementService.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.riskmanagement; 2 | 3 | import com.apssouza.mytrade.feed.api.SignalDto; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 5 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 6 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 7 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation.StopOrderDto; 8 | 9 | import java.util.EnumMap; 10 | import java.util.List; 11 | 12 | public interface RiskManagementService { 13 | 14 | EnumMap createStopOrders(PositionDto position, Event event); 15 | List getExitPositions(List positions, List signals); 16 | boolean canCreateOrder(OrderDto order, List openPositions); 17 | boolean canExecuteOrder(Event event, OrderDto order, List processedOrders, List exitedPositions); 18 | int getPositionSize(); 19 | } 20 | -------------------------------------------------------------------------------- /common/src/main/java/com/apssouza/mytrade/common/time/DateTimeConverter.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.common.time; 2 | 3 | 4 | import java.time.LocalDate; 5 | import java.time.LocalDateTime; 6 | import java.time.ZoneId; 7 | import java.time.format.DateTimeFormatter; 8 | import java.util.Date; 9 | 10 | /** 11 | * Date time converter helper class 12 | */ 13 | public class DateTimeConverter { 14 | 15 | 16 | public static Date getDateFromLocalDate(LocalDate localDate) { 17 | return java.sql.Date.valueOf(localDate); 18 | } 19 | 20 | public static LocalDate getLocalDateFromDate(Date date) { 21 | return new java.sql.Date(date.getTime()).toLocalDate(); 22 | } 23 | 24 | public static LocalDateTime getLocalDateTimeFromDate(Date date) { 25 | return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); 26 | } 27 | 28 | 29 | 30 | public static String getDatabaseFormat(LocalDateTime time) { 31 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); 32 | return time.format(formatter); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /common/src/test/java/com/apssouza/mytrade/common/encrypt/AesPBEStringEncryptorTest.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.common.encrypt; 2 | 3 | import org.junit.Test; 4 | import static org.junit.Assert.*; 5 | 6 | public class AesPBEStringEncryptorTest { 7 | 8 | @Test 9 | public void testEncrypt_with_default_settings() throws Exception { 10 | var encryptBuilder = new AESPBEStringEncryptor.Builder(); 11 | var encryptor = encryptBuilder.build("secretKey"); 12 | String myPassEncrypted = encryptor.encrypt("myPass"); 13 | assertEquals("myPass",encryptor.decrypt(myPassEncrypted)); 14 | } 15 | 16 | 17 | @Test 18 | public void testEncrypt_with_custom_settings() throws Exception { 19 | var encryptBuilder = new AESPBEStringEncryptor.Builder(); 20 | encryptBuilder.withSalt("test") 21 | .withIterationCount(2000) 22 | .withKeyLength(256); 23 | var encryptor = encryptBuilder.build("secretKey"); 24 | String myPassEncrypted = encryptor.encrypt("myPass"); 25 | assertEquals("myPass",encryptor.decrypt(myPassEncrypted)); 26 | } 27 | } -------------------------------------------------------------------------------- /forex/src/test/java/com/apssouza/mytrade/trading/domain/forex/session/LoopEventBuilder.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.session; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.PriceChangedEvent; 5 | 6 | import java.math.BigDecimal; 7 | import java.time.LocalDateTime; 8 | import java.util.HashMap; 9 | 10 | public class LoopEventBuilder { 11 | 12 | HashMap priceMap = new HashMap<>(); 13 | LocalDateTime time; 14 | 15 | public LoopEventBuilder withPriceMap(BigDecimal close) { 16 | if (time == null) 17 | time = LocalDateTime.MIN; 18 | PriceDto priceDto = new PriceDto(time, close, close, close, close, "AUDUSD"); 19 | priceMap.put("AUDUSD", priceDto); 20 | return this; 21 | } 22 | 23 | public LoopEventBuilder withTime(LocalDateTime time) { 24 | this.time = time; 25 | return this; 26 | } 27 | 28 | public PriceChangedEvent build() { 29 | if (time == null) 30 | time = LocalDateTime.MIN; 31 | return new PriceChangedEvent( time, priceMap); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/orderbook/SessionFinishedListener.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.orderbook; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 4 | import com.apssouza.mytrade.trading.domain.forex.common.TradingParams; 5 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.Observer; 6 | import com.apssouza.mytrade.trading.domain.forex.common.events.SessionFinishedEvent; 7 | 8 | import java.io.IOException; 9 | 10 | class SessionFinishedListener implements Observer { 11 | 12 | private final OrderBookService historyHandler; 13 | 14 | public SessionFinishedListener(OrderBookService historyHandler) { 15 | this.historyHandler = historyHandler; 16 | } 17 | 18 | @Override 19 | public void update(final Event e) { 20 | if (!(e instanceof SessionFinishedEvent event)) { 21 | return; 22 | } 23 | try { 24 | historyHandler.export(TradingParams.transaction_path); 25 | } catch (IOException ioe) { 26 | ioe.printStackTrace(); 27 | } 28 | System.out.println("Finished session"); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /forex/src/test/java/com/apssouza/mytrade/trading/domain/forex/portfolio/PortfolioHandlerBuilder.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.portfolio; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.brokerintegration.BrokerIntegrationService; 4 | import com.apssouza.mytrade.trading.domain.forex.order.OrderService; 5 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.RiskManagementService; 6 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.EventNotifier; 7 | 8 | import static org.mockito.Mockito.mock; 9 | 10 | public class PortfolioHandlerBuilder { 11 | OrderService orderService = mock(OrderService.class); 12 | BrokerIntegrationService brokerIntegrationService = mock(BrokerIntegrationService.class); 13 | RiskManagementService riskManagementService = mock(RiskManagementService.class); 14 | 15 | EventNotifier eventNotifier = mock(EventNotifier.class); 16 | 17 | public PortfolioService build() { 18 | return PortfolioFactory.create( 19 | orderService, 20 | brokerIntegrationService, 21 | riskManagementService, 22 | eventNotifier 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/portfolio/EndedTradingDayListener.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.portfolio; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 4 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.Observer; 5 | import com.apssouza.mytrade.trading.domain.forex.common.events.EndedTradingDayEvent; 6 | 7 | import java.util.List; 8 | import java.util.logging.Logger; 9 | 10 | class EndedTradingDayListener implements Observer { 11 | 12 | private final PortfolioService portfolioService; 13 | 14 | private static Logger log = Logger.getLogger(EndedTradingDayListener.class.getSimpleName()); 15 | 16 | public EndedTradingDayListener(PortfolioService portfolioService) { 17 | this.portfolioService = portfolioService; 18 | } 19 | 20 | @Override 21 | public void update(final Event e) { 22 | if (!(e instanceof EndedTradingDayEvent event)) { 23 | return; 24 | } 25 | 26 | List positions = portfolioService.closeAllPositions(PositionDto.ExitReason.END_OF_DAY, event); 27 | log.info(positions.size() + " positions closed"); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /feed/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.apssouza.mytrade 9 | trading-system-parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | com.apssouza.mytrade 14 | feed 15 | 1.0-SNAPSHOT 16 | 17 | 18 | 19 | com.apssouza.mytrade 20 | common 21 | 22 | 23 | 24 | io.cucumber 25 | cucumber-java 26 | 27 | 28 | io.cucumber 29 | cucumber-junit 30 | 31 | 32 | junit 33 | junit 34 | 4.13.1 35 | test 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/api/TransactionDto.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.api; 2 | 3 | import java.time.format.DateTimeFormatter; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | public record TransactionDto(com.apssouza.mytrade.trading.domain.forex.orderbook.TransactionDto event 8 | ) { 9 | public Map getTransactionTable() { 10 | var table = new HashMap(); 11 | if (event.getOrder() == null) { 12 | table.put("time", event.getTime().format(DateTimeFormatter.ISO_DATE_TIME)); 13 | table.put("created order - symbol", event.getOrder().symbol()); 14 | table.put("created order - action", event.getOrder().action().name()); 15 | table.put("created order - origin", event.getOrder().origin().name()); 16 | table.put("created order - qtd", String.valueOf(event.getOrder().quantity())); 17 | } 18 | if (event.getFilledOrder() != null) { 19 | table.put("Order filled - symbol", event.getFilledOrder().symbol()); 20 | table.put("Order filled - action", event.getFilledOrder().action().name()); 21 | table.put("Order filled - price with spread", event.getFilledOrder().priceWithSpread().toPlainString()); 22 | } 23 | return table; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /runner/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | time 8 | 9 | 10 | 11 | 12 | 13 | 14 | {"service":"forex-runner","class-name":"%c"} 15 | 16 | 17 | 18 | msg 19 | 20 | 21 | 22 | 30 23 | 2048 24 | 20 25 | ^sun\.reflect\..*\.invoke 26 | ^net\.sf\.cglib\.proxy\.MethodProxy\.invoke 27 | true 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /common/src/main/java/com/apssouza/mytrade/common/file/ReadFileHelper.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.common.file; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.nio.file.Files; 8 | import java.nio.file.Paths; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | import java.util.stream.Stream; 12 | 13 | public class ReadFileHelper { 14 | 15 | public static Stream getStream(String path) { 16 | try (BufferedReader br = Files.newBufferedReader(Paths.get(path))) { 17 | return br.lines(); 18 | } catch (IOException e) { 19 | throw new RuntimeException(e); 20 | } 21 | } 22 | 23 | public static List getList(String path) { 24 | Stream stream = getStream(path); 25 | return stream.collect(Collectors.toList()); 26 | } 27 | 28 | public static String getString(String path) { 29 | Stream stream = getStream(path); 30 | return stream.collect(Collectors.joining("\n")); 31 | } 32 | 33 | public static Stream readFromInputStream(InputStream inputStream) { 34 | try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) { 35 | return br.lines(); 36 | } catch (IOException e) { 37 | throw new RuntimeException(e); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/TradingHelper.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 4 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 5 | import com.apssouza.mytrade.common.time.DayHelper; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | public class TradingHelper { 10 | 11 | public static boolean isTradingTime(LocalDateTime currentTime) { 12 | if (DayHelper.isWeekend(currentTime.toLocalDate())) 13 | return false; 14 | 15 | if (TradingParams.tradingStartTime.compareTo(currentTime.toLocalTime()) > 0) 16 | return false; 17 | if (TradingParams.tradingEndTime.compareTo(currentTime.toLocalTime()) < 0) 18 | return false; 19 | return true; 20 | } 21 | 22 | public static boolean hasEndedTradingTime(LocalDateTime currentTime) { 23 | if (TradingParams.tradingEndTime.compareTo(currentTime.toLocalTime()) >= 0) 24 | return false; 25 | return true; 26 | } 27 | 28 | 29 | 30 | public static OrderDto.OrderAction getExitOrderActionFromPosition(PositionDto position) { 31 | OrderDto.OrderAction action = OrderDto.OrderAction.BUY; 32 | if (position.positionType() == PositionDto.PositionType.LONG) { 33 | action = OrderDto.OrderAction.SELL; 34 | } 35 | return action; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /common/src/main/java/com/apssouza/mytrade/common/file/FileDiskHelper.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.common.file; 2 | 3 | 4 | import java.io.Closeable; 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.util.logging.Logger; 8 | 9 | /** 10 | * A helper for working with files 11 | */ 12 | public class FileDiskHelper { 13 | 14 | private static final Logger LOG = Logger.getLogger(FileDiskHelper.class.getSimpleName()); 15 | 16 | public static void createFolder(String path) { 17 | File folder = new File(path); 18 | if (!folder.exists()) { 19 | folder.mkdirs(); 20 | } 21 | } 22 | 23 | public static boolean isFile(String path) { 24 | File file = new File(path); 25 | return file.isFile(); 26 | } 27 | 28 | public static boolean isDirectory(String path) { 29 | File file = new File(path); 30 | return file.isDirectory(); 31 | } 32 | 33 | public static boolean delete(String filename) { 34 | try { 35 | File file = new File(filename); 36 | return file.delete(); 37 | } catch (Exception e) { 38 | LOG.severe("Error in deleting the file " + filename); 39 | } 40 | return false; 41 | } 42 | 43 | private static void close(Closeable o) { 44 | try { 45 | if (o != null) { 46 | o.close(); 47 | } 48 | } catch (IOException ex) { 49 | LOG.severe("Error in closing resource" + ex.getMessage()); 50 | } 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/TradingParams.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common; 2 | 3 | import java.time.LocalTime; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | public class TradingParams { 8 | public static String brokerHost; 9 | public static String brokerPort; 10 | public static String brokerClientId; 11 | public static LocalTime tradingStartTime = LocalTime.of(8, 0); 12 | public static LocalTime tradingEndTime = LocalTime.of(20, 0); 13 | public static boolean trading_multi_position_enabled = false; 14 | public static boolean trading_position_edit_enabled = false; 15 | public static boolean hard_stop_loss_enabled = true; 16 | public static boolean entry_stop_loss_enabled = true; 17 | public static boolean trailing_stop_loss_enabled = true; 18 | public static boolean take_profit_stop_enabled = true; 19 | public static Map currency_pair_significant_digits_in_price; 20 | public static double hard_stop_loss_distance = 0.1; 21 | public static double trailing_stop_loss_distance = 1; 22 | public static double take_profit_distance_fixed = 0.1; 23 | public static double entry_stop_loss_distance_fixed = 1; 24 | public static String systemName = "signal_test"; 25 | public static String transaction_path = "book-history.csv"; 26 | 27 | static { 28 | currency_pair_significant_digits_in_price = new HashMap<>(); 29 | currency_pair_significant_digits_in_price.put("AUDUSD", 4); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/orderbook/HistoryStopOrderFilledListener.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.orderbook; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 4 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.Observer; 5 | import com.apssouza.mytrade.trading.domain.forex.portfolio.FilledOrderDto; 6 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation.StopOrderDto; 7 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation.StopOrderFilledEvent; 8 | 9 | import java.time.LocalDateTime; 10 | 11 | class HistoryStopOrderFilledListener implements Observer { 12 | 13 | private final OrderBookService historyHandler; 14 | 15 | public HistoryStopOrderFilledListener(OrderBookService historyHandler) { 16 | this.historyHandler = historyHandler; 17 | } 18 | 19 | @Override 20 | public void update(final Event e) { 21 | if (!(e instanceof StopOrderFilledEvent event)) { 22 | return; 23 | } 24 | 25 | StopOrderDto stopOrder = event.getStopOrder(); 26 | LocalDateTime time = event.getTimestamp(); 27 | 28 | this.historyHandler.addOrderFilled(new FilledOrderDto( 29 | time, 30 | stopOrder.symbol(), 31 | stopOrder.action(), 32 | stopOrder.quantity(), 33 | stopOrder.filledPrice(), 34 | "", 35 | stopOrder.id() 36 | )); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /forex/src/test/java/com/apssouza/mytrade/trading/domain/forex/portfolio/FilledOrderBuilder.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.portfolio; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 4 | 5 | import java.math.BigDecimal; 6 | import java.time.LocalDateTime; 7 | 8 | public class FilledOrderBuilder { 9 | 10 | private LocalDateTime time = LocalDateTime.MIN; 11 | private String symbol = "AUDUSD"; 12 | private OrderDto.OrderAction action = OrderDto.OrderAction.BUY; 13 | private int quantity = 100; 14 | private BigDecimal priceWithSpread = BigDecimal.TEN; 15 | private String identifier = "AUDUSD"; 16 | private Integer id = 1; 17 | 18 | public void withTime(LocalDateTime time) { 19 | this.time = time; 20 | } 21 | 22 | public void withSymbol(String symbol) { 23 | this.symbol = symbol; 24 | } 25 | 26 | public void withAction(OrderDto.OrderAction action) { 27 | this.action = action; 28 | } 29 | 30 | public void withQuantity(int quantity) { 31 | this.quantity = quantity; 32 | } 33 | 34 | public void withPriceWithSpread(BigDecimal priceWithSpread) { 35 | this.priceWithSpread = priceWithSpread; 36 | } 37 | 38 | public void withIdentifier(String identifier) { 39 | this.identifier = identifier; 40 | } 41 | 42 | public void withId(Integer id) { 43 | this.id = id; 44 | } 45 | 46 | public FilledOrderDto build() { 47 | return new FilledOrderDto(time, symbol, action, quantity, priceWithSpread, identifier, id); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /runner/src/main/java/com/apssouza/mytrade/runner/Application.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.runner; 2 | 3 | import com.apssouza.mytrade.trading.api.ExecutionType; 4 | import com.apssouza.mytrade.trading.api.ForexBuilder; 5 | import com.apssouza.mytrade.trading.api.SessionType; 6 | 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | import java.math.BigDecimal; 12 | import java.time.LocalDateTime; 13 | import java.time.LocalTime; 14 | import static java.time.LocalDate.of; 15 | 16 | 17 | @SpringBootApplication 18 | @Configuration 19 | public class Application { 20 | 21 | 22 | public static void main(String[] args) { 23 | // var springApplication = new SpringApplication(Application.class, Application.class); 24 | // springApplication.run(args); 25 | 26 | var date = of(2018, 9, 10); 27 | var systemName = "signal_test"; 28 | LocalDateTime start = LocalDateTime.of(date.minusDays(20), LocalTime.MIN); 29 | LocalDateTime end = LocalDateTime.of(date.plusDays(6), LocalTime.MIN); 30 | 31 | 32 | var engine = new ForexBuilder() 33 | .withSystemName(systemName) 34 | .withStartTime(start) 35 | .withEndTime(end) 36 | .withEquity(BigDecimal.valueOf(100000L)) 37 | .withSessionType(SessionType.BACK_TEST) 38 | .withExecutionType(ExecutionType.SIMULATED) 39 | .build(); 40 | engine.start(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /common/src/main/java/com/apssouza/mytrade/common/time/DateRangeHelper.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.common.time; 2 | 3 | import java.time.LocalDate; 4 | import java.time.LocalDateTime; 5 | import java.time.temporal.ChronoUnit; 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | import java.util.stream.IntStream; 9 | 10 | public class DateRangeHelper { 11 | 12 | public static List getDatesBetween(LocalDate startDate, LocalDate endDate) { 13 | 14 | long intervalBetween = ChronoUnit.DAYS.between(startDate, endDate); 15 | return IntStream.iterate(0, i -> i + 1) 16 | .limit(intervalBetween) 17 | .mapToObj(i -> startDate.plusDays(i)) 18 | .collect(Collectors.toList()); 19 | } 20 | 21 | public static List getMinutesBetween(LocalDateTime startDate, LocalDateTime endDate) { 22 | long intervalBetween = ChronoUnit.MINUTES.between(startDate, endDate); 23 | return IntStream.iterate(0, i -> i + 1) 24 | .limit(intervalBetween) 25 | .mapToObj(i -> startDate.plusMinutes(i)) 26 | .collect(Collectors.toList()); 27 | } 28 | 29 | public static List getSecondsBetween(LocalDateTime startDate, LocalDateTime endDate) { 30 | long intervalBetween = ChronoUnit.SECONDS.between(startDate, endDate); 31 | return IntStream.iterate(0, i -> i + 1) 32 | .limit(intervalBetween) 33 | .mapToObj(i -> startDate.plusSeconds(i)) 34 | .collect(Collectors.toList()); 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/portfolio/PortfolioService.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.portfolio; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.feed.api.SignalDto; 5 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 6 | import com.apssouza.mytrade.trading.domain.forex.common.events.PriceChangedEvent; 7 | 8 | import java.math.BigDecimal; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | public interface PortfolioService { 13 | 14 | PositionDto addPositionQtd(String identifier, int qtd, BigDecimal price) throws PortfolioException ; 15 | PositionDto getPosition(final String identifier); 16 | PositionDto addNewPosition(PositionDto.PositionType positionType, FilledOrderDto filledOrder); 17 | List getPositions(); 18 | List closeAllPositions(PositionDto.ExitReason reason, Event event) ; 19 | 20 | void createStopOrder(Event event); 21 | void handleStopOrder(Event event); 22 | void updatePositionsPrices(Map price) ; 23 | void checkExits(PriceChangedEvent event, List signals) ; 24 | /** 25 | * Check if the local portfolio is in sync with the portfolio on the broker 26 | */ 27 | void processReconciliation(Event e) ; 28 | boolean isEmpty(); 29 | int size(); 30 | boolean removePositionQtd(String identfier, int qtd) throws PortfolioException; 31 | boolean contains(String identifier); 32 | boolean closePosition(String identifier, PositionDto.ExitReason reason); 33 | void printPortfolio(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /common/src/main/java/com/apssouza/mytrade/common/time/Interval.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.common.time; 2 | 3 | /** 4 | * 5 | */ 6 | public class Interval { 7 | 8 | private final long years; 9 | private final long months; 10 | private final long weeks; 11 | private final long days; 12 | private final long hours; 13 | private final long minutes; 14 | private final long seconds; 15 | private final long milliseconds; 16 | 17 | public Interval( 18 | long years, 19 | long months, 20 | long weekends, 21 | long days, 22 | long hours, 23 | long minutes, 24 | long seconds, 25 | long milliseconds 26 | ) { 27 | this.years = years; 28 | this.months = months; 29 | this.weeks = weekends; 30 | this.days = days; 31 | this.hours = hours; 32 | this.minutes = minutes; 33 | this.seconds = seconds; 34 | this.milliseconds = milliseconds; 35 | } 36 | 37 | public long getDays() { 38 | return days; 39 | } 40 | 41 | public long getHours() { 42 | return hours; 43 | } 44 | 45 | public long getMinutes() { 46 | return minutes; 47 | } 48 | 49 | public long getSeconds() { 50 | return seconds; 51 | } 52 | 53 | public long getMilliseconds() { 54 | return milliseconds; 55 | } 56 | 57 | public long getYears() { 58 | return years; 59 | } 60 | 61 | public long getMonths() { 62 | return months; 63 | } 64 | 65 | public long getWeeks() { 66 | return weeks; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /forex/src/test/java/moduletest/FeedMockBuilder.java: -------------------------------------------------------------------------------- 1 | package moduletest; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.feed.api.SignalDto; 5 | import com.apssouza.mytrade.trading.domain.forex.feed.FeedService; 6 | 7 | import static org.mockito.ArgumentMatchers.any; 8 | import static org.mockito.ArgumentMatchers.eq; 9 | import static org.mockito.Mockito.mock; 10 | import static org.mockito.Mockito.when; 11 | 12 | import java.math.BigDecimal; 13 | import java.time.LocalDateTime; 14 | import java.util.HashMap; 15 | import static java.util.Collections.singletonList; 16 | 17 | public class FeedMockBuilder { 18 | FeedService feed = mock(FeedService.class); 19 | HashMap priceDtoHashMap = new HashMap<>(); 20 | 21 | public FeedMockBuilder withPrice(LocalDateTime time) { 22 | priceDtoHashMap.put("AUDUSD", new PriceDto( 23 | time, 24 | BigDecimal.ONE, 25 | BigDecimal.ONE, 26 | BigDecimal.ONE, 27 | BigDecimal.ONE, 28 | "AUDUSD" 29 | )); 30 | return this; 31 | } 32 | public FeedMockBuilder withSignal(LocalDateTime time, String action) { 33 | when(feed.getSignal(any(), eq(time.plusSeconds(2)))).thenReturn(singletonList(new SignalDto( 34 | time.plusSeconds(2), 35 | action, 36 | "AUDUSD", 37 | "signal_test" 38 | ))); 39 | return this; 40 | } 41 | 42 | public FeedService build() { 43 | when(feed.getPriceSymbolMapped(any())).thenReturn(priceDtoHashMap); 44 | return feed; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /feed/src/test/java/moduletest/DoesntExistSignal.java: -------------------------------------------------------------------------------- 1 | package moduletest; 2 | 3 | 4 | import com.apssouza.mytrade.feed.api.FeedBuilder; 5 | import com.apssouza.mytrade.feed.api.FeedModule; 6 | import com.apssouza.mytrade.feed.api.SignalDto; 7 | 8 | import org.junit.Assert; 9 | 10 | import java.time.LocalDateTime; 11 | import java.time.LocalTime; 12 | import java.util.List; 13 | import static java.time.LocalDate.of; 14 | 15 | import io.cucumber.java.en.Given; 16 | import io.cucumber.java.en.Then; 17 | import io.cucumber.java.en.When; 18 | 19 | public class DoesntExistSignal { 20 | private FeedModule feed; 21 | private List signals; 22 | 23 | @Given("that exists signal to the given time but not for the given system") 24 | public void that_exists_signal_to_the_given_time_but_not_for_the_given_system() { 25 | var date = of(2018, 9, 10); 26 | this.feed = new FeedBuilder() 27 | .withStartTime(LocalDateTime.of(date.minusDays(20), LocalTime.MIN)) 28 | .withEndTime(LocalDateTime.of(date.plusDays(6), LocalTime.MIN)) 29 | .withSignalName("system-test") 30 | .build(); 31 | 32 | } 33 | 34 | @When("^try to retrieve signal to system \"([^\"]*)\" and (\\d+)-(\\d+)-(\\d+) (\\d+):(\\d+):(\\d+)$") 35 | public void tryToRetrieveSignalToAnd(String systemName, int year, int month, int day, int hour, int minute, int second){ 36 | this.signals = feed.getSignal(systemName, LocalDateTime.of(year, month, day, hour, minute, second)); 37 | } 38 | 39 | @Then("return no signal") 40 | public void return_no_signal() { 41 | Assert.assertEquals(0, signals.size()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/portfolio/PortfolioFactory.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.portfolio; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.brokerintegration.BrokerIntegrationService; 4 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.Observer; 5 | import com.apssouza.mytrade.trading.domain.forex.order.OrderService; 6 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.RiskManagementService; 7 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.EventNotifier; 8 | 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | public class PortfolioFactory { 13 | 14 | public static PortfolioService create( 15 | OrderService orderService, 16 | BrokerIntegrationService executionHandler, 17 | RiskManagementService riskManagementService, 18 | EventNotifier eventNotifier 19 | ) { 20 | var reconciliationHandler = new PortfolioChecker(executionHandler); 21 | return new PortfolioServiceImpl( 22 | orderService, 23 | executionHandler, 24 | reconciliationHandler, 25 | riskManagementService, 26 | eventNotifier 27 | ); 28 | } 29 | 30 | public static List createListeners( 31 | PortfolioService portfolioService, 32 | EventNotifier eventNotifier 33 | ) { 34 | return Arrays.asList( 35 | new FilledOrderListener(portfolioService), 36 | new StopOrderFilledListener(portfolioService,eventNotifier), 37 | new EndedTradingDayListener(portfolioService) 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/riskmanagement/stopordercreation/StopOrderDto.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 4 | 5 | import java.math.BigDecimal; 6 | 7 | public record StopOrderDto ( 8 | StopOrderType type, 9 | Integer id, 10 | StopOrderStatus status, 11 | OrderDto.OrderAction action, 12 | BigDecimal price, 13 | BigDecimal filledPrice, 14 | String symbol, 15 | int quantity, 16 | String identifier 17 | ){ 18 | 19 | public StopOrderDto(StopOrderStatus status, StopOrderDto stop) { 20 | this( 21 | stop.type(), 22 | stop.id(), 23 | status, 24 | stop.action(), 25 | stop.price(), 26 | null, 27 | stop.symbol(), 28 | stop.quantity(), 29 | stop.identifier() 30 | ); 31 | } 32 | 33 | public StopOrderDto(StopOrderStatus status, BigDecimal filledPrice, StopOrderDto stop) { 34 | this( 35 | stop.type(), 36 | stop.id(), 37 | status, 38 | stop.action(), 39 | stop.price(), 40 | filledPrice, 41 | stop.symbol(), 42 | stop.quantity(), 43 | stop.identifier() 44 | ); 45 | 46 | } 47 | 48 | public enum StopOrderType { 49 | HARD_STOP, ENTRY_STOP, TRAILLING_STOP, STOP_LOSS, TAKE_PROFIT 50 | } 51 | 52 | public enum StopOrderStatus { 53 | FILLED, CANCELLED, OPENED,SUBMITTED, CREATED; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/order/OrderDto.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.order; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public record OrderDto( 6 | String symbol, 7 | OrderAction action, 8 | int quantity, 9 | OrderOrigin origin, 10 | LocalDateTime time, 11 | String identifier, 12 | OrderStatus status, 13 | int id 14 | ) { 15 | public OrderDto( 16 | String symbol, 17 | OrderAction action, 18 | int quantity, 19 | OrderOrigin origin, 20 | LocalDateTime time, 21 | String identifier, 22 | OrderStatus status 23 | ){ 24 | this(symbol, action, quantity, origin, time, identifier, status, 0); 25 | } 26 | 27 | public OrderDto( 28 | Integer id, 29 | OrderDto order 30 | ) { 31 | this(order.symbol(), order.action(), order.quantity(), order.origin(), order.time(), order.identifier(), order.status(), id); 32 | } 33 | 34 | public OrderDto(String identifierFromOrder, OrderDto order) { 35 | this(order.symbol(), order.action(), order.quantity(), order.origin(), order.time(), identifierFromOrder, order.status(), order.id()); 36 | } 37 | 38 | public OrderDto(OrderStatus status, OrderDto order) { 39 | this(order.symbol(), order.action(), order.quantity(), order.origin(), order.time(), order.identifier(), status, order.id()); 40 | } 41 | 42 | public enum OrderOrigin { 43 | STOP_ORDER, EXITS, SIGNAL 44 | } 45 | 46 | public enum OrderStatus { 47 | CREATED, FILLED, FAILED, EXECUTED, PROCESSING, CANCELLED 48 | } 49 | 50 | public enum OrderAction { 51 | BUY, SELL 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/common/Currency.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.common; 2 | 3 | 4 | public enum Currency { 5 | 6 | XAU(java.util.Currency.getInstance("XAU")), 7 | XAG(java.util.Currency.getInstance("XAG")), 8 | 9 | EUR(java.util.Currency.getInstance("EUR")), 10 | GBP(java.util.Currency.getInstance("GBP")), 11 | AUD(java.util.Currency.getInstance("AUD")), 12 | NZD(java.util.Currency.getInstance("NZD")), 13 | 14 | USD(java.util.Currency.getInstance("USD")), 15 | 16 | BRL(java.util.Currency.getInstance("BRL")), 17 | CAD(java.util.Currency.getInstance("CAD")), 18 | // use CNY for CNH 19 | CNH(java.util.Currency.getInstance("CNY")), 20 | CNY(java.util.Currency.getInstance("CNY")), 21 | CZK(java.util.Currency.getInstance("CZK")), 22 | DKK(java.util.Currency.getInstance("DKK")), 23 | HKD(java.util.Currency.getInstance("HKD")), 24 | HUF(java.util.Currency.getInstance("HUF")), 25 | MXN(java.util.Currency.getInstance("MXN")), 26 | NOK(java.util.Currency.getInstance("NOK")), 27 | PLN(java.util.Currency.getInstance("PLN")), 28 | RON(java.util.Currency.getInstance("RON")), 29 | RUB(java.util.Currency.getInstance("RUB")), 30 | SEK(java.util.Currency.getInstance("SEK")), 31 | SGD(java.util.Currency.getInstance("SGD")), 32 | TRY(java.util.Currency.getInstance("TRY")), 33 | ZAR(java.util.Currency.getInstance("ZAR")), 34 | 35 | CHF(java.util.Currency.getInstance("CHF")), 36 | JPY(java.util.Currency.getInstance("JPY")); 37 | 38 | private java.util.Currency currency; 39 | 40 | Currency(java.util.Currency currency) { 41 | this.currency = currency; 42 | } 43 | 44 | public java.util.Currency getCurrency() { 45 | return currency; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /common/src/main/java/com/apssouza/mytrade/common/file/CSVHelper.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.common.file; 2 | 3 | import java.io.IOException; 4 | import java.io.Writer; 5 | import java.util.List; 6 | 7 | public class CSVHelper { 8 | 9 | 10 | private static final char DEFAULT_SEPARATOR = ','; 11 | 12 | public static void writeLine(Writer w, List values) { 13 | writeLine(w, values, DEFAULT_SEPARATOR, ' '); 14 | } 15 | 16 | public static void writeLine(Writer w, List values, char separators) { 17 | writeLine(w, values, separators, ' '); 18 | } 19 | 20 | private static String followCVSformat(String value) { 21 | 22 | String result = value; 23 | if (result.contains("\"")) { 24 | result = result.replace("\"", "\"\""); 25 | } 26 | return result; 27 | 28 | } 29 | 30 | public static void writeLine(Writer w, List values, char separators, char customQuote) { 31 | boolean first = true; 32 | //default customQuote is empty 33 | if (separators == ' ') { 34 | separators = DEFAULT_SEPARATOR; 35 | } 36 | 37 | StringBuilder sb = new StringBuilder(); 38 | for (String value : values) { 39 | if (!first) { 40 | sb.append(separators); 41 | } 42 | if (customQuote == ' ') { 43 | sb.append(followCVSformat(value)); 44 | } else { 45 | sb.append(customQuote).append(followCVSformat(value)).append(customQuote); 46 | } 47 | 48 | first = false; 49 | } 50 | sb.append("\n"); 51 | try { 52 | w.append(sb.toString()); 53 | } catch (IOException e) { 54 | throw new RuntimeException(e); 55 | } 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /feed/src/main/java/com/apssouza/mytrade/feed/api/FeedBuilder.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.feed.api; 2 | 3 | import com.apssouza.mytrade.feed.domain.price.MemoryPriceDao; 4 | import com.apssouza.mytrade.feed.domain.price.PriceHandler; 5 | import com.apssouza.mytrade.feed.domain.price.SqlPriceDao; 6 | import com.apssouza.mytrade.feed.domain.signal.MemorySignalDao; 7 | import com.apssouza.mytrade.feed.domain.signal.SignalHandler; 8 | import com.apssouza.mytrade.feed.domain.signal.SqlSignalDao; 9 | 10 | import java.sql.Connection; 11 | import java.time.LocalDateTime; 12 | 13 | public class FeedBuilder { 14 | 15 | private Connection connection; 16 | private String signalName; 17 | private LocalDateTime end; 18 | private LocalDateTime start; 19 | 20 | public FeedBuilder withConnection(Connection connection) { 21 | this.connection = connection; 22 | return this; 23 | } 24 | 25 | public FeedBuilder withStartTime(LocalDateTime start) { 26 | this.start = start; 27 | return this; 28 | } 29 | 30 | public FeedBuilder withEndTime(LocalDateTime end) { 31 | this.end = end; 32 | return this; 33 | } 34 | 35 | public FeedBuilder withSignalName(String signalName) { 36 | this.signalName = signalName; 37 | return this; 38 | } 39 | 40 | 41 | public FeedModule build() { 42 | if (this.connection != null) { 43 | return new FeedModule( 44 | new SignalHandler(new SqlSignalDao(this.connection)), 45 | new PriceHandler(new SqlPriceDao(this.connection)) 46 | ); 47 | } 48 | return new FeedModule( 49 | new SignalHandler(new MemorySignalDao(this.start, this.end, this.signalName)), 50 | new PriceHandler(new MemoryPriceDao(this.start, this.end)) 51 | ); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /feed/src/test/java/moduletest/ExistSignal.java: -------------------------------------------------------------------------------- 1 | package moduletest; 2 | 3 | 4 | import com.apssouza.mytrade.feed.api.FeedBuilder; 5 | import com.apssouza.mytrade.feed.api.FeedModule; 6 | import com.apssouza.mytrade.feed.api.SignalDto; 7 | 8 | import org.junit.Assert; 9 | 10 | import java.time.LocalDateTime; 11 | import java.time.LocalTime; 12 | import java.util.List; 13 | 14 | import static java.time.LocalDate.of; 15 | 16 | import io.cucumber.java.en.Given; 17 | import io.cucumber.java.en.Then; 18 | import io.cucumber.java.en.When; 19 | 20 | public class ExistSignal { 21 | 22 | private FeedModule feed; 23 | private List signals; 24 | 25 | @Given("^that exists signal to a give time and system name$") 26 | public void thatExistsSignalToAGiveTimeAndSystemName() { 27 | var date = of(2018, 9, 10); 28 | this.feed = new FeedBuilder() 29 | .withStartTime(LocalDateTime.of(date.minusDays(20), LocalTime.MIN)) 30 | .withEndTime(LocalDateTime.of(date.plusDays(6), LocalTime.MIN)) 31 | .withSignalName("system-test") 32 | .build(); 33 | } 34 | 35 | @When("^try to retrieve signal to \"([^\"]*)\" and (\\d+)-(\\d+)-(\\d+) (\\d+):(\\d+):(\\d+)$") 36 | public void tryToRetrieveSignalToAnd( String systemName, int year, int month, int day, int hour, int minute, int second) { 37 | LocalDateTime dateTime = LocalDateTime.of(year, month, day, hour, minute, second); 38 | this.signals = feed.getSignal(systemName, dateTime); 39 | } 40 | 41 | @Then("^return (\\d+) signal$") 42 | public void returnSignal(int total) { 43 | Assert.assertEquals(total, signals.size()); 44 | } 45 | 46 | @Then("returned signal is {string}") 47 | public void returnedSignalIs(String action) { 48 | Assert.assertEquals(action, signals.get(0).action()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/orderbook/CycleHistoryDto.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.orderbook; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 4 | import com.apssouza.mytrade.trading.domain.forex.portfolio.FilledOrderDto; 5 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class CycleHistoryDto { 12 | 13 | private final LocalDateTime time; 14 | 15 | private Map transactions = new HashMap<>(); 16 | 17 | public CycleHistoryDto(LocalDateTime time) { 18 | this.time = time; 19 | } 20 | 21 | public void setState(TransactionDto.TransactionState state, String identifier) { 22 | getTransaction(identifier).setState(state); 23 | } 24 | 25 | private TransactionDto getTransaction(String identifier) { 26 | if (transactions.containsKey(identifier)) { 27 | return transactions.get(identifier); 28 | } 29 | TransactionDto transactionDto = new TransactionDto(this.time, identifier); 30 | transactions.put(identifier, transactionDto); 31 | return transactions.get(identifier); 32 | 33 | } 34 | 35 | public void addPosition(PositionDto ps) { 36 | getTransaction(ps.identifier()).setPosition(ps); 37 | } 38 | 39 | public void addOrderFilled(FilledOrderDto order) { 40 | getTransaction(order.identifier()).setFilledOrder(order); 41 | } 42 | 43 | public void addOrder(OrderDto order) { 44 | getTransaction(order.identifier()).setOrder(order); 45 | } 46 | 47 | public LocalDateTime getTime() { 48 | return time; 49 | } 50 | 51 | public Map getTransactions() { 52 | return transactions; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /forex/src/test/java/com/apssouza/mytrade/trading/domain/forex/order/StopOrderBuilder.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.order; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation.StopOrderDto; 4 | 5 | import java.math.BigDecimal; 6 | 7 | public class StopOrderBuilder { 8 | 9 | StopOrderDto.StopOrderType type = StopOrderDto.StopOrderType.HARD_STOP; 10 | Integer id = 0; 11 | StopOrderDto.StopOrderStatus status = StopOrderDto.StopOrderStatus.CREATED; 12 | OrderDto.OrderAction action = OrderDto.OrderAction.BUY; 13 | BigDecimal price = BigDecimal.ONE; 14 | BigDecimal filledPrice = null; 15 | String symbol = "AUDUSD"; 16 | Integer qtd = 100; 17 | String identifier = "AUDUSD"; 18 | 19 | public void withType(StopOrderDto.StopOrderType type) { 20 | this.type = type; 21 | } 22 | 23 | public void withId(Integer id) { 24 | this.id = id; 25 | } 26 | 27 | public StopOrderBuilder withStatus(StopOrderDto.StopOrderStatus status) { 28 | this.status = status; 29 | return this; 30 | } 31 | 32 | public void withAction(OrderDto.OrderAction action) { 33 | this.action = action; 34 | } 35 | 36 | public void withPrice(BigDecimal price) { 37 | this.price = price; 38 | } 39 | 40 | public void withFilledPrice(BigDecimal filledPrice) { 41 | this.filledPrice = filledPrice; 42 | } 43 | 44 | public void withSymbol(String symbol) { 45 | this.symbol = symbol; 46 | } 47 | 48 | public void withQtd(Integer qtd) { 49 | this.qtd = qtd; 50 | } 51 | 52 | public void withIdentifier(String identifier) { 53 | this.identifier = identifier; 54 | } 55 | 56 | public StopOrderDto build() { 57 | return new StopOrderDto(type, id, status, action, price, filledPrice, symbol, qtd, identifier); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /feed/src/main/java/com/apssouza/mytrade/feed/domain/signal/SqlSignalDao.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.feed.domain.signal; 2 | 3 | import com.apssouza.mytrade.common.time.DateTimeConverter; 4 | import com.apssouza.mytrade.feed.api.SignalDto; 5 | 6 | import java.sql.*; 7 | import java.time.LocalDateTime; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class SqlSignalDao implements SignalDao { 12 | 13 | private Connection connection; 14 | 15 | public SqlSignalDao(Connection connection) { 16 | this.connection = connection; 17 | } 18 | 19 | private List getList(String query) { 20 | ArrayList signals = new ArrayList<>(); 21 | 22 | ResultSet resultSet; 23 | try (Statement sta = this.connection.createStatement()) { 24 | resultSet = sta.executeQuery(query); 25 | while (resultSet.next()) { 26 | signals.add(bindResultToDto(resultSet)); 27 | } 28 | } catch (SQLException e) { 29 | throw new RuntimeException(e); 30 | } 31 | return signals; 32 | } 33 | 34 | private SignalDto bindResultToDto(ResultSet resultSet) throws SQLException { 35 | Timestamp timestamp = resultSet.getTimestamp(2); 36 | return new SignalDto( 37 | timestamp.toLocalDateTime(), 38 | resultSet.getString(3), 39 | resultSet.getString(4), 40 | resultSet.getString(5) 41 | ); 42 | } 43 | 44 | 45 | @Override 46 | public List getSignal(String sourceName, LocalDateTime currentTime) { 47 | String query = String.format("select * from signal where source_name = '%s' and created_at = '%s'", 48 | sourceName, 49 | DateTimeConverter.getDatabaseFormat(currentTime) 50 | ); 51 | return getList(query); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/orderbook/OrderBookServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.orderbook; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 4 | import com.apssouza.mytrade.trading.domain.forex.portfolio.FilledOrderDto; 5 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 6 | 7 | import java.io.IOException; 8 | import java.time.LocalDateTime; 9 | import java.util.List; 10 | import java.util.concurrent.CopyOnWriteArrayList; 11 | 12 | class OrderBookServiceImpl implements OrderBookService { 13 | private final TransactionsExporter transactionsExporter; 14 | public List transactions = new CopyOnWriteArrayList<>(); 15 | 16 | public OrderBookServiceImpl(TransactionsExporter transactionsExporter) { 17 | this.transactionsExporter = transactionsExporter; 18 | } 19 | 20 | private CycleHistoryDto cycle; 21 | 22 | public List getTransactions() { 23 | return this.transactions; 24 | } 25 | 26 | public void startCycle(LocalDateTime time) { 27 | this.cycle = new CycleHistoryDto(time); 28 | } 29 | 30 | public void endCycle() { 31 | transactions.add(this.cycle); 32 | } 33 | 34 | public void setState(TransactionDto.TransactionState exit, String identifier) { 35 | this.cycle.setState(exit, identifier); 36 | } 37 | 38 | public void addPosition(PositionDto ps) { 39 | this.cycle.addPosition(ps); 40 | } 41 | 42 | public void addOrderFilled(FilledOrderDto order) { 43 | this.cycle.addOrderFilled(order); 44 | } 45 | 46 | public void addOrder(OrderDto order) { 47 | this.cycle.addOrder(order); 48 | } 49 | 50 | public void export(String filepath) throws IOException { 51 | transactionsExporter.exportCsv(transactions, filepath); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /assets/trading-system.drawio: -------------------------------------------------------------------------------- 1 | 7VvRkqI4FP0aH7uLJBDxUdTemdrZWqudqpl9pCVCZpC4IXbrfv0mkggIOrQt6pTyIrlJLuHcw8lNwA4azFd/cH8R/cUCEnegFaw6aNiBEAAM5I+yrDMLxlZmCDkNdKPcMKH/EW00zZY0IGmpoWAsFnRRNk5ZkpCpKNl8ztlbudmMxeWrLvyQVAyTqR9Xrd9oIKLM6jpWbv9EaBiZKwNL18x901gb0sgP2FvBhEYdNOCMiexsvhqQWIFncMn6Pe2p3Q6Mk0Q06fAtSKJP/p/QHr+khA6xE33lDwhnbl79eKnvWI9WrA0EIWfLRfVqegCvhAuyqouF/2I85LcreULYnAi+lu10L2gg0xRxbJSV33LAkavbRAWwodPVgdZBDre+cxzkiYbiPbCAX8MiUUkCoryADvLeIirIZOFPVe2bfBakLRLzWFdX4Tscjl1Qq+BpsGC3IVbIagsrWIMVjuV1vRmTd1sEDf+7ZKbiId087H3ZALiLVV4pz0L1+7yUNOLGlxxb5i6rrIRD4iXKmPsxDRN5PpWYSz/IU6hS+Wj3dcWcBoHq7nEix6LpasnygtFEbHByvI4zVL6WgmXjbSmcptZxKvHs1oWztWg6lxcEG5cFoWc14zjCbaFSR/GPyIFi8kT3tapIHo5MY21ATXFrTxtakoYnxsnqKpWh5cgekAl8Vpmoi+y5ZaKsEsACFUy2qUWZ7r2WULlU1oD2QLo/a+g1hAq0RaBeW8pAJLjXKAynj6WuxVXan1UKGiQMJJBrKl1kXEQsZIkfj3Krlz8WCsy8zRfGFhrAH0SItV4gKmzLkZOo8vV3WXiwHi2AjOUf5fEROdAYhit9jay0LpbGhFOJiQp8ZlxRoVyCR8tydNk4dHU596cK60Jh19teJUzZkk/Jr2VF+Dwkh7ijgVdoHyQOJ7Ev6Gt5hVvHC911rLidEw7BsvDK1XDZRTZQ3StnV59zf11oph+ZXf5tB3o8JbsfFxe7TlwmUgT8uKm8qDh88V9I3J7GNBSVffnFu6dc+XABhO1S+HXpWFKZJmw2S4logw34InpktKOkG13nsGwURSyTnFzEcK5q7xGx31V29qtFvSrtpoNIz0M5mTKPJ6WW25LQjDmVofntdGbfdsdROtOzeqfUGTN54UdYcrvjoT0ZMqn06cki5WnGYsqaEiaN/IU6jcjKl6omm6SCs5/bDW57Z7OCxvGAxYxveqMnp+851rZToWa2OVSN2eq2zIpY58GmXHS3Oer4Wl5K1y+WUlmkSWh8l2V7r/Q1T66dnVwHuNVsGwBYTbeBBVvKt0HdKvMUPHrP0uljFBq5DrbhjVIIutXXG+emUN1+5ilW3xOSplTS4SwsArAvlehGWIStctYNzWuXC7LoBLu7tUL0Nw+av/f5IIdwrzdEN8ohUPOC4Nwcslvi0DNNf56HQl2EB333VijkuleXD9VtQJ5MhjzGzsQj+OR43cGN8ugakqK6T2GywEs0kuN55KlwbuezF27sn2U8Qi4Xs3nClFcWOJddvR3t2hy3wrmd6Q8h++Kcu46d6vuGQGMSuTsp1BXo1lVsQt43BI6l0Pb12eUoBKtTn/n47r5iPzbM2+nFfPl8+ekGVqXieTT5el8QHR9lCOHVRbn6bU34PB7clyvHR9m28fmiLIv5vwKyt0/5fyvQ6H8= -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/portfolio/StopOrderFilledListener.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.portfolio; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.MultiPositionHandler; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 5 | import com.apssouza.mytrade.trading.domain.forex.common.events.PortfolioChangedEvent; 6 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.EventNotifier; 7 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.Observer; 8 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation.StopOrderDto; 9 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation.StopOrderFilledEvent; 10 | import static com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto.ExitReason; 11 | 12 | class StopOrderFilledListener implements Observer { 13 | 14 | private final PortfolioService portfolioService; 15 | private final EventNotifier eventNotifier; 16 | 17 | public StopOrderFilledListener( 18 | PortfolioService portfolioService, 19 | EventNotifier eventNotifier 20 | ) { 21 | this.portfolioService = portfolioService; 22 | this.eventNotifier = eventNotifier; 23 | } 24 | 25 | @Override 26 | public void update(final Event e) { 27 | if (!(e instanceof StopOrderFilledEvent event)) { 28 | return; 29 | } 30 | StopOrderDto stopOrder = event.getStopOrder(); 31 | PositionDto ps = MultiPositionHandler.getPositionByStopOrder(stopOrder); 32 | 33 | portfolioService.closePosition(ps.identifier(), ExitReason.STOP_ORDER_FILLED); 34 | portfolioService.processReconciliation(e); 35 | eventNotifier.notify(new PortfolioChangedEvent( 36 | e.getTimestamp(), 37 | e.getPrice(), 38 | ps 39 | )); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/order/OrderFactory.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.order; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.brokerintegration.BrokerIntegrationService; 4 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.EventNotifier; 5 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.Observer; 6 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PortfolioService; 7 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.RiskManagementService; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class OrderFactory { 13 | 14 | public static OrderService create(RiskManagementService riskManagementService){ 15 | return create(riskManagementService, new MemoryOrderDao()); 16 | } 17 | 18 | public static OrderService create(RiskManagementService riskManagementService, OrderDao orderDao){ 19 | return new OrderServiceImpl(orderDao, riskManagementService); 20 | } 21 | 22 | public static List createListeners( 23 | OrderService orderService, 24 | RiskManagementService riskManagementService, 25 | BrokerIntegrationService executionHandler, 26 | EventNotifier eventNotifier, 27 | PortfolioService portfolioService 28 | ) { 29 | var listeners = new ArrayList(); 30 | listeners.add(new PositionClosedListener(orderService)); 31 | listeners.add(new OrderFoundListener( 32 | executionHandler, 33 | orderService, 34 | eventNotifier, 35 | riskManagementService 36 | )); 37 | listeners.add(new SignalCreatedListener( 38 | riskManagementService, 39 | orderService, 40 | eventNotifier, 41 | portfolioService 42 | )); 43 | return listeners; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/order/MemoryOrderDao.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.order; 2 | 3 | import java.util.*; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | class MemoryOrderDao implements OrderDao { 8 | private static Map ORDERS = new ConcurrentHashMap<>(); 9 | private static AtomicInteger orderId = new AtomicInteger(); 10 | 11 | 12 | @Override 13 | public OrderDto persist(OrderDto order) { 14 | Integer id = MemoryOrderDao.orderId.incrementAndGet(); 15 | 16 | OrderDto orderDto = new OrderDto( 17 | id, 18 | order 19 | ); 20 | MemoryOrderDao.ORDERS.put(id, orderDto); 21 | return orderDto; 22 | } 23 | 24 | @Override 25 | public boolean updateStatus(Integer id, OrderDto.OrderStatus status) { 26 | OrderDto order = MemoryOrderDao.ORDERS.get(id); 27 | OrderDto orderDto = new OrderDto( 28 | status, 29 | order 30 | ); 31 | MemoryOrderDao.ORDERS.put(id, orderDto); 32 | return true; 33 | } 34 | 35 | @Override 36 | public List getOrderByStatus(OrderDto.OrderStatus status) { 37 | List orders = new ArrayList<>(); 38 | for (Map.Entry entry : MemoryOrderDao.ORDERS.entrySet()) { 39 | if (entry.getValue().status().equals(status)) { 40 | orders.add(new OrderDto( 41 | entry.getValue().id(), 42 | entry.getValue()) 43 | ); 44 | } 45 | } 46 | return orders; 47 | } 48 | 49 | public Optional getOrderById(Integer id){ 50 | if (MemoryOrderDao.ORDERS.containsKey(id)){ 51 | return Optional.of(MemoryOrderDao.ORDERS.get(id)); 52 | } 53 | return Optional.empty(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/portfolio/PositionCollection.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.portfolio; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | import java.util.function.Function; 7 | import java.util.stream.Collectors; 8 | 9 | class PositionCollection { 10 | 11 | private Map positions = new ConcurrentHashMap<>(); 12 | 13 | public void add(PositionDto position) { 14 | positions.put(position.identifier(), position); 15 | } 16 | 17 | public void update(PositionDto position) { 18 | positions.put(position.identifier(), position); 19 | } 20 | 21 | public void updateItems(Function mapper) { 22 | this.positions 23 | .entrySet() 24 | .stream() 25 | .forEach(item -> item.setValue(mapper.apply(item.getValue()))); 26 | } 27 | 28 | public void remove(String identifier) { 29 | positions.remove(identifier); 30 | } 31 | 32 | public List getPositions() { 33 | return positions 34 | .entrySet() 35 | .stream() 36 | .map(Map.Entry::getValue). 37 | collect(Collectors.toList()); 38 | } 39 | 40 | public PositionDto get(String identifier) { 41 | return positions.get(identifier); 42 | } 43 | 44 | public Map getOpenPositions() { 45 | return positions.entrySet() 46 | .stream() 47 | .filter(entry -> entry.getValue().isPositionAlive()) 48 | .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue())); 49 | } 50 | 51 | public boolean contains(String identifier) { 52 | return positions.containsKey(identifier); 53 | } 54 | 55 | public boolean isEmpty() { 56 | return positions.isEmpty(); 57 | } 58 | 59 | public int size() { 60 | return positions.size(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /feed/src/main/java/com/apssouza/mytrade/feed/domain/signal/MemorySignalDao.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.feed.domain.signal; 2 | 3 | import com.apssouza.mytrade.feed.api.SignalDto; 4 | 5 | import java.time.LocalDateTime; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Random; 9 | import java.util.stream.Collectors; 10 | 11 | public class MemorySignalDao implements SignalDao { 12 | private final List signals; 13 | private static final Random RANDOM = new Random(); 14 | 15 | static { 16 | RANDOM.setSeed(1); 17 | } 18 | 19 | public MemorySignalDao(LocalDateTime start, LocalDateTime end, String signalName) { 20 | this.signals = loadSignals(start, end, signalName); 21 | } 22 | 23 | public static List loadSignals(LocalDateTime start, LocalDateTime end, String signalName) { 24 | LocalDateTime current = start; 25 | List signals = new ArrayList<>(); 26 | 27 | while (current.compareTo(end) <= 0) { 28 | current = current.plusMinutes(1L); 29 | int hasSignal = getRandomNumberInRange(0, 1, RANDOM); 30 | int signalAction = getRandomNumberInRange(0, 1, RANDOM); 31 | if (hasSignal > 0) { 32 | signals.add(new SignalDto( 33 | current, 34 | signalAction > 0 ? "Buy" : "Sell", 35 | "AUDUSD", 36 | signalName 37 | )); 38 | } 39 | 40 | } 41 | return signals; 42 | } 43 | 44 | private static int getRandomNumberInRange(int min, int max, Random r) { 45 | return r.ints(min, (max + 1)) 46 | .limit(1) 47 | .findFirst() 48 | .getAsInt(); 49 | } 50 | 51 | @Override 52 | public List getSignal(String systemName, LocalDateTime currentTime) { 53 | return signals.stream() 54 | .filter(signal -> signal.sourceName().equals(systemName) && signal.createdAt().equals(currentTime)) 55 | .collect(Collectors.toList()); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FXyou ![Build status](https://github.com/apssouza22/trading-system/actions/workflows/ci.yml/badge.svg) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=apssouza22_trading-system&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=apssouza22_trading-system) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=apssouza22_trading-system&metric=alert_status)](https://sonarcloud.io/dashboard?id=apssouza22_trading-system) 2 | Simple High frequency trading with backtesting simulation and live trading engine written in Java. 3 | 4 | ## Disclaimer 5 | This software is for educational purposes only. Do not risk money which you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS. 6 | 7 | Always start by running a trading bot in Dry-run and do not engage money before you understand how it works and what profit/loss you should expect. 8 | 9 | 10 | 11 | ## Features 12 | * Persistence: Persistence is achieved through the H2 13 | * Backtesting: Run a simulation of your buy/sell strategy. 14 | * Stop order/ Limit order management 15 | * Broker reconciliation 16 | * Multi position for currency pair 17 | * Daily summary of profit/loss: Provide a daily summary of your profit/loss. 18 | * Performance status report: Provide a performance status of your current trades. 19 | 20 | # Tech details 21 | * Java 17 22 | * Event driven architecture 23 | * Maven multi-modules 24 | * Java modularization 25 | * Packaging by domain 26 | * Domain driven design (DDD) 27 | * BDD with cucumber 28 | * Multiple design patterns (Strategy, factory, builder, Observer...) 29 | * Cross-cutting concern 30 | 31 | ![Alt text](assets/trading-system.png?raw=true "Trading system") 32 | 33 | ## How to run 34 | ``` 35 | mvn package && \ 36 | java -jar runner/target/runner-1.0-SNAPSHOT.jar 37 | ``` 38 | 39 | ### Using Docker 40 | 41 | ``` 42 | docker build . -t trading-engine 43 | docker run --rm -p 8080:8080 trading-engine 44 | ``` 45 | 46 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/riskmanagement/stopordercreation/StopOrderCreatorFixed.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 5 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 6 | 7 | import java.math.BigDecimal; 8 | import java.util.Map; 9 | import java.util.Optional; 10 | 11 | class StopOrderCreatorFixed implements StopOrderCreator { 12 | private CreatorContext creatorContext; 13 | private final StopOrderConfigDto priceDistance; 14 | 15 | public StopOrderCreatorFixed(StopOrderConfigDto priceDistance) { 16 | this.priceDistance = priceDistance; 17 | } 18 | 19 | @Override 20 | public void createContext(PositionDto.PositionType type) { 21 | if (type == PositionDto.PositionType.LONG) { 22 | this.creatorContext = new CreatorContext(new LongPositionStrategy(priceDistance)); 23 | return; 24 | } 25 | this.creatorContext = new CreatorContext(new ShortPositionStrategy(priceDistance)); 26 | 27 | } 28 | 29 | @Override 30 | public StopOrderDto getHardStopLoss(PositionDto position) { 31 | return creatorContext.getHardStopLoss(position); 32 | } 33 | 34 | @Override 35 | public StopOrderDto getProfitStopOrder(PositionDto position) { 36 | return creatorContext.getProfitStopOrder(position); 37 | } 38 | 39 | @Override 40 | public Optional getEntryStopOrder(PositionDto position, Event event) { 41 | Map price = event.getPrice(); 42 | BigDecimal priceClose = price.get(position.symbol()).close(); 43 | return creatorContext.getEntryStopOrder(position, priceClose); 44 | } 45 | 46 | @Override 47 | public Optional getTrailingStopOrder(PositionDto position, Event event) { 48 | Map price = event.getPrice(); 49 | BigDecimal priceClose = price.get(position.symbol()).close(); 50 | return creatorContext.getTrailingStopOrder(position, priceClose); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/portfolio/PortfolioChecker.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.portfolio; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.brokerintegration.BrokerIntegrationService; 4 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | 10 | class PortfolioChecker { 11 | private final BrokerIntegrationService executionHandler; 12 | 13 | public PortfolioChecker(BrokerIntegrationService executionHandler) { 14 | this.executionHandler = executionHandler; 15 | } 16 | 17 | /** 18 | * Check if the local portfolio is in sync with the portfolio on the broker 19 | */ 20 | public void process(List localPositions) throws ReconciliationException { 21 | Map remotePositions = executionHandler.getPositions(); 22 | 23 | if (localPositions.isEmpty() && remotePositions.isEmpty()) { 24 | return; 25 | } 26 | 27 | if (localPositions.size() != remotePositions.size()) { 28 | throw new ReconciliationException("Portfolio is not in sync", localPositions, remotePositions); 29 | } 30 | 31 | if (localPositions.size() == remotePositions.size()) { 32 | checkEveryPosition(localPositions, remotePositions); 33 | } 34 | } 35 | 36 | private void checkEveryPosition( 37 | List localPositions, 38 | Map remotePositions 39 | ) throws ReconciliationException { 40 | for (PositionDto position : localPositions) { 41 | String symbol = position.symbol(); 42 | if (!remotePositions.containsKey(symbol)) { 43 | throw new ReconciliationException("Position key mismatch", localPositions, remotePositions); 44 | } 45 | OrderDto.OrderAction orderAction = position.positionType().getOrderAction(); 46 | if (!remotePositions.get(symbol).action().equals(orderAction)) { 47 | throw new ReconciliationException("Position action mismatch", localPositions, remotePositions); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/feed/pricefeed/RealtimePriceStream.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.feed.pricefeed; 2 | 3 | import com.apssouza.mytrade.common.time.DateTimeHelper; 4 | import com.apssouza.mytrade.feed.api.PriceDto; 5 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 6 | import com.apssouza.mytrade.trading.domain.forex.common.TradingHelper; 7 | import com.apssouza.mytrade.trading.domain.forex.common.events.PriceChangedEvent; 8 | 9 | import java.time.LocalDateTime; 10 | import java.util.Map; 11 | import java.util.Timer; 12 | import java.util.TimerTask; 13 | import java.util.concurrent.BlockingQueue; 14 | 15 | /** 16 | * Provide a feed price steam based on real time price feed 17 | */ 18 | class RealtimePriceStream implements PriceStream { 19 | 20 | private final BlockingQueue eventQueue; 21 | private final PriceFeedHandler priceHandler; 22 | 23 | public RealtimePriceStream(BlockingQueue eventQueue, PriceFeedHandler priceHandler) { 24 | this.eventQueue = eventQueue; 25 | this.priceHandler = priceHandler; 26 | } 27 | 28 | @Override 29 | public void start(LocalDateTime start, LocalDateTime end) { 30 | TimerTask repeatedTask = new TimerTask() { 31 | @Override 32 | public void run() { 33 | LocalDateTime time = LocalDateTime.now(DateTimeHelper.ZONEID_UTC); 34 | if (!TradingHelper.isTradingTime(time)) { 35 | return; 36 | } 37 | var event = new PriceChangedEvent(time, priceHandler.getPriceSymbolMapped(time)); 38 | 39 | try { 40 | eventQueue.put(event); 41 | } catch (InterruptedException e) { 42 | throw new RuntimeException(e); 43 | } 44 | } 45 | }; 46 | Timer timer = new Timer("PriceStream"); 47 | 48 | long delay = 1000L; 49 | long period = 1000L; 50 | timer.scheduleAtFixedRate(repeatedTask, delay, period); 51 | } 52 | 53 | @Override 54 | public Map getPriceSymbolMapped(LocalDateTime current) { 55 | return priceHandler.getPriceSymbolMapped(current); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/session/QueueConsumer.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.session; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 4 | import com.apssouza.mytrade.trading.domain.forex.common.ForexException; 5 | import com.apssouza.mytrade.trading.domain.forex.common.events.SessionFinishedEvent; 6 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.EventNotifier; 7 | import com.apssouza.mytrade.trading.domain.forex.orderbook.OrderBookService; 8 | 9 | import java.time.LocalDateTime; 10 | import java.util.concurrent.BlockingQueue; 11 | import java.util.logging.Logger; 12 | 13 | class QueueConsumer extends Thread { 14 | private final BlockingQueue eventQueue; 15 | private final OrderBookService historyHandler; 16 | private final EventNotifier notifier; 17 | private final LocalDateTime endDate; 18 | 19 | private static Logger log = Logger.getLogger(QueueConsumer.class.getSimpleName()); 20 | 21 | public QueueConsumer( 22 | BlockingQueue eventQueue, 23 | OrderBookService historyHandler, 24 | EventNotifier notifier, 25 | LocalDateTime endDate 26 | 27 | ) { 28 | super(); 29 | this.eventQueue = eventQueue; 30 | this.historyHandler = historyHandler; 31 | this.notifier = notifier; 32 | this.endDate = endDate; 33 | } 34 | 35 | @Override 36 | public void run() { 37 | for (; ; ) { 38 | try { 39 | Event event = eventQueue.take(); 40 | var logMsg = String.format( 41 | "%s - %s - %s", 42 | event, 43 | event.getTimestamp(), 44 | event.getPrice().get("AUDUSD").close() 45 | ); 46 | log.info(logMsg); 47 | historyHandler.startCycle(event.getTimestamp()); 48 | 49 | notifier.notify(event); 50 | 51 | historyHandler.endCycle(); 52 | if (event instanceof SessionFinishedEvent) { 53 | return; 54 | } 55 | } catch (InterruptedException ex) { 56 | throw new ForexException(ex); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/order/SignalCreatedListener.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.order; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.OrderCreatedEvent; 5 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.Observer; 6 | import com.apssouza.mytrade.trading.domain.forex.common.events.SignalCreatedEvent; 7 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PortfolioService; 8 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.RiskManagementService; 9 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.EventNotifier; 10 | 11 | import java.util.logging.Logger; 12 | 13 | class SignalCreatedListener implements Observer { 14 | private static Logger log = Logger.getLogger(SignalCreatedListener.class.getName()); 15 | private final RiskManagementService riskManagementService; 16 | private final OrderService orderService; 17 | private final EventNotifier eventNotifier; 18 | private final PortfolioService portfolioService; 19 | 20 | public SignalCreatedListener( 21 | RiskManagementService riskManagementService, 22 | OrderService orderService, 23 | EventNotifier eventNotifier, 24 | PortfolioService portfolioService 25 | ) { 26 | this.riskManagementService = riskManagementService; 27 | this.orderService = orderService; 28 | this.eventNotifier = eventNotifier; 29 | this.portfolioService = portfolioService; 30 | } 31 | 32 | 33 | @Override 34 | public void update(final Event e) { 35 | if (!(e instanceof SignalCreatedEvent event)) { 36 | return; 37 | } 38 | log.info("Processing new signal..."); 39 | OrderDto order = this.orderService.createOrderFromSignal(event); 40 | if (riskManagementService.canCreateOrder(order, portfolioService.getPositions())) { 41 | this.orderService.persist(order); 42 | log.info("Created order: " + order); 43 | eventNotifier.notify(new OrderCreatedEvent( 44 | event.getTimestamp(), 45 | event.getPrice(), 46 | order 47 | )); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /feed/src/main/java/com/apssouza/mytrade/feed/domain/price/SqlPriceDao.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.feed.domain.price; 2 | 3 | import com.apssouza.mytrade.common.time.DateTimeConverter; 4 | import com.apssouza.mytrade.feed.api.PriceDto; 5 | 6 | import java.sql.*; 7 | import java.time.LocalDateTime; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class SqlPriceDao implements PriceDao { 12 | 13 | private final Connection connection; 14 | 15 | public SqlPriceDao(Connection connection) { 16 | this.connection = connection; 17 | } 18 | 19 | 20 | private List getList(String query) { 21 | ArrayList prices = new ArrayList<>(); 22 | ResultSet resultSet; 23 | try (Statement sta = this.connection.createStatement()) { 24 | resultSet = sta.executeQuery(query); 25 | while (resultSet.next()) { 26 | prices.add(bindResultToDto(resultSet)); 27 | } 28 | } catch (SQLException e) { 29 | throw new RuntimeException(e); 30 | } 31 | return prices; 32 | } 33 | 34 | private PriceDto bindResultToDto(ResultSet resultSet) throws SQLException { 35 | Timestamp timestamp = resultSet.getTimestamp(2); 36 | return new PriceDto( 37 | timestamp.toLocalDateTime(), 38 | resultSet.getBigDecimal(3), 39 | resultSet.getBigDecimal(4), 40 | resultSet.getBigDecimal(5), 41 | resultSet.getBigDecimal(6), 42 | resultSet.getString(8) 43 | ); 44 | } 45 | 46 | @Override 47 | public List getPriceInterval(LocalDateTime start, LocalDateTime end) { 48 | String query = String.format("select * from price where timestamp >= '%s' and timestamp <='%s'", 49 | DateTimeConverter.getDatabaseFormat(start), 50 | DateTimeConverter.getDatabaseFormat(end) 51 | ); 52 | return getList(query); 53 | } 54 | 55 | @Override 56 | public List getClosestPrice(LocalDateTime datetime) { 57 | String query = String.format("select * from price where timestamp <= '%s' LIMIT 1", datetime); 58 | List list = getList(query); 59 | String query2 = String.format("select * from price where timestamp = '%s' LIMIT 1", list.get(0).timestamp()); 60 | return getList(query2); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/brokerintegration/MultiPositionPerCPairHandler.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.brokerintegration; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 4 | import com.apssouza.mytrade.trading.domain.forex.portfolio.FilledOrderDto; 5 | 6 | import java.util.Map; 7 | 8 | class MultiPositionPerCPairHandler { 9 | private final Map positions; 10 | 11 | public MultiPositionPerCPairHandler(Map positions) { 12 | this.positions = positions; 13 | } 14 | 15 | public void handle(OrderDto.OrderAction action, String currency_pair, Integer quantity) { 16 | FilledOrderDto filledOrderDto = this.positions.get(currency_pair); 17 | if (action.equals(OrderDto.OrderAction.SELL) && filledOrderDto.action().equals(OrderDto.OrderAction.BUY)) { 18 | handlerOppositeDirection(currency_pair, quantity, filledOrderDto); 19 | return; 20 | } 21 | if (action.equals(OrderDto.OrderAction.BUY) && filledOrderDto.action().equals(OrderDto.OrderAction.SELL)) { 22 | handlerOppositeDirection(currency_pair, quantity, filledOrderDto); 23 | return; 24 | } 25 | if (action.equals(OrderDto.OrderAction.BUY) && filledOrderDto.action().equals(OrderDto.OrderAction.BUY)) { 26 | handleSameDirection(quantity, filledOrderDto); 27 | return; 28 | } 29 | if (action.equals(OrderDto.OrderAction.SELL) && filledOrderDto.action().equals(OrderDto.OrderAction.SELL)) { 30 | handleSameDirection(quantity, filledOrderDto); 31 | return; 32 | } 33 | } 34 | 35 | private void handleSameDirection(Integer quantity, FilledOrderDto filledOrderDto) { 36 | filledOrderDto = new FilledOrderDto(filledOrderDto.quantity() + quantity, filledOrderDto); 37 | this.positions.put(filledOrderDto.symbol(), filledOrderDto); 38 | } 39 | 40 | private void handlerOppositeDirection(String currency_pair, Integer quantity, FilledOrderDto filledOrderDto) { 41 | if (quantity == filledOrderDto.quantity()) { 42 | this.positions.remove(currency_pair); 43 | return; 44 | } 45 | filledOrderDto = new FilledOrderDto(Math.abs(filledOrderDto.quantity() - quantity), filledOrderDto); 46 | this.positions.put(currency_pair, filledOrderDto); 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/riskmanagement/stopordercreation/ShortPositionStrategy.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 4 | import com.apssouza.mytrade.trading.domain.forex.common.NumberHelper; 5 | 6 | import java.math.BigDecimal; 7 | import java.util.Optional; 8 | 9 | class ShortPositionStrategy implements CreatorStrategy { 10 | 11 | private final StopOrderConfigDto distanceObject; 12 | 13 | public ShortPositionStrategy(StopOrderConfigDto distanceObject) { 14 | this.distanceObject = distanceObject; 15 | } 16 | 17 | @Override 18 | public BigDecimal getEntryStopPrice(PositionDto position, BigDecimal priceClose) { 19 | BigDecimal stopPrice = null; 20 | if (priceClose.compareTo(position.initPrice().subtract(distanceObject.entryStopDistance())) < 0) { 21 | stopPrice = position.initPrice(); 22 | } 23 | return stopPrice; 24 | } 25 | 26 | @Override 27 | public BigDecimal getProfitStopPrice(PositionDto position) { 28 | return position.initPrice().subtract(distanceObject.takeProfitDistance()); 29 | } 30 | 31 | @Override 32 | public BigDecimal getHardStopPrice(PositionDto position) { 33 | return position.initPrice().add(distanceObject.hardStopDistance()); 34 | } 35 | 36 | @Override 37 | public Optional getTrailingStopPrice(PositionDto position, BigDecimal last_close) { 38 | BigDecimal stopPrice; 39 | BigDecimal tsPrice = position.initPrice().subtract(distanceObject.traillingStopDistance()); 40 | if (last_close.compareTo(tsPrice) < 0) { 41 | return Optional.empty(); 42 | } 43 | if (position.getPlacedStopLoss() == null){ 44 | return Optional.empty(); 45 | } 46 | if (!position.getPlacedStopLoss().type().equals(StopOrderDto.StopOrderType.TRAILLING_STOP)) { 47 | stopPrice = last_close.add(distanceObject.traillingStopDistance()); 48 | } else { 49 | stopPrice = position.getPlacedStopLoss().price().subtract(distanceObject.traillingStopDistance()); 50 | stopPrice = position.getPlacedStopLoss().price().compareTo(stopPrice) < 0 ? position.getPlacedStopLoss().price() : stopPrice; 51 | } 52 | stopPrice = NumberHelper.roundSymbolPrice(position.symbol(), stopPrice); 53 | return Optional.of(stopPrice); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /feed/src/main/java/com/apssouza/mytrade/feed/domain/price/MemoryPriceDao.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.feed.domain.price; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | 5 | import java.math.BigDecimal; 6 | import java.time.LocalDateTime; 7 | import java.util.ArrayList; 8 | import java.util.Comparator; 9 | import java.util.List; 10 | import java.util.Optional; 11 | import java.util.Random; 12 | import java.util.stream.Collectors; 13 | 14 | public class MemoryPriceDao implements PriceDao { 15 | 16 | private List prices; 17 | private static final Random RANDOM = new Random(); 18 | 19 | static { 20 | RANDOM.setSeed(1); 21 | } 22 | 23 | public MemoryPriceDao(LocalDateTime start, LocalDateTime end) { 24 | this.prices = getPrices(start, end); 25 | prices.sort(Comparator.comparing(PriceDto::timestamp)); 26 | } 27 | 28 | @Override 29 | public List getPriceInterval(LocalDateTime start, LocalDateTime end) { 30 | return prices.parallelStream() 31 | .filter(i -> i.timestamp().compareTo(start) >= 0 && i.timestamp().compareTo(end) <= 0) 32 | .collect(Collectors.toList()); 33 | } 34 | 35 | @Override 36 | public List getClosestPrice(LocalDateTime time) { 37 | Optional first = prices.stream() 38 | .sorted(Comparator.comparing(PriceDto::timestamp).reversed()) 39 | .filter(i -> i.timestamp().compareTo(time) <= 0) 40 | .findFirst(); 41 | 42 | return prices.stream() 43 | .filter(i -> i.timestamp().equals(first.get().timestamp())) 44 | .collect(Collectors.toList()); 45 | } 46 | 47 | 48 | public static List getPrices(LocalDateTime start, LocalDateTime end) { 49 | LocalDateTime current = start; 50 | List prices = new ArrayList<>(); 51 | 52 | while (current.compareTo(end) <= 0) { 53 | BigDecimal close = BigDecimal.valueOf(getRandomPrice(RANDOM)); 54 | prices.add(new PriceDto( 55 | current, 56 | close, 57 | close, 58 | close, 59 | close, 60 | "AUDUSD" 61 | )); 62 | current = current.plusMinutes(1L); 63 | } 64 | return prices; 65 | } 66 | 67 | 68 | private static double getRandomPrice(Random r) { 69 | return r.doubles(1, 2).limit(1).findFirst().getAsDouble(); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/brokerintegration/InteractiveBrokerOrderExecution.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.brokerintegration; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 5 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation.StopOrderDto; 6 | import com.apssouza.mytrade.trading.domain.forex.portfolio.FilledOrderDto; 7 | 8 | import java.time.LocalDateTime; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | /** 13 | * Implementation to execute orders against the Broker called interactive broker 14 | */ 15 | class InteractiveBrokerOrderExecution implements BrokerIntegrationService { 16 | 17 | private final String brokerHost; 18 | private final String brokerPort; 19 | private final String brokerClientId; 20 | 21 | public InteractiveBrokerOrderExecution(String brokerHost, String brokerPort, String brokerClientId) { 22 | this.brokerHost = brokerHost; 23 | this.brokerPort = brokerPort; 24 | this.brokerClientId = brokerClientId; 25 | } 26 | 27 | @Override 28 | public void closeAllPositions() { 29 | 30 | } 31 | 32 | @Override 33 | public Integer cancelOpenLimitOrders() { 34 | return 0; 35 | } 36 | 37 | @Override 38 | public Integer cancelOpenStopOrders() { 39 | return 0; 40 | } 41 | 42 | @Override 43 | public Map getPortfolio() { 44 | return new HashMap<>(); 45 | } 46 | 47 | @Override 48 | public void setCurrentTime(LocalDateTime currentTime) { 49 | 50 | } 51 | 52 | @Override 53 | public Map getStopLossOrders() { 54 | return null; 55 | } 56 | 57 | @Override 58 | public Map getLimitOrders() { 59 | return null; 60 | } 61 | 62 | @Override 63 | public FilledOrderDto executeOrder(OrderDto order) { 64 | return null; 65 | } 66 | 67 | @Override 68 | public void setPriceMap(Map priceMap) { 69 | 70 | } 71 | 72 | @Override 73 | public void deleteStopOrders() { 74 | 75 | } 76 | 77 | @Override 78 | public StopOrderDto placeStopOrder(StopOrderDto stopLoss) { 79 | return null; 80 | } 81 | 82 | @Override 83 | public void processStopOrders() { 84 | 85 | } 86 | 87 | @Override 88 | public Map getPositions() { 89 | return null; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /common/src/main/java/com/apssouza/mytrade/common/time/DayHelper.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.common.time; 2 | 3 | import java.time.LocalDate; 4 | import java.util.Calendar; 5 | import java.util.Date; 6 | 7 | /** 8 | * A day helper 9 | */ 10 | public class DayHelper { 11 | 12 | public static boolean isWeekend(LocalDate date) { 13 | Date dtDate = DateTimeConverter.getDateFromLocalDate(date); 14 | Calendar c1 = Calendar.getInstance(); 15 | c1.setTime(dtDate); 16 | return isSaturday(dtDate) || isSunday(dtDate); 17 | } 18 | 19 | public static boolean isWeekDay(LocalDate date) { 20 | return !isWeekend(date); 21 | } 22 | 23 | public static boolean isMonday(Date date) { 24 | Calendar c1 = Calendar.getInstance(); 25 | c1.setTime(date); 26 | return c1.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY; 27 | } 28 | 29 | public static boolean isTusday(Date date) { 30 | Calendar c1 = Calendar.getInstance(); 31 | c1.setTime(date); 32 | return c1.get(Calendar.DAY_OF_WEEK) == Calendar.TUESDAY; 33 | } 34 | 35 | public static boolean isWednesday(Date date) { 36 | Calendar c1 = Calendar.getInstance(); 37 | c1.setTime(date); 38 | return c1.get(Calendar.DAY_OF_WEEK) == Calendar.WEDNESDAY; 39 | } 40 | 41 | public static boolean isThursday(Date date) { 42 | Calendar c1 = Calendar.getInstance(); 43 | c1.setTime(date); 44 | return c1.get(Calendar.DAY_OF_WEEK) == Calendar.THURSDAY; 45 | } 46 | 47 | public static boolean isFriday(Date date) { 48 | Calendar c1 = Calendar.getInstance(); 49 | c1.setTime(date); 50 | return c1.get(Calendar.DAY_OF_WEEK) == Calendar.FRIDAY; 51 | } 52 | 53 | public static boolean isSaturday(Date date) { 54 | Calendar c1 = Calendar.getInstance(); 55 | c1.setTime(date); 56 | return c1.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY; 57 | } 58 | 59 | public static boolean isSunday(Date date) { 60 | Calendar c1 = Calendar.getInstance(); 61 | c1.setTime(date); 62 | return c1.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY; 63 | } 64 | 65 | public static boolean isToday(Date date){ 66 | Calendar today = Calendar.getInstance(); 67 | Calendar dateCalender = Calendar.getInstance(); 68 | dateCalender.setTime(date); 69 | return today.get(Calendar.YEAR) == dateCalender.get(Calendar.YEAR) && 70 | today.get(Calendar.DAY_OF_YEAR) == dateCalender.get(Calendar.DAY_OF_YEAR); 71 | 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/riskmanagement/stopordercreation/LongPositionStrategy.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 4 | import com.apssouza.mytrade.trading.domain.forex.common.NumberHelper; 5 | 6 | import java.math.BigDecimal; 7 | import java.util.Optional; 8 | 9 | class LongPositionStrategy implements CreatorStrategy { 10 | private final StopOrderConfigDto distanceObject; 11 | 12 | public LongPositionStrategy(StopOrderConfigDto distanceObject) { 13 | this.distanceObject = distanceObject; 14 | } 15 | 16 | @Override 17 | public BigDecimal getHardStopPrice(PositionDto position) { 18 | return position.initPrice().subtract(distanceObject.hardStopDistance()); 19 | } 20 | 21 | @Override 22 | public BigDecimal getEntryStopPrice(PositionDto position, BigDecimal priceClose) { 23 | BigDecimal stopPrice = null; 24 | if (priceClose.compareTo(position.initPrice().add(distanceObject.entryStopDistance())) > 0) { 25 | stopPrice = position.initPrice(); 26 | } 27 | return stopPrice; 28 | } 29 | 30 | @Override 31 | public BigDecimal getProfitStopPrice(PositionDto position) { 32 | return position.initPrice().add(distanceObject.takeProfitDistance()); 33 | } 34 | 35 | @Override 36 | public Optional getTrailingStopPrice(PositionDto position, BigDecimal last_close) { 37 | BigDecimal stopPrice = null; 38 | // if price is high enough to warrant creating trailing stop loss: 39 | BigDecimal tsPrice = position.initPrice().add(distanceObject.traillingStopDistance()); 40 | if (last_close.compareTo(tsPrice) > 0) { 41 | return Optional.empty(); 42 | } 43 | if (position.getPlacedStopLoss() == null){ 44 | return Optional.empty(); 45 | } 46 | if (!position.getPlacedStopLoss().type().equals(StopOrderDto.StopOrderType.TRAILLING_STOP)) { 47 | stopPrice = last_close.subtract(distanceObject.traillingStopDistance()); 48 | } else { 49 | stopPrice = position.getPlacedStopLoss().price().subtract(distanceObject.traillingStopDistance()); 50 | stopPrice = position.getPlacedStopLoss().price().compareTo(stopPrice) > 0 ? position.getPlacedStopLoss().price() : stopPrice; 51 | } 52 | stopPrice = NumberHelper.roundSymbolPrice(position.symbol(), stopPrice); 53 | return Optional.of(stopPrice); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/brokerintegration/BrokerIntegrationService.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.brokerintegration; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 5 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation.StopOrderDto; 6 | import com.apssouza.mytrade.trading.domain.forex.portfolio.FilledOrderDto; 7 | 8 | import java.time.LocalDateTime; 9 | import java.util.Map; 10 | 11 | /** 12 | * Execute orders. This the interface communication with Brokers 13 | */ 14 | public interface BrokerIntegrationService { 15 | /** 16 | * Close all open positions 17 | */ 18 | void closeAllPositions(); 19 | 20 | /** 21 | * Cancel open limit order 22 | * 23 | * @return number of closed orders 24 | */ 25 | Integer cancelOpenLimitOrders(); 26 | 27 | /** 28 | * Cancel open stop order 29 | * 30 | * @return number of closed orders 31 | */ 32 | Integer cancelOpenStopOrders(); 33 | 34 | /** 35 | * Set the current time ticker 36 | */ 37 | void setCurrentTime(LocalDateTime currentTime); 38 | 39 | /** 40 | * Get a map of open stop loss orders 41 | * 42 | * @return a map of id order -> stop order 43 | */ 44 | Map getStopLossOrders(); 45 | 46 | /** 47 | * Get a map of open stop limit orders 48 | * 49 | * @return a map of id order -> stop order 50 | */ 51 | Map getLimitOrders(); 52 | 53 | /** 54 | * Execute a buy/sell order 55 | * 56 | * @return the filed order data 57 | */ 58 | FilledOrderDto executeOrder(OrderDto order); 59 | 60 | /** 61 | * Set a currency pair price 62 | * 63 | * @param priceMap 64 | */ 65 | void setPriceMap(Map priceMap); 66 | 67 | /** 68 | * Delete stop orders 69 | */ 70 | void deleteStopOrders(); 71 | 72 | /** 73 | * Place the stop order to be executed when match the price 74 | * 75 | * @param stopLoss 76 | * @return stop order filled info 77 | */ 78 | StopOrderDto placeStopOrder(StopOrderDto stopLoss); 79 | 80 | /** 81 | * Process placed stop orders 82 | */ 83 | void processStopOrders(); 84 | 85 | /** 86 | * Get open position in the broker 87 | * 88 | * @return return a list of open position 89 | */ 90 | Map getPositions(); 91 | 92 | Map getPortfolio(); 93 | } 94 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/order/OrderServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.order; 2 | 3 | import com.apssouza.mytrade.feed.api.SignalDto; 4 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 5 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.RiskManagementService; 6 | import com.apssouza.mytrade.trading.domain.forex.common.events.SignalCreatedEvent; 7 | 8 | import java.time.LocalDateTime; 9 | import java.util.List; 10 | import java.util.logging.Logger; 11 | 12 | class OrderServiceImpl implements OrderService { 13 | private final OrderDao orderDao; 14 | private final RiskManagementService riskManagementService; 15 | 16 | private static Logger log = Logger.getLogger(OrderService.class.getName()); 17 | 18 | public OrderServiceImpl( 19 | OrderDao orderDao, 20 | RiskManagementService riskManagementService 21 | ) { 22 | this.orderDao = orderDao; 23 | this.riskManagementService = riskManagementService; 24 | } 25 | 26 | public OrderDto createOrderFromClosedPosition(PositionDto position, LocalDateTime time) { 27 | OrderDto.OrderAction action = position.positionType().equals(PositionDto.PositionType.LONG) ? OrderDto.OrderAction.SELL : OrderDto.OrderAction.BUY; 28 | return new OrderDto( 29 | position.symbol(), 30 | action, position.quantity(), 31 | OrderDto.OrderOrigin.EXITS, 32 | time, 33 | position.identifier(), 34 | OrderDto.OrderStatus.CREATED 35 | ); 36 | } 37 | 38 | public OrderDto persist(OrderDto order) { 39 | return orderDao.persist(order); 40 | } 41 | 42 | 43 | public boolean updateOrderStatus(Integer id, OrderDto.OrderStatus status) { 44 | return orderDao.updateStatus(id, status); 45 | } 46 | 47 | public OrderDto createOrderFromSignal(SignalCreatedEvent event) { 48 | LocalDateTime time = event.getTimestamp(); 49 | SignalDto signal = event.getSignal(); 50 | String action = signal.action(); 51 | 52 | OrderDto order = new OrderDto( 53 | signal.symbol(), 54 | OrderDto.OrderAction.valueOf(action.toUpperCase()), 55 | riskManagementService.getPositionSize(), 56 | OrderDto.OrderOrigin.SIGNAL, 57 | time, 58 | "", 59 | OrderDto.OrderStatus.CREATED 60 | ); 61 | return order; 62 | } 63 | 64 | public List getOrderByStatus(OrderDto.OrderStatus status) { 65 | return orderDao.getOrderByStatus(status); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/riskmanagement/PositionExitChecker.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.riskmanagement; 2 | 3 | import com.apssouza.mytrade.common.time.MarketTimeHelper; 4 | import com.apssouza.mytrade.feed.api.SignalDto; 5 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 6 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 7 | 8 | import java.time.LocalDateTime; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.logging.Logger; 13 | 14 | class PositionExitChecker { 15 | private static Logger log = Logger.getLogger(PositionExitChecker.class.getSimpleName()); 16 | 17 | public List check(List positions, List signals) { 18 | if (positions.isEmpty()){ 19 | return Collections.emptyList(); 20 | } 21 | log.info("Processing exits..."); 22 | List exitedPositions = new ArrayList<>(); 23 | for (PositionDto position : positions) { 24 | PositionDto.ExitReason exit_reason = null; 25 | if (this.hasCounterSignal(position, signals)) { 26 | exit_reason = PositionDto.ExitReason.COUNTER_SIGNAL; 27 | } 28 | if (exit_reason != null) { 29 | log.info("Exiting position for(" + position.symbol() + " Reason " + exit_reason); 30 | exitedPositions.add(position); 31 | } 32 | } 33 | return exitedPositions; 34 | } 35 | 36 | private boolean isEndOfDay(LocalDateTime time) { 37 | return MarketTimeHelper.isEOD(time); 38 | } 39 | 40 | private SignalDto getSignalBySymbol(String symbol, List signals) { 41 | for (SignalDto signal : signals) { 42 | if (signal.symbol().equals(symbol)) { 43 | return signal; 44 | } 45 | } 46 | return null; 47 | } 48 | 49 | 50 | private boolean hasCounterSignal(PositionDto position, List signals) { 51 | SignalDto signal = getSignalBySymbol(position.symbol(), signals); 52 | if (signal == null) { 53 | return false; 54 | } 55 | 56 | OrderDto.OrderAction exit_direction = null; 57 | if (position.positionType() == PositionDto.PositionType.LONG) { 58 | exit_direction = OrderDto.OrderAction.SELL; 59 | } else { 60 | exit_direction = OrderDto.OrderAction.BUY; 61 | } 62 | 63 | if (OrderDto.OrderAction.valueOf(signal.action().toUpperCase()) == exit_direction) { 64 | return true; 65 | } 66 | return false; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /forex/src/test/java/com/apssouza/mytrade/trading/domain/forex/order/OrderBuilder.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.order; 2 | 3 | import static com.apssouza.mytrade.trading.domain.forex.order.OrderDto.OrderAction.*; 4 | import static com.apssouza.mytrade.trading.domain.forex.order.OrderDto.OrderOrigin.*; 5 | import static com.apssouza.mytrade.trading.domain.forex.order.OrderDto.OrderStatus.*; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class OrderBuilder { 12 | private String symbol = "AUDUSD"; 13 | private String identifier = "AUDUSD"; 14 | private OrderDto.OrderAction action = BUY; 15 | private int qtd = 1000; 16 | private OrderDto.OrderOrigin origin = SIGNAL; 17 | private LocalDateTime time = LocalDateTime.MIN; 18 | private OrderDto.OrderStatus status = CREATED; 19 | 20 | public OrderBuilder withSymbol(String symbol) { 21 | this.symbol = symbol; 22 | return this; 23 | } 24 | 25 | public OrderBuilder withIdentifier(String identifier) { 26 | this.identifier = identifier; 27 | return this; 28 | } 29 | 30 | public OrderBuilder withAction(OrderDto.OrderAction action) { 31 | this.action = action; 32 | return this; 33 | } 34 | 35 | public OrderBuilder withQtd(int qtd) { 36 | this.qtd = qtd; 37 | return this; 38 | } 39 | 40 | public void withOrigin(OrderDto.OrderOrigin origin) { 41 | this.origin = origin; 42 | } 43 | 44 | public void withTime(LocalDateTime time) { 45 | this.time = time; 46 | } 47 | 48 | public OrderBuilder withStatus(OrderDto.OrderStatus status) { 49 | this.status = status; 50 | return this; 51 | } 52 | 53 | List orders = new ArrayList<>(); 54 | 55 | public OrderBuilder withOrder(LocalDateTime time, OrderDto.OrderAction action, OrderDto.OrderStatus status) { 56 | orders.add(new OrderDto( 57 | symbol, 58 | action, 59 | qtd, 60 | origin, 61 | time, 62 | identifier, 63 | status 64 | )); 65 | return this; 66 | } 67 | 68 | public List buildList() { 69 | return orders; 70 | } 71 | 72 | public OrderDto build() { 73 | if (orders.isEmpty()){ 74 | return new OrderDto( 75 | symbol, 76 | action, 77 | qtd, 78 | origin, 79 | time, 80 | identifier, 81 | status 82 | ); 83 | } 84 | return orders.get(0); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /common/src/main/java/com/apssouza/mytrade/common/time/MarketTimeHelper.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.common.time; 2 | 3 | import java.time.DayOfWeek; 4 | import java.time.LocalDate; 5 | import java.time.LocalDateTime; 6 | import java.time.LocalTime; 7 | import java.util.TimeZone; 8 | 9 | 10 | public class MarketTimeHelper { 11 | 12 | private static final LocalTime OPEN_WEEEK_MONDAY = LocalTime.of(6, 0); 13 | 14 | private static final LocalTime CLOSE_WEEEK_SATURDAY = LocalTime.of(6, 0); 15 | 16 | private static final LocalTime END_OF_DAY = LocalTime.of(18, 0); 17 | 18 | 19 | private static final LocalTime START_OF_DAY = LocalTime.of(5, 55); 20 | 21 | private static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("utc"); 22 | 23 | 24 | public static boolean isMarketOpened(LocalDateTime dateTime) { 25 | switch (dateTime.getDayOfWeek()) { 26 | case TUESDAY: 27 | case WEDNESDAY: 28 | case THURSDAY: 29 | case FRIDAY: 30 | return true; 31 | case MONDAY: 32 | return dateTime.toLocalTime().isBefore(START_OF_DAY) ? false : true; 33 | case SATURDAY: 34 | return dateTime.toLocalTime().isAfter(CLOSE_WEEEK_SATURDAY) ? false : true; 35 | case SUNDAY: 36 | return false; 37 | default: 38 | return false; 39 | } 40 | } 41 | 42 | /** 43 | * @param dateTime 44 | * @return 45 | */ 46 | public static LocalDateTime getNextOpenTime(LocalDateTime dateTime) { 47 | LocalDateTime nextOpenTime = LocalDateTime.of( 48 | dateTime.getYear(), 49 | dateTime.getMonth(), 50 | dateTime.getDayOfMonth(), 51 | START_OF_DAY.getHour(), START_OF_DAY.getMinute() 52 | ); 53 | 54 | while (nextOpenTime.getDayOfWeek() != DayOfWeek.MONDAY) { 55 | nextOpenTime = nextOpenTime.plusDays(1); 56 | } 57 | return nextOpenTime; 58 | } 59 | 60 | 61 | /** 62 | * Is end of day 63 | * @param dateTime 64 | * @return 65 | */ 66 | public static boolean isEOD(LocalDateTime dateTime) { 67 | if (dateTime.toLocalTime().isAfter(END_OF_DAY)) { 68 | return true; 69 | } 70 | return false; 71 | } 72 | 73 | /** 74 | * 75 | * @param date 76 | * @return 77 | */ 78 | public static LocalTime getOpenWeekTimeMonday(LocalDate date) { 79 | return START_OF_DAY; 80 | } 81 | 82 | /** 83 | * 84 | * @param date 85 | * @return 86 | */ 87 | public static LocalTime getCloseWeekTimeSaturday(LocalDate date) { 88 | return CLOSE_WEEEK_SATURDAY; 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /forex/src/test/java/moduletest/HistoryWithTransactions.java: -------------------------------------------------------------------------------- 1 | package moduletest; 2 | 3 | 4 | import com.apssouza.mytrade.trading.api.CycleHistoryDto; 5 | import com.apssouza.mytrade.trading.api.ExecutionType; 6 | import com.apssouza.mytrade.trading.api.ForexBuilder; 7 | import com.apssouza.mytrade.trading.api.ForexEngine; 8 | import com.apssouza.mytrade.trading.api.SessionType; 9 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 10 | 11 | import org.junit.Assert; 12 | 13 | import java.math.BigDecimal; 14 | import java.time.LocalDateTime; 15 | import java.util.List; 16 | 17 | import io.cucumber.java.en.Given; 18 | import io.cucumber.java.en.Then; 19 | import io.cucumber.java.en.When; 20 | 21 | public class HistoryWithTransactions { 22 | 23 | private ForexEngine engine; 24 | private List sessionHistory; 25 | 26 | @Given("^2 buy signals and 1 sell signal$") 27 | public void startRunningTheEngine() { 28 | var date = LocalDateTime.of(2021, 9, 10, 10, 0, 0); 29 | 30 | var systemName = "signal_test"; 31 | LocalDateTime start = date.minusDays(1); 32 | LocalDateTime end = date; 33 | 34 | var feedBuilder = new FeedMockBuilder(); 35 | feedBuilder.withPrice(start); 36 | feedBuilder.withSignal(start.plusSeconds(1),"BUY"); 37 | feedBuilder.withSignal(start.plusSeconds(2),"SELL"); 38 | feedBuilder.withSignal(start.plusSeconds(3),"BUY"); 39 | 40 | engine = new ForexBuilder() 41 | .withSystemName(systemName) 42 | .withStartTime(start) 43 | .withEndTime(end) 44 | .withEquity(BigDecimal.valueOf(100000L)) 45 | .withSessionType(SessionType.BACK_TEST) 46 | .withExecutionType(ExecutionType.SIMULATED) 47 | .withFeed(feedBuilder.build()) 48 | .build(); 49 | 50 | engine.start(); 51 | } 52 | 53 | @When("^retrieving session history$") 54 | public void retrieveSessionHistory() { 55 | this.sessionHistory = engine.getHistory(); 56 | } 57 | 58 | @Then("^return a history with (\\d+) transactions$") 59 | public void countTransactions(int total) { 60 | Assert.assertEquals(total, sessionHistory.size()); 61 | } 62 | 63 | @Then("^history should contain 2 buys and 1 sell orders$") 64 | public void countOrders() { 65 | Assert.assertEquals(OrderDto.OrderAction.BUY, sessionHistory.get(0).events().get(0).event().getOrder().action()); 66 | Assert.assertEquals(OrderDto.OrderAction.SELL, sessionHistory.get(1).events().get(0).event().getOrder().action()); 67 | Assert.assertEquals(OrderDto.OrderAction.BUY, sessionHistory.get(2).events().get(0).event().getOrder().action()); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /runner/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.apssouza.mytrade 9 | trading-system-parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | com.apssouza.mytrade 14 | runner 15 | 1.0-SNAPSHOT 16 | jar 17 | 18 | 19 | com.apssouza.mytrade.runner.Application 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-devtools 26 | runtime 27 | true 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-web 32 | 33 | 34 | com.apssouza.mytrade 35 | forex 36 | 37 | 38 | 39 | 40 | org.cfg4j 41 | cfg4j-core 42 | 4.4.0 43 | 44 | 45 | 46 | 47 | ch.qos.logback 48 | logback-core 49 | 1.2.13 50 | 51 | 52 | ch.qos.logback 53 | logback-classic 54 | 1.2.13 55 | 56 | 57 | 58 | net.logstash.logback 59 | logstash-logback-encoder 60 | 6.4 61 | 62 | 63 | 64 | com.fasterxml.jackson.core 65 | jackson-core 66 | 2.12.4 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | org.springframework.boot 75 | spring-boot-maven-plugin 76 | 77 | 78 | true 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /forex/src/test/java/com/apssouza/mytrade/trading/domain/forex/portfolio/PositionBuilder.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.portfolio; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation.StopOrderDto; 4 | 5 | import java.math.BigDecimal; 6 | import java.time.LocalDateTime; 7 | import java.util.EnumMap; 8 | 9 | public class PositionBuilder { 10 | 11 | PositionDto.PositionType type = PositionDto.PositionType.LONG; 12 | String symbol = "AUDUSD"; 13 | Integer qtd = 1000; 14 | BigDecimal price = BigDecimal.valueOf(1.004); 15 | LocalDateTime timestamp = LocalDateTime.MIN; 16 | String identifier = "AUDUSD"; 17 | PositionDto.ExitReason exitReason = null; 18 | FilledOrderDto filledOrder = new FilledOrderDto(timestamp, symbol, null,qtd, price,identifier, 1); 19 | PositionDto.PositionStatus positionStatus = PositionDto.PositionStatus.FILLED; 20 | private EnumMap stopOrders = new EnumMap(StopOrderDto.StopOrderType.class); 21 | 22 | public void withType(PositionDto.PositionType type) { 23 | this.type = type; 24 | } 25 | 26 | public void withSymbol(String symbol) { 27 | this.symbol = symbol; 28 | } 29 | 30 | public void withQtd(Integer qtd) { 31 | this.qtd = qtd; 32 | } 33 | 34 | public void withPrice(BigDecimal price) { 35 | this.price = price; 36 | } 37 | 38 | public void withTimestamp(LocalDateTime timestamp) { 39 | this.timestamp = timestamp; 40 | } 41 | 42 | public void withIdentifier(String identifier) { 43 | this.identifier = identifier; 44 | } 45 | 46 | public void withExitReason(PositionDto.ExitReason exitReason) { 47 | this.exitReason = exitReason; 48 | } 49 | 50 | public void withFilledOrder(FilledOrderDto filledOrder) { 51 | this.filledOrder = filledOrder; 52 | } 53 | 54 | public PositionBuilder withPositionStatus(PositionDto.PositionStatus positionStatus) { 55 | this.positionStatus = positionStatus; 56 | return this; 57 | } 58 | 59 | public PositionDto build() { 60 | 61 | PositionDto position = new PositionDto( 62 | type, 63 | symbol, 64 | qtd, 65 | price, 66 | timestamp, 67 | identifier, 68 | filledOrder, 69 | exitReason, 70 | positionStatus, 71 | price, 72 | price, 73 | null 74 | ); 75 | 76 | if (stopOrders.isEmpty()) { 77 | return position; 78 | } 79 | return new PositionDto(position, stopOrders); 80 | } 81 | 82 | 83 | public void addStopOrder(StopOrderDto stopOrder) { 84 | this.stopOrders.put(stopOrder.type(), stopOrder); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /common/src/main/java/com/apssouza/mytrade/common/appconfig/ServiceConfig.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.common.appconfig; 2 | 3 | 4 | import org.cfg4j.provider.ConfigurationProvider; 5 | import org.cfg4j.provider.ConfigurationProviderBuilder; 6 | import org.cfg4j.source.ConfigurationSource; 7 | import org.cfg4j.source.classpath.ClasspathConfigurationSource; 8 | import org.cfg4j.source.compose.MergeConfigurationSource; 9 | import org.cfg4j.source.files.FilesConfigurationSource; 10 | import org.cfg4j.source.system.EnvironmentVariablesConfigurationSource; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.nio.file.Path; 15 | import java.nio.file.Paths; 16 | import java.util.Arrays; 17 | import java.util.List; 18 | import java.util.stream.Stream; 19 | 20 | /** 21 | * Class responsible to config the application 22 | */ 23 | public class ServiceConfig { 24 | 25 | private static final Logger LOG = LoggerFactory.getLogger(ServiceConfig.class); 26 | 27 | /** 28 | * Load the configuration properties 29 | * 30 | * @param configPathDir config path 31 | * @param env environment 32 | * @return config provider 33 | */ 34 | public static ConfigurationProvider load(String configPathDir, String env) { 35 | String configLocation = ServiceConfig.class.getProtectionDomain().getCodeSource().getLocation().getFile(); 36 | if (configPathDir != null) { 37 | configLocation = configPathDir; 38 | } 39 | 40 | var filePath = Paths.get(String.format("%s/properties.%s.yml", configLocation, env)); 41 | List classPathFiles = Arrays.asList(Paths.get("properties.yml")); 42 | List filePaths = Arrays.asList(filePath); 43 | 44 | Stream.of(classPathFiles, filePaths) 45 | .flatMap(i -> i.stream()) 46 | .forEach(file -> LOG.info("Loading configuration from {}", file)); 47 | 48 | ConfigurationSource classPathSource = new ClasspathConfigurationSource(() -> classPathFiles); 49 | ConfigurationSource fileSource = new FilesConfigurationSource(() -> filePaths); 50 | ConfigurationSource envSource = new EnvironmentVariablesConfigurationSource(); 51 | ConfigurationSource mergedSource = new MergeConfigurationSource( 52 | classPathSource, 53 | fileSource, 54 | envSource 55 | ); 56 | return new ConfigurationProviderBuilder() 57 | .withConfigurationSource(mergedSource) 58 | .build(); 59 | } 60 | 61 | /** 62 | * Bind the gRPC server properties to the ServerConfigDto 63 | * 64 | * @param config amadeus service's server config 65 | * @return {@link ServerConfigDto} 66 | */ 67 | public static ServerConfigDto bindServerProps(ConfigurationProvider config) { 68 | return config.bind("grpc.server", ServerConfigDto.class); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/api/ForexBuilder.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.api; 2 | 3 | import com.apssouza.mytrade.feed.api.FeedBuilder; 4 | import com.apssouza.mytrade.feed.api.FeedModule; 5 | import com.apssouza.mytrade.trading.domain.forex.feed.FeedService; 6 | import com.apssouza.mytrade.trading.domain.forex.feed.FeedFactory; 7 | import com.apssouza.mytrade.trading.domain.forex.session.TradingSession; 8 | 9 | import java.math.BigDecimal; 10 | import java.time.LocalDateTime; 11 | 12 | public class ForexBuilder { 13 | 14 | private FeedService feed; 15 | private String systemName; 16 | private LocalDateTime start; 17 | private LocalDateTime end; 18 | private SessionType sessionType; 19 | private ExecutionType executionType; 20 | private BigDecimal equity; 21 | 22 | public ForexBuilder withFeed(FeedService feed) { 23 | this.feed = feed; 24 | return this; 25 | } 26 | 27 | public ForexBuilder withSystemName(String systemName) { 28 | this.systemName = systemName; 29 | return this; 30 | } 31 | 32 | public ForexBuilder withStartTime(LocalDateTime start) { 33 | this.start = start; 34 | return this; 35 | } 36 | 37 | public ForexBuilder withEndTime(LocalDateTime end) { 38 | this.end = end; 39 | return this; 40 | } 41 | 42 | public ForexBuilder withSessionType(SessionType sessionType) { 43 | this.sessionType = sessionType; 44 | return this; 45 | } 46 | 47 | public ForexBuilder withExecutionType(ExecutionType executionType) { 48 | this.executionType = executionType; 49 | return this; 50 | } 51 | 52 | public ForexBuilder withEquity(BigDecimal equity) { 53 | this.equity = equity; 54 | return this; 55 | } 56 | 57 | public ForexEngine build() { 58 | FeedModule feedModule = new FeedBuilder() 59 | .withStartTime(start) 60 | .withEndTime(end) 61 | .withSignalName(systemName) 62 | .build(); 63 | 64 | if (feed == null){ 65 | this.feed = FeedFactory.create(feedModule); 66 | } 67 | var tradingSession = new TradingSession( 68 | equity, 69 | start, 70 | end, 71 | sessionType, 72 | systemName, 73 | getSafeExecutionType(), 74 | feed 75 | ); 76 | registerShutDownListener(tradingSession); 77 | return new ForexEngine(tradingSession); 78 | } 79 | 80 | private static void registerShutDownListener(TradingSession tradingSession) { 81 | Thread shutdown = new Thread(tradingSession::shutdown); 82 | shutdown.setDaemon(true); 83 | Runtime.getRuntime().addShutdownHook(shutdown); 84 | } 85 | 86 | private ExecutionType getSafeExecutionType() { 87 | return sessionType == SessionType.BACK_TEST ? ExecutionType.SIMULATED : executionType; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/brokerintegration/StopOrderPriceMonitor.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.brokerintegration; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 5 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation.StopOrderDto; 6 | 7 | import java.util.*; 8 | 9 | class StopOrderPriceMonitor { 10 | 11 | 12 | public Set getFilledOrders(Map priceMap, Map allStopOrders) { 13 | Set filledOrders = new HashSet<>(); 14 | for (Map.Entry entry : allStopOrders.entrySet()) { 15 | StopOrderDto stopOrder = allStopOrders.get(entry.getKey()); 16 | if (filledOrders.contains(stopOrder)) { 17 | continue; 18 | } 19 | 20 | if (!stopOrder.status().equals(StopOrderDto.StopOrderStatus.SUBMITTED)) { 21 | continue; 22 | } 23 | PriceDto df_current_price = priceMap.get(stopOrder.symbol()); 24 | filledOrderCheck(filledOrders, stopOrder, df_current_price); 25 | 26 | } 27 | return filledOrders; 28 | } 29 | 30 | private void filledOrderCheck( 31 | Set filledPositions, 32 | StopOrderDto stopOrder, PriceDto priceDto 33 | ) { 34 | if (stopOrder.action().equals(OrderDto.OrderAction.BUY)) { 35 | if(hasFilledBuyOrder(stopOrder, priceDto)){ 36 | filledPositions.add(stopOrder); 37 | } 38 | } 39 | 40 | if (stopOrder.action().equals(OrderDto.OrderAction.SELL)) { 41 | if(hasFilledSellOrder(stopOrder, priceDto)){ 42 | filledPositions.add(stopOrder); 43 | } 44 | } 45 | } 46 | 47 | 48 | private boolean hasFilledSellOrder(StopOrderDto stopOrder, PriceDto priceDto) { 49 | if (stopOrder.type().equals(StopOrderDto.StopOrderType.TAKE_PROFIT)) { 50 | if (priceDto.high().compareTo(stopOrder.price()) >= 0) { 51 | return true; 52 | } 53 | } 54 | if (stopOrder.type().equals(StopOrderDto.StopOrderType.STOP_LOSS)) { 55 | if (priceDto.high().compareTo(stopOrder.price()) <= 0) { 56 | return true; 57 | } 58 | } 59 | return false; 60 | } 61 | 62 | private boolean hasFilledBuyOrder( StopOrderDto stopOrder, PriceDto priceDto) { 63 | if (stopOrder.type().equals(StopOrderDto.StopOrderType.TAKE_PROFIT)) { 64 | if (priceDto.low().compareTo(stopOrder.price()) <= 0) { 65 | return true; 66 | } 67 | } 68 | if (stopOrder.type().equals(StopOrderDto.StopOrderType.STOP_LOSS)) { 69 | if (priceDto.high().compareTo(stopOrder.price()) >= 0) { 70 | return true; 71 | } 72 | } 73 | return false; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/feed/pricefeed/HistoricalPriceStream.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.feed.pricefeed; 2 | 3 | import com.apssouza.mytrade.feed.api.PriceDto; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.PriceChangedEvent; 5 | import com.apssouza.mytrade.trading.domain.forex.common.events.EndedTradingDayEvent; 6 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 7 | import com.apssouza.mytrade.trading.domain.forex.common.events.SessionFinishedEvent; 8 | import com.apssouza.mytrade.trading.domain.forex.common.ForexException; 9 | import com.apssouza.mytrade.trading.domain.forex.common.TradingHelper; 10 | 11 | import java.time.LocalDate; 12 | import java.time.LocalDateTime; 13 | import java.util.Map; 14 | import java.util.concurrent.BlockingQueue; 15 | 16 | /** 17 | * Provide a feed price steam based on historical price 18 | */ 19 | class HistoricalPriceStream implements PriceStream{ 20 | 21 | private final BlockingQueue eventQueue; 22 | private final PriceFeedHandler priceFeedHandler; 23 | 24 | public HistoricalPriceStream(BlockingQueue eventQueue, PriceFeedHandler priceFeedHandler) { 25 | this.eventQueue = eventQueue; 26 | this.priceFeedHandler = priceFeedHandler; 27 | } 28 | 29 | @Override 30 | public void start(LocalDateTime start, LocalDateTime end) { 31 | LocalDateTime current = start; 32 | LocalDate lastDayProcessed = start.toLocalDate().minusDays(1); 33 | boolean trading = true; 34 | while (current.compareTo(end) <= 0) { 35 | if (TradingHelper.hasEndedTradingTime(current) && trading){ 36 | addToQueue(new EndedTradingDayEvent( 37 | current, 38 | priceFeedHandler.getPriceSymbolMapped(current) 39 | )); 40 | trading = false; 41 | } 42 | if (!TradingHelper.isTradingTime(current)) { 43 | current = current.plusSeconds(1L); 44 | continue; 45 | } 46 | trading = true; 47 | if (lastDayProcessed.compareTo(current.toLocalDate()) < 0) { 48 | lastDayProcessed = current.toLocalDate(); 49 | } 50 | PriceChangedEvent event = new PriceChangedEvent( 51 | current, 52 | priceFeedHandler.getPriceSymbolMapped(current) 53 | ); 54 | 55 | addToQueue(event); 56 | current = current.plusSeconds(1L); 57 | } 58 | SessionFinishedEvent endEvent = new SessionFinishedEvent( 59 | current, 60 | priceFeedHandler.getPriceSymbolMapped(current) 61 | ); 62 | addToQueue(endEvent); 63 | } 64 | 65 | @Override 66 | public Map getPriceSymbolMapped(LocalDateTime current) { 67 | return priceFeedHandler.getPriceSymbolMapped(current); 68 | } 69 | 70 | private void addToQueue(Event event) { 71 | try { 72 | eventQueue.put(event); 73 | } catch (InterruptedException e) { 74 | throw new ForexException(e); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/orderbook/TransactionDto.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.orderbook; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 4 | import com.apssouza.mytrade.trading.domain.forex.portfolio.FilledOrderDto; 5 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.Objects; 9 | 10 | public class TransactionDto { 11 | private final LocalDateTime time; 12 | private final String identifier; 13 | private OrderDto order; 14 | private PositionDto position; 15 | private FilledOrderDto filledOrder; 16 | private TransactionState state; 17 | 18 | public TransactionDto(LocalDateTime time, String identifier) { 19 | this.time = time; 20 | this.identifier = identifier; 21 | } 22 | 23 | public LocalDateTime getTime() { 24 | return this.time; 25 | } 26 | 27 | public String getIdentifier() { 28 | return this.identifier; 29 | } 30 | 31 | public OrderDto getOrder() { 32 | return this.order; 33 | } 34 | 35 | public PositionDto getPosition() { 36 | return this.position; 37 | } 38 | 39 | public FilledOrderDto getFilledOrder() { 40 | return this.filledOrder; 41 | } 42 | 43 | public TransactionState getState() { 44 | return this.state; 45 | } 46 | 47 | public void setOrder(OrderDto order) { 48 | this.order = order; 49 | } 50 | 51 | public void setPosition(PositionDto position) { 52 | this.position = position; 53 | } 54 | 55 | public void setFilledOrder(FilledOrderDto filledOrder) { 56 | this.filledOrder = filledOrder; 57 | } 58 | 59 | public void setState(TransactionState state) { 60 | this.state = state; 61 | } 62 | 63 | @Override 64 | public boolean equals(Object o) { 65 | if (o == this) { 66 | return true; 67 | } 68 | if (!(o instanceof TransactionDto)) { 69 | return false; 70 | } 71 | final TransactionDto other =(TransactionDto) o; 72 | if (!other.canEqual( this)) { 73 | return false; 74 | } 75 | final Object this$time = this.getTime(); 76 | final Object other$time = other.getTime(); 77 | if (this$time == null ? other$time != null : !this$time.equals(other$time)) { 78 | return false; 79 | } 80 | final Object this$identifier = this.getIdentifier(); 81 | final Object other$identifier = other.getIdentifier(); 82 | if (this$identifier == null ? other$identifier != null : !this$identifier.equals(other$identifier)) { 83 | return false; 84 | } 85 | return true; 86 | } 87 | 88 | protected boolean canEqual(Object other) { 89 | return other instanceof TransactionDto; 90 | } 91 | 92 | @Override 93 | public int hashCode() { 94 | return Objects.hashCode(this); 95 | } 96 | 97 | public enum TransactionState { 98 | ENTRY, REMOVE_QTD, ADD_QTD, EXIT 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /common/src/main/java/com/apssouza/mytrade/common/time/DateTimeHelper.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.common.time; 2 | 3 | import java.time.LocalDate; 4 | import java.time.LocalDateTime; 5 | import java.time.LocalTime; 6 | import java.time.ZoneId; 7 | import java.time.ZoneOffset; 8 | import java.time.format.DateTimeFormatter; 9 | import java.time.temporal.ChronoUnit; 10 | 11 | /** 12 | * Date time helper 13 | */ 14 | public class DateTimeHelper { 15 | public static final ZoneId DEFAULT_ZONEID = ZoneId.systemDefault(); 16 | public static final ZoneId ZONEID_UTC = ZoneId.of("UTC"); 17 | public static final ZoneOffset ZONEOFFSET_UTC = ZoneOffset.UTC; 18 | 19 | public static Interval calculate(LocalDateTime start, LocalDateTime end) { 20 | // count between dates 21 | long years = ChronoUnit.YEARS.between(start, end); 22 | long months = ChronoUnit.MONTHS.between(start, end); 23 | long weeks = ChronoUnit.WEEKS.between(start, end); 24 | long days = ChronoUnit.DAYS.between(start, end); 25 | long hours = ChronoUnit.HOURS.between(start, end); 26 | long minutes = ChronoUnit.MINUTES.between(start, end); 27 | long seconds = ChronoUnit.SECONDS.between(start, end); 28 | long milis = ChronoUnit.MILLIS.between(start, end); 29 | return new Interval(years, months, weeks, days, hours, minutes, seconds, milis); 30 | } 31 | 32 | public static Interval calculate(String start, String end) { 33 | LocalDateTime startDate = LocalDateTime.parse(start, DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")); 34 | LocalDateTime endDate = LocalDateTime.parse(end, DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")); 35 | return calculate(startDate, endDate); 36 | } 37 | 38 | public static boolean isPastDate(LocalDateTime date) { 39 | LocalDateTime now = LocalDateTime.now(); 40 | return date.isBefore(now); 41 | } 42 | 43 | public static boolean isPastDate(String date) { 44 | LocalDateTime startDate = LocalDateTime.parse(date, DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")); 45 | return isPastDate(startDate); 46 | } 47 | 48 | public static String getCurrentTimeFormat(String format) { 49 | LocalDateTime now = LocalDateTime.now(); 50 | return now.format(DateTimeFormatter.ofPattern(format)); 51 | } 52 | 53 | public static boolean compare(LocalDateTime dt1, String operator, LocalDateTime dt2) { 54 | Interval interval = DateTimeHelper.calculate(dt1, dt2); 55 | if (operator.equals(">")) { 56 | return interval.getMilliseconds() < 0; 57 | } 58 | if (operator.equals(">=")) { 59 | return interval.getMilliseconds() <= 0; 60 | } 61 | if (operator.equals("<")) { 62 | return interval.getMilliseconds() > 0; 63 | } 64 | if (operator.equals("<=")) { 65 | return interval.getMilliseconds() >= 0; 66 | } 67 | 68 | throw new RuntimeException("Invalid operator"); 69 | } 70 | 71 | public static boolean compare(LocalDate dt1, String operator, LocalDate dt2) { 72 | return DateTimeHelper.compare( 73 | LocalDateTime.of(dt1, LocalTime.MIN), 74 | operator, 75 | LocalDateTime.of(dt2, LocalTime.MIN) 76 | ); 77 | } 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/order/OrderFoundListener.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.order; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.brokerintegration.BrokerIntegrationService; 4 | import com.apssouza.mytrade.trading.domain.forex.common.events.Event; 5 | import com.apssouza.mytrade.trading.domain.forex.common.events.OrderFilledEvent; 6 | import com.apssouza.mytrade.trading.domain.forex.common.events.OrderFoundEvent; 7 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.Observer; 8 | import com.apssouza.mytrade.trading.domain.forex.portfolio.FilledOrderDto; 9 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.RiskManagementService; 10 | import com.apssouza.mytrade.trading.domain.forex.common.observerinfra.EventNotifier; 11 | import static com.apssouza.mytrade.trading.domain.forex.order.OrderDto.OrderOrigin.EXITS; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.logging.Logger; 16 | 17 | class OrderFoundListener implements Observer { 18 | 19 | private static Logger log = Logger.getLogger(OrderFoundListener.class.getSimpleName()); 20 | private final BrokerIntegrationService executionHandler; 21 | private final OrderService orderService; 22 | private final EventNotifier eventNotifier; 23 | private final RiskManagementService riskManagementService; 24 | 25 | public OrderFoundListener( 26 | BrokerIntegrationService executionHandler, 27 | OrderService orderService, 28 | EventNotifier eventNotifier, 29 | RiskManagementService riskManagementService 30 | ) { 31 | 32 | this.executionHandler = executionHandler; 33 | this.orderService = orderService; 34 | this.eventNotifier = eventNotifier; 35 | this.riskManagementService = riskManagementService; 36 | } 37 | 38 | @Override 39 | public void update(final Event e) { 40 | if (!(e instanceof OrderFoundEvent event)) { 41 | return; 42 | } 43 | 44 | List orders = event.getOrders(); 45 | if (orders.isEmpty()) { 46 | log.info("No orders"); 47 | return; 48 | } 49 | 50 | log.info(orders.size() + " new orders"); 51 | List exitedPositions = new ArrayList<>(); 52 | for (OrderDto order : orders) { 53 | if (order.origin() == EXITS) { 54 | exitedPositions.add(order.symbol()); 55 | } 56 | } 57 | 58 | for (OrderDto order : orders) { 59 | if (!riskManagementService.canExecuteOrder(e, order, new ArrayList<>(), exitedPositions)) { 60 | orderService.updateOrderStatus(order.id(), OrderDto.OrderStatus.CANCELLED); 61 | continue; 62 | } 63 | processNewOrder(order, event); 64 | } 65 | } 66 | 67 | private void processNewOrder(OrderDto order, OrderFoundEvent event) { 68 | FilledOrderDto filledOrder = executionHandler.executeOrder(order); 69 | if (filledOrder != null) { 70 | eventNotifier.notify(new OrderFilledEvent( 71 | filledOrder.time(), 72 | event.getPrice(), 73 | filledOrder 74 | )); 75 | orderService.updateOrderStatus(order.id(), OrderDto.OrderStatus.EXECUTED); 76 | } else { 77 | orderService.updateOrderStatus(order.id(), OrderDto.OrderStatus.FAILED); 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /common/src/main/java/com/apssouza/mytrade/common/encrypt/AESPBEStringEncryptor.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.common.encrypt; 2 | 3 | import javax.crypto.Cipher; 4 | import javax.crypto.SecretKey; 5 | import javax.crypto.SecretKeyFactory; 6 | import javax.crypto.spec.IvParameterSpec; 7 | import javax.crypto.spec.PBEKeySpec; 8 | import javax.crypto.spec.SecretKeySpec; 9 | 10 | import java.io.UnsupportedEncodingException; 11 | import java.security.GeneralSecurityException; 12 | import java.security.NoSuchAlgorithmException; 13 | import java.security.SecureRandom; 14 | import java.security.spec.InvalidKeySpecException; 15 | import java.util.Base64; 16 | 17 | public class AESPBEStringEncryptor { 18 | 19 | private final SecretKey secretKeySpec; 20 | private final IvParameterSpec iv; 21 | 22 | public AESPBEStringEncryptor(final SecretKey secretKeySpec, IvParameterSpec iv) { 23 | this.secretKeySpec = secretKeySpec; 24 | this.iv = iv; 25 | } 26 | 27 | public String encrypt(String dataToEncrypt) throws GeneralSecurityException, UnsupportedEncodingException { 28 | Cipher pbeCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 29 | pbeCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv); 30 | byte[] cipherText = pbeCipher.doFinal(dataToEncrypt.getBytes("UTF-8")); 31 | return Base64.getEncoder().encodeToString(cipherText); 32 | } 33 | 34 | public String decrypt(String dataToDecrypt) throws GeneralSecurityException { 35 | Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 36 | cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv); 37 | byte[] decode = Base64.getDecoder().decode(dataToDecrypt); 38 | byte[] plainText = cipher.doFinal(decode); 39 | return new String(plainText); 40 | } 41 | 42 | 43 | static class Builder { 44 | private String secretKeyFactory = "PBKDF2WithHmacSHA512"; 45 | private int iterationCount = 40000; 46 | private int keyLength = 128; 47 | private String salt = "rand888s88"; 48 | private IvParameterSpec iv = generateIv(); 49 | 50 | public Builder withSecretKeyFactory(String factory) { 51 | this.secretKeyFactory = factory; 52 | return this; 53 | } 54 | 55 | public Builder withIterationCount(int iterationCount) { 56 | this.iterationCount = iterationCount; 57 | return this; 58 | } 59 | 60 | public Builder withKeyLength(int length) { 61 | this.keyLength = length; 62 | return this; 63 | } 64 | 65 | public Builder withSalt(String salt) { 66 | this.salt = salt; 67 | return this; 68 | } 69 | 70 | public Builder withIv(IvParameterSpec iv) { 71 | this.iv = iv; 72 | return this; 73 | } 74 | 75 | private IvParameterSpec generateIv() { 76 | byte[] iv = new byte[16]; 77 | new SecureRandom().nextBytes(iv); 78 | return new IvParameterSpec(iv); 79 | } 80 | 81 | public AESPBEStringEncryptor build(String key) throws NoSuchAlgorithmException, InvalidKeySpecException { 82 | SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(secretKeyFactory); 83 | PBEKeySpec keySpec = new PBEKeySpec(key.toCharArray(), salt.getBytes(), iterationCount, keyLength); 84 | SecretKey keyTmp = keyFactory.generateSecret(keySpec); 85 | return new AESPBEStringEncryptor(new SecretKeySpec(keyTmp.getEncoded(), "AES"), iv); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/api/ForexEngine.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.api; 2 | 3 | import com.apssouza.mytrade.feed.api.FeedBuilder; 4 | import com.apssouza.mytrade.trading.domain.forex.common.ForexException; 5 | import com.apssouza.mytrade.trading.domain.forex.feed.FeedFactory; 6 | import com.apssouza.mytrade.trading.domain.forex.orderbook.CycleHistoryDto; 7 | import com.apssouza.mytrade.trading.domain.forex.session.TradingSession; 8 | 9 | import java.math.BigDecimal; 10 | import java.sql.Connection; 11 | import java.sql.DriverManager; 12 | import java.sql.SQLException; 13 | import java.time.LocalDateTime; 14 | import java.time.LocalTime; 15 | import java.util.List; 16 | import java.util.stream.Collectors; 17 | import static java.time.LocalDate.of; 18 | 19 | 20 | public class ForexEngine { 21 | 22 | private final TradingSession tradingSession; 23 | 24 | public ForexEngine(final TradingSession tradingSession){ 25 | this.tradingSession = tradingSession; 26 | } 27 | 28 | public static void main(String[] args) { 29 | var date = of(2018, 9, 10); 30 | 31 | var systemName = "signal_test"; 32 | 33 | LocalDateTime start = LocalDateTime.of(date, LocalTime.MIN); 34 | LocalDateTime end = LocalDateTime.of(date.plusDays(10), LocalTime.MIN); 35 | 36 | var feed = new FeedBuilder() 37 | .withStartTime(start) 38 | .withEndTime(end) 39 | .withSignalName(systemName) 40 | // .withConnection(getConnection()) 41 | .build(); 42 | 43 | var engine =new ForexBuilder() 44 | .withSystemName(systemName) 45 | .withStartTime(start) 46 | .withEndTime(end) 47 | .withEquity(BigDecimal.valueOf(100000L)) 48 | .withSessionType(SessionType.BACK_TEST) 49 | .withExecutionType(ExecutionType.SIMULATED) 50 | .withFeed(FeedFactory.create(feed)) 51 | .build(); 52 | engine.start(); 53 | System.out.println("finished session"); 54 | } 55 | 56 | private static com.apssouza.mytrade.trading.api.CycleHistoryDto mapHistory(CycleHistoryDto c) { 57 | List transactions = c.getTransactions() 58 | .entrySet() 59 | .stream() 60 | .map(e -> new TransactionDto(e.getValue())) 61 | .collect(Collectors.toList()); 62 | return new com.apssouza.mytrade.trading.api.CycleHistoryDto(c.getTime(), transactions); 63 | } 64 | 65 | public void start() { 66 | tradingSession.start(); 67 | } 68 | 69 | public List getHistory() { 70 | List collect = tradingSession.getHistory() 71 | .stream() 72 | .filter(i -> i.getTransactions().size() > 0) 73 | .map(ForexEngine::mapHistory) 74 | .collect(Collectors.toList()); 75 | return collect; 76 | } 77 | 78 | 79 | private static Connection getConnection() throws ForexException { 80 | try { 81 | Class.forName("org.h2.Driver"); 82 | } catch (ClassNotFoundException ex) { 83 | throw new ForexException(ex); 84 | } 85 | String url = "jdbc:h2:mem:"; 86 | 87 | Connection con; 88 | try { 89 | con = DriverManager.getConnection(url); 90 | } catch (SQLException ex) { 91 | throw new ForexException(ex); 92 | } 93 | return con; 94 | } 95 | 96 | 97 | } 98 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/orderbook/TransactionsExporter.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.orderbook; 2 | 3 | import com.apssouza.mytrade.common.file.WriteFileHelper; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.math.BigDecimal; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.nio.file.Paths; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | class TransactionsExporter { 16 | 17 | public void exportCsv(List transactions, String filepath) throws IOException { 18 | Path path = Paths.get(filepath); 19 | if (!Files.exists(path)){ 20 | File file = new File(filepath); 21 | file.createNewFile(); 22 | } 23 | WriteFileHelper.write(filepath, getHeader() + "\n"); 24 | for (CycleHistoryDto item : transactions) { 25 | for (Map.Entry trans : item.getTransactions().entrySet()) { 26 | List line = Arrays.asList( 27 | trans.getValue().getIdentifier(), 28 | trans.getValue().getPosition() != null ? toString(trans.getValue().getPosition().initPrice()) : "", 29 | trans.getValue().getPosition() != null ? toString(trans.getValue().getPosition().currentPrice()) : "", 30 | trans.getValue().getPosition() != null ? toString(trans.getValue().getPosition().quantity()) : "", 31 | trans.getValue().getPosition() != null ? toString(trans.getValue().getPosition().initPrice().multiply(BigDecimal.valueOf(trans.getValue().getPosition().quantity()))) : "", 32 | trans.getValue().getPosition() != null ? toString(trans.getValue().getPosition().currentPrice().multiply(BigDecimal.valueOf(trans.getValue().getPosition().quantity()))) : "", 33 | trans.getValue().getPosition() != null ? toString(trans.getValue().getPosition().currentPrice().subtract(trans.getValue().getPosition().initPrice())) : "", 34 | trans.getValue().getFilledOrder() != null ? toString(trans.getValue().getFilledOrder().action()) : "", 35 | trans.getValue().getPosition() != null ? toString(trans.getValue().getPosition().timestamp()) : "", 36 | //trans.getValue().getPosition() != null ? trans.getValue().getPosition().getPlacedStopLoss().getPrice().toString(): "", 37 | //trans.getValue().getPosition() != null ? trans.getValue().getPosition().getTakeProfitOrder().getPrice().toString(): "", 38 | trans.getValue().getPosition() != null ? toString(trans.getValue().getPosition().exitReason()) : "", 39 | trans.getValue().getState() != null ? toString(trans.getValue().getState()) : "", 40 | "" 41 | ); 42 | WriteFileHelper.append(filepath, String.join(",", line) + "\n"); 43 | } 44 | } 45 | } 46 | 47 | private String toString(Object ob) { 48 | if (ob != null) { 49 | return ob.toString(); 50 | } 51 | return ""; 52 | } 53 | 54 | private List getHeader() { 55 | List line = Arrays.asList( 56 | "Identifier", 57 | "Init price", 58 | "End price", 59 | "Quantidade", 60 | "Init amount", 61 | "End amount", 62 | "Result", 63 | "Action", 64 | "Timestamp", 65 | "Exit reason", 66 | "State", 67 | "" 68 | ); 69 | return line; 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /forex/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 16 8 | 16 9 | 10 | 11 | 12 | com.apssouza.mytrade 13 | trading-system-parent 14 | 1.0-SNAPSHOT 15 | 16 | 17 | com.apssouza.mytrade 18 | forex 19 | 1.0-SNAPSHOT 20 | 21 | 22 | 23 | org.slf4j 24 | slf4j-api 25 | 1.7.30 26 | 27 | 28 | org.slf4j 29 | slf4j-simple 30 | 1.7.30 31 | provided 32 | 33 | 34 | com.apssouza.mytrade 35 | common 36 | 37 | 38 | com.apssouza.mytrade 39 | feed 40 | 41 | 42 | io.cucumber 43 | cucumber-java 44 | 45 | 46 | io.cucumber 47 | cucumber-junit 48 | 49 | 50 | org.mockito 51 | mockito-core 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/riskmanagement/stopordercreation/CreatorContext.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 4 | import com.apssouza.mytrade.trading.domain.forex.portfolio.PositionDto; 5 | import com.apssouza.mytrade.trading.domain.forex.common.NumberHelper; 6 | import com.apssouza.mytrade.trading.domain.forex.common.TradingHelper; 7 | 8 | import java.math.BigDecimal; 9 | import java.util.Optional; 10 | 11 | class CreatorContext { 12 | 13 | private final CreatorStrategy strategy; 14 | 15 | public CreatorContext(CreatorStrategy strategy) { 16 | this.strategy = strategy; 17 | } 18 | 19 | 20 | public StopOrderDto getHardStopLoss(PositionDto position) { 21 | OrderDto.OrderAction action = TradingHelper.getExitOrderActionFromPosition(position); 22 | BigDecimal stopPrice = strategy.getHardStopPrice(position); 23 | return new StopOrderDto( 24 | StopOrderDto.StopOrderType.HARD_STOP, 25 | null, 26 | StopOrderDto.StopOrderStatus.CREATED, 27 | action, 28 | NumberHelper.roundSymbolPrice(position.symbol(), stopPrice), 29 | null, 30 | position.symbol(), 31 | position.quantity(), 32 | position.identifier() 33 | ); 34 | } 35 | 36 | public Optional getTrailingStopOrder(PositionDto position, BigDecimal priceClose) { 37 | OrderDto.OrderAction action = TradingHelper.getExitOrderActionFromPosition(position); 38 | Optional stopPrice = strategy.getTrailingStopPrice(position, priceClose); 39 | 40 | if (!stopPrice.isPresent()) { 41 | return Optional.empty(); 42 | } 43 | 44 | return Optional.of(new StopOrderDto( 45 | StopOrderDto.StopOrderType.TRAILLING_STOP, 46 | null, 47 | StopOrderDto.StopOrderStatus.CREATED, 48 | action, 49 | stopPrice.get(), 50 | null, 51 | position.symbol(), 52 | position.quantity(), 53 | position.identifier() 54 | )); 55 | } 56 | 57 | public Optional getEntryStopOrder(PositionDto position, BigDecimal priceClose) { 58 | OrderDto.OrderAction action = TradingHelper.getExitOrderActionFromPosition(position); 59 | BigDecimal stopPrice = strategy.getEntryStopPrice(position, priceClose); 60 | 61 | if (stopPrice == null) { 62 | return Optional.empty(); 63 | } 64 | 65 | return Optional.of(new StopOrderDto( 66 | StopOrderDto.StopOrderType.ENTRY_STOP, 67 | null, 68 | StopOrderDto.StopOrderStatus.CREATED, 69 | action, 70 | NumberHelper.roundSymbolPrice(position.symbol(), stopPrice), 71 | null, 72 | position.symbol(), 73 | position.quantity(), 74 | position.identifier() 75 | )); 76 | 77 | } 78 | 79 | 80 | public StopOrderDto getProfitStopOrder(PositionDto position) { 81 | BigDecimal stopPrice = strategy.getProfitStopPrice(position); 82 | OrderDto.OrderAction action = TradingHelper.getExitOrderActionFromPosition(position); 83 | stopPrice = NumberHelper.roundSymbolPrice(position.symbol(), stopPrice); 84 | return new StopOrderDto( 85 | StopOrderDto.StopOrderType.TAKE_PROFIT, 86 | null, 87 | StopOrderDto.StopOrderStatus.CREATED, 88 | action, 89 | stopPrice, 90 | null, 91 | position.symbol(), 92 | position.quantity(), 93 | position.identifier() 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /forex/src/main/java/com/apssouza/mytrade/trading/domain/forex/portfolio/PositionDto.java: -------------------------------------------------------------------------------- 1 | package com.apssouza.mytrade.trading.domain.forex.portfolio; 2 | 3 | import com.apssouza.mytrade.trading.domain.forex.common.NumberHelper; 4 | import com.apssouza.mytrade.trading.domain.forex.common.Symbol; 5 | import com.apssouza.mytrade.trading.domain.forex.order.OrderDto; 6 | import com.apssouza.mytrade.trading.domain.forex.riskmanagement.stopordercreation.StopOrderDto; 7 | 8 | import java.math.BigDecimal; 9 | import java.math.RoundingMode; 10 | import java.time.LocalDateTime; 11 | import java.util.EnumMap; 12 | import static java.math.BigDecimal.valueOf; 13 | 14 | public record PositionDto( 15 | PositionType positionType, 16 | String symbol, 17 | int quantity, 18 | BigDecimal initPrice, 19 | LocalDateTime timestamp, 20 | String identifier, 21 | FilledOrderDto filledOrder, 22 | ExitReason exitReason, 23 | PositionStatus status, 24 | BigDecimal currentPrice, 25 | BigDecimal avgPrice, 26 | EnumMap stopOrders 27 | ) { 28 | 29 | public PositionDto { 30 | currentPrice = NumberHelper.roundSymbolPrice(symbol, initPrice); 31 | } 32 | 33 | 34 | public PositionDto(PositionDto position, EnumMap stopOrders) { 35 | this( 36 | position.positionType(), 37 | position.symbol(), 38 | position.quantity(), 39 | position.initPrice(), 40 | position.timestamp(), 41 | position.identifier(), 42 | position.filledOrder(), 43 | position.exitReason(), 44 | position.status(), 45 | position.currentPrice(), 46 | position.avgPrice(), 47 | stopOrders 48 | ); 49 | } 50 | 51 | 52 | public PositionDto(PositionDto position, int qtd, BigDecimal price, BigDecimal avgPrice) { 53 | this( 54 | position.positionType(), 55 | position.symbol(), 56 | qtd, 57 | price, 58 | position.timestamp(), 59 | position.identifier(), 60 | position.filledOrder(), 61 | position.exitReason(), 62 | position.status(), 63 | position.currentPrice(), 64 | avgPrice, 65 | position.stopOrders() 66 | ); 67 | 68 | } 69 | 70 | public BigDecimal getNewAveragePrice(int qtd, BigDecimal price) { 71 | if (avgPrice() == null){ 72 | return price; 73 | } 74 | var newQuantity = quantity() + qtd; 75 | var newCost = currentPrice().multiply(valueOf(qtd)); 76 | var newTotalCost = avgPrice().add(newCost); 77 | int pipScale = Symbol.valueOf(symbol()).getPipScale(); 78 | return newTotalCost.divide(valueOf(newQuantity), pipScale, RoundingMode.HALF_UP); 79 | } 80 | 81 | public boolean isPositionAlive() { 82 | return status == PositionDto.PositionStatus.OPEN || status == PositionDto.PositionStatus.FILLED; 83 | } 84 | 85 | 86 | public StopOrderDto getPlacedStopLoss() { 87 | return stopOrders.get(StopOrderDto.StopOrderType.STOP_LOSS); 88 | } 89 | 90 | public enum PositionType { 91 | LONG, SHORT; 92 | 93 | public OrderDto.OrderAction getOrderAction() { 94 | if (this == LONG) { 95 | return OrderDto.OrderAction.BUY; 96 | } 97 | return OrderDto.OrderAction.SELL; 98 | } 99 | } 100 | 101 | public enum PositionStatus { 102 | OPEN, FILLED, CANCELLED, CLOSED 103 | } 104 | 105 | public enum ExitReason { 106 | STOP_ORDER_FILLED, COUNTER_SIGNAL, RECONCILIATION_FAILED, END_OF_DAY, PORTFOLIO_EXCEPTION; 107 | } 108 | } 109 | --------------------------------------------------------------------------------