From 1fd30130cf2cbf81a4de3fdb891acf26d95733c7 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 1 Jul 2026 12:33:36 +0200 Subject: [PATCH] Market order logging --- .gitignore | 5 +- .../smartasset/client/MarketOrderClient.kt | 39 ++++++ .../smartasset/model/MarketOrder.kt | 16 +++ .../client/MarketOrderClientTest.kt | 130 ++++++++++++++++++ 4 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/com/frankenergie/smartasset/client/MarketOrderClient.kt create mode 100644 src/main/kotlin/com/frankenergie/smartasset/model/MarketOrder.kt create mode 100644 src/test/kotlin/com/frankenergie/smartasset/client/MarketOrderClientTest.kt diff --git a/.gitignore b/.gitignore index 9860ca8..8f95dd0 100644 --- a/.gitignore +++ b/.gitignore @@ -39,5 +39,6 @@ out/ ### Kotlin ### .kotlin -# simpler tests -test_steering_signals.log +# testing sideeffects +*steering_signals.log +*market_orders.log diff --git a/src/main/kotlin/com/frankenergie/smartasset/client/MarketOrderClient.kt b/src/main/kotlin/com/frankenergie/smartasset/client/MarketOrderClient.kt new file mode 100644 index 0000000..2e5e12b --- /dev/null +++ b/src/main/kotlin/com/frankenergie/smartasset/client/MarketOrderClient.kt @@ -0,0 +1,39 @@ +package com.frankenergie.smartasset.client + +import com.frankenergie.smartasset.model.MarketOrder +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Component +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardOpenOption + +@Component +class MarketOrderClient( + @Value("\${market.orders.file:market_orders.log}") private val filePath: String +) { + private val file: Path = Path.of(filePath) + + init { + if (!Files.exists(file)) { + Files.createFile(file) + } + } + + fun sendOrder(order: MarketOrder) { + val line = formatOrder(order) + Files.writeString(file, "$line\n", StandardOpenOption.APPEND) + } + + fun getAllOrders(): List { + if (!Files.exists(file)) return emptyList() + return Files.readAllLines(file).filter { it.isNotBlank() } + } + + fun clear() { + Files.writeString(file, "") + } + + private fun formatOrder(order: MarketOrder): String { + return "${order.timestamp}|${order.orderId}|${order.groupId}|${order.deliveryStart}|${order.deliveryEnd}|${order.side}|${order.quantity}|${order.price}" + } +} diff --git a/src/main/kotlin/com/frankenergie/smartasset/model/MarketOrder.kt b/src/main/kotlin/com/frankenergie/smartasset/model/MarketOrder.kt new file mode 100644 index 0000000..f5540b1 --- /dev/null +++ b/src/main/kotlin/com/frankenergie/smartasset/model/MarketOrder.kt @@ -0,0 +1,16 @@ +package com.frankenergie.smartasset.model + +import java.math.BigDecimal +import java.time.Instant +import java.time.LocalDateTime + +data class MarketOrder( + val orderId: String, + val groupId: String, + val deliveryStart: LocalDateTime, + val deliveryEnd: LocalDateTime, + val side: OrderSide, + val quantity: BigDecimal, + val price: BigDecimal, + val timestamp: Instant = Instant.now() +) diff --git a/src/test/kotlin/com/frankenergie/smartasset/client/MarketOrderClientTest.kt b/src/test/kotlin/com/frankenergie/smartasset/client/MarketOrderClientTest.kt new file mode 100644 index 0000000..807a35a --- /dev/null +++ b/src/test/kotlin/com/frankenergie/smartasset/client/MarketOrderClientTest.kt @@ -0,0 +1,130 @@ +package com.frankenergie.smartasset.client + +import com.frankenergie.smartasset.model.MarketOrder +import com.frankenergie.smartasset.model.OrderSide +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.math.BigDecimal +import java.nio.file.Files +import java.nio.file.Path +import java.time.LocalDateTime + +class MarketOrderClientTest { + + private val testFilePath = "./test_market_orders.log" + private lateinit var client: MarketOrderClient + + @BeforeEach + fun setUp() { + Files.deleteIfExists(Path.of(testFilePath)) + client = MarketOrderClient(testFilePath) + } + + @AfterEach + fun tearDown() { + Files.deleteIfExists(Path.of(testFilePath)) + } + + @Test + fun `sendOrder writes order to file`() { + val order = MarketOrder( + orderId = "order-123", + groupId = "A", + deliveryStart = LocalDateTime.of(2024, 1, 15, 10, 0), + deliveryEnd = LocalDateTime.of(2024, 1, 15, 10, 15), + side = OrderSide.BUY, + quantity = BigDecimal("10"), + price = BigDecimal("50.00") + ) + + client.sendOrder(order) + + val lines = client.getAllOrders() + assertEquals(1, lines.size) + assertTrue(lines[0].contains("order-123")) + assertTrue(lines[0].contains("A")) + assertTrue(lines[0].contains("BUY")) + } + + @Test + fun `multiple orders written on separate lines`() { + val order1 = MarketOrder( + "order-1", + "A", + LocalDateTime.of(2024, 1, 15, 10, 0), + LocalDateTime.of(2024, 1, 15, 10, 15), + OrderSide.BUY, + BigDecimal("10"), + BigDecimal("50.00") + ) + val order2 = MarketOrder( + "order-2", + "B", + LocalDateTime.of(2024, 1, 15, 10, 15), + LocalDateTime.of(2024, 1, 15, 10, 30), + OrderSide.SELL, + BigDecimal("5"), + BigDecimal("55.00") + ) + + client.sendOrder(order1) + client.sendOrder(order2) + + val lines = client.getAllOrders() + assertEquals(2, lines.size) + assertTrue(lines[0].contains("order-1")) + assertTrue(lines[0].contains("BUY")) + assertTrue(lines[1].contains("order-2")) + assertTrue(lines[1].contains("SELL")) + } + + @Test + fun `clear removes all orders`() { + val order = MarketOrder( + "order-1", + "A", + LocalDateTime.of(2024, 1, 15, 10, 0), + LocalDateTime.of(2024, 1, 15, 10, 15), + OrderSide.BUY, + BigDecimal("10"), + BigDecimal("50.00") + ) + client.sendOrder(order) + + client.clear() + + assertTrue(client.getAllOrders().isEmpty()) + } + + @Test + fun `tracks both buy and sell orders`() { + val buyOrder = MarketOrder( + "buy-1", + "A", + LocalDateTime.of(2024, 1, 15, 10, 0), + LocalDateTime.of(2024, 1, 15, 10, 15), + OrderSide.BUY, + BigDecimal("10"), + BigDecimal("50.00") + ) + val sellOrder = MarketOrder( + "sell-1", + "A", + LocalDateTime.of(2024, 1, 15, 10, 0), + LocalDateTime.of(2024, 1, 15, 10, 15), + OrderSide.SELL, + BigDecimal("10"), + BigDecimal("55.00") + ) + + client.sendOrder(buyOrder) + client.sendOrder(sellOrder) + + val lines = client.getAllOrders() + assertEquals(2, lines.size) + assertTrue(lines.any { it.contains("BUY") }) + assertTrue(lines.any { it.contains("SELL") }) + } +}