diff --git a/pom.xml b/pom.xml
index 1ced3501..84472601 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,6 +15,7 @@
~ limitations under the License.
-->
+
@@ -69,7 +70,7 @@
official
17
false
- 1.9.24
+ 1.9.25
1.9
1.9
2.0.13
@@ -467,6 +468,7 @@
${slf4j.version}
test
+
@@ -484,6 +486,13 @@
jackson-databind
2.15.2
+
+ roboquant
+ org.roboquant
+ ${project.version}
+ test-jar
+ test
+
diff --git a/roboquant-charts/pom.xml b/roboquant-charts/pom.xml
index 44c518f9..e8cca53a 100644
--- a/roboquant-charts/pom.xml
+++ b/roboquant-charts/pom.xml
@@ -60,6 +60,12 @@
+
+ roboquant
+ org.roboquant
+ test-jar
+ test
+
diff --git a/roboquant-ibkr/src/main/kotlin/org/roboquant/ibkr/IBKRBroker.kt b/roboquant-ibkr/src/main/kotlin/org/roboquant/ibkr/IBKRBroker.kt
index af796232..4b3a15f0 100644
--- a/roboquant-ibkr/src/main/kotlin/org/roboquant/ibkr/IBKRBroker.kt
+++ b/roboquant-ibkr/src/main/kotlin/org/roboquant/ibkr/IBKRBroker.kt
@@ -230,11 +230,6 @@ class IBKRBroker(
*/
private inner class Wrapper(logger: Logging.Logger) : BaseWrapper(logger) {
- val accountTags = mutableMapOf(
- AccountSummaryTag.TotalCashValue to Amount(Currency.USD, 0),
- AccountSummaryTag.BuyingPower to Amount(Currency.USD, 0),
- )
-
/**
* Convert an IBOrder to a roboquant Instruction.
* This is only used during initial connection when retrieving any open orders linked to the account.
diff --git a/roboquant-questdb/pom.xml b/roboquant-questdb/pom.xml
index d438ca8b..12e68651 100644
--- a/roboquant-questdb/pom.xml
+++ b/roboquant-questdb/pom.xml
@@ -45,6 +45,12 @@
roboquant
org.roboquant
+
+ roboquant
+ org.roboquant
+ test-jar
+ test
+
org.questdb
@@ -52,14 +58,6 @@
8.1.0
-
- org.roboquant
- roboquant
- 3.0.0-SNAPSHOT
- test-jar
- test
-
-
diff --git a/roboquant-ta/pom.xml b/roboquant-ta/pom.xml
index e049c7c8..cfb49675 100644
--- a/roboquant-ta/pom.xml
+++ b/roboquant-ta/pom.xml
@@ -54,6 +54,12 @@
ta-lib
0.4.0
+
+ roboquant
+ org.roboquant
+ test-jar
+ test
+
diff --git a/roboquant/src/main/kotlin/org/roboquant/common/Asset.kt b/roboquant/src/main/kotlin/org/roboquant/common/Asset.kt
index 3bc0bc18..6bb28262 100644
--- a/roboquant/src/main/kotlin/org/roboquant/common/Asset.kt
+++ b/roboquant/src/main/kotlin/org/roboquant/common/Asset.kt
@@ -148,28 +148,6 @@ data class Forex(override val symbol: String, override val currency: Currency) :
*/
fun Collection.getBySymbol(symbol: String): Asset = first { it.symbol == symbol }
-/**
- * Find an asset based on its [symbols] name. Will return an empty list if no assets are matched.
- */
-fun Collection.findBySymbols(vararg symbols: String): List = findBySymbols(symbols.asList())
-
-/**
- * Find an asset based on its [symbols] name. Will return an empty list if no assets are matched.
- */
-fun Collection.findBySymbols(symbols: Collection): List = filter { it.symbol in symbols }
-
-/**
- * Find all assets based on their [currencyCodes]. Returns an empty list if no matching assets can be found.
- */
-fun Collection.findByCurrencies(vararg currencyCodes: String): List =
- findByCurrencies(currencyCodes.asList())
-
-/**
- * Find all assets based on their [currencyCodes]. Returns an empty list if no matching assets can be found.
- */
-fun Collection.findByCurrencies(currencyCodes: Collection): List =
- filter { it.currency.currencyCode in currencyCodes }
-
/**
* Get all unique symbols from the assets
*/
diff --git a/roboquant/src/main/kotlin/org/roboquant/common/TimeSeries.kt b/roboquant/src/main/kotlin/org/roboquant/common/TimeSeries.kt
index d4549e60..3c99f6ce 100644
--- a/roboquant/src/main/kotlin/org/roboquant/common/TimeSeries.kt
+++ b/roboquant/src/main/kotlin/org/roboquant/common/TimeSeries.kt
@@ -235,28 +235,6 @@ class TimeSeries(val timeline: Timeline, val values: DoubleArray) : Iterable.flatten(noOverlap: Boolean = true): TimeSeries {
- // Optimized path for a map with only one entry
- if (size == 1) return values.first()
- val sortedTimeSeries = values.sortedBy { it.timeline.first() }
- val result = mutableListOf()
- var last = Instant.MIN
- for (timeSeries in sortedTimeSeries) {
- for (entry in timeSeries) {
- if (noOverlap && entry.time <= last) continue
- result.add(entry)
- last = entry.time
- }
-
- }
- return TimeSeries(result)
-}
-
/**
* Normalize all the timeseries in his map
*/
diff --git a/roboquant/src/main/kotlin/org/roboquant/common/extensions.kt b/roboquant/src/main/kotlin/org/roboquant/common/extensions.kt
index a63c6206..dff38773 100644
--- a/roboquant/src/main/kotlin/org/roboquant/common/extensions.kt
+++ b/roboquant/src/main/kotlin/org/roboquant/common/extensions.kt
@@ -17,7 +17,6 @@
package org.roboquant.common
-import kotlinx.coroutines.delay
import org.hipparchus.stat.descriptive.moment.StandardDeviation
import org.roboquant.common.Config.EPS
import java.lang.Integer.max
@@ -28,8 +27,6 @@ import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
import java.time.ZoneOffset
-import java.time.ZonedDateTime
-import java.time.temporal.ChronoUnit
import kotlin.math.absoluteValue
import kotlin.math.ln
@@ -50,20 +47,6 @@ operator fun Instant.compareTo(timeframe: Timeframe): Int {
}
}
-/**
- * Delay until this time is reached
- */
-suspend fun Instant.delayUntil() {
- val now = Instant.now()
- delay(now.until(this, ChronoUnit.MILLIS).coerceAtLeast(0L))
-}
-
-/**
- * Get the instant as ZonedDateTime UTC
- */
-fun Instant.toUTC(): ZonedDateTime = atZone(ZoneOffset.UTC)
-
-
fun Instant.sameDay(other: Instant, zoneId: ZoneId = ZoneOffset.UTC): Boolean {
val dt1 = LocalDate.ofInstant(this, zoneId)
val dt2 = LocalDate.ofInstant(other, zoneId)
diff --git a/roboquant/src/main/kotlin/org/roboquant/feeds/AggregatorLiveFeed.kt b/roboquant/src/main/kotlin/org/roboquant/feeds/AggregatorLiveFeed.kt
index 037cf24f..2a5738dc 100644
--- a/roboquant/src/main/kotlin/org/roboquant/feeds/AggregatorLiveFeed.kt
+++ b/roboquant/src/main/kotlin/org/roboquant/feeds/AggregatorLiveFeed.kt
@@ -45,6 +45,7 @@ import kotlin.reflect.KClass
* @property remaining should any remaining actions be sent, default is true
*
*/
+@Suppress("unused")
class AggregatorLiveFeed(
private val feed: LiveFeed,
private val aggregationPeriod: TimeSpan,
diff --git a/roboquant/src/main/kotlin/org/roboquant/feeds/csv/CSVConfig.kt b/roboquant/src/main/kotlin/org/roboquant/feeds/csv/CSVConfig.kt
index 812ee162..61605609 100644
--- a/roboquant/src/main/kotlin/org/roboquant/feeds/csv/CSVConfig.kt
+++ b/roboquant/src/main/kotlin/org/roboquant/feeds/csv/CSVConfig.kt
@@ -54,7 +54,6 @@ data class CSVConfig(
var assetBuilder: AssetBuilder = StockBuilder()
) {
-
private val pattern by lazy { Pattern.compile(filePattern) }
private var isInitialized = false
@@ -75,11 +74,15 @@ data class CSVConfig(
return CSVConfig(
filePattern = ".*.txt",
timeParser = AutoDetectTimeParser(2),
- assetBuilder = { name: String -> Stock(name.removeSuffix(".us.txt").replace('-', '.').uppercase(), Currency.USD) }
+ assetBuilder = { name: String ->
+ Stock(
+ name.removeSuffix(".us.txt").replace('-', '.').uppercase(),
+ Currency.USD
+ )
+ }
)
}
-
/**
* Returns a CSVConfig suited for parsing MT5 CSV files
*
@@ -210,8 +213,6 @@ data class CSVConfig(
return file.isFile && pattern.matcher(name).matches() && name !in fileSkip
}
-
-
/**
* Merge a config map into this CSV config
*
diff --git a/roboquant/src/main/kotlin/org/roboquant/traders/FlexTrader.kt b/roboquant/src/main/kotlin/org/roboquant/traders/FlexTrader.kt
index 70e4618d..d0d2da36 100644
--- a/roboquant/src/main/kotlin/org/roboquant/traders/FlexTrader.kt
+++ b/roboquant/src/main/kotlin/org/roboquant/traders/FlexTrader.kt
@@ -18,7 +18,6 @@ package org.roboquant.traders
import org.roboquant.brokers.Account
import org.roboquant.brokers.Position
-import org.roboquant.brokers.exposure
import org.roboquant.common.*
import org.roboquant.feeds.Event
import org.roboquant.feeds.PriceItem
@@ -113,22 +112,6 @@ open class FlexTrader(
return SingleTrader()
}
- /**
- * Capital-based flex trader.
- */
- fun capitalBased(
- configure: FlexPolicyConfig.() -> Unit = {}
- ): FlexTrader {
- class CapiltalBasedTrader : FlexTrader(configure) {
- override fun amountPerOrder(account: Account): Amount {
- val capital = account.positions.values.exposure + account.buyingPower
- val amount = account.convert(capital)
- return amount * config.orderPercentage
- }
- }
- return CapiltalBasedTrader()
- }
-
/**
* Return a FlexTrader that generates bracket orders, with the following characteristics:
* - a market order for entry
diff --git a/roboquant/src/main/kotlin/org/roboquant/traders/SignalResolution.kt b/roboquant/src/main/kotlin/org/roboquant/traders/SignalResolution.kt
index ada082ff..ebb6daab 100644
--- a/roboquant/src/main/kotlin/org/roboquant/traders/SignalResolution.kt
+++ b/roboquant/src/main/kotlin/org/roboquant/traders/SignalResolution.kt
@@ -43,25 +43,3 @@ private class SignalShuffleTrader(private val trader: Trader, private val random
*/
fun Trader.shuffleSignals(random: Random = Config.random): Trader = SignalShuffleTrader(this, random)
-/**
- * Shuffle signals before processing them in the trader, avoiding favoring assets that appear always first in the
- * actions of an event.
- *
- * @property trader
- * @constructor Create empty Signal resolver
- */
-private class SkipSymbolsTrader(private val trader: Trader, private val symbols: List) : Trader by trader {
-
- override fun create(signals: List, account: Account, event: Event): List {
- val newSignals = signals.filter { it.asset.symbol !in symbols }
- return trader.create(newSignals, account, event)
- }
-}
-
-/**
- * Skip [symbols] that should not be converted into orders by removing them from the signals
- */
-fun Trader.skipSymbols(vararg symbols: String): Trader = SkipSymbolsTrader(this, symbols.asList())
-
-
-
diff --git a/roboquant/src/test/kotlin/org/roboquant/feeds/PriceItemTest.kt b/roboquant/src/test/kotlin/org/roboquant/feeds/PriceItemTest.kt
index 987c4e0b..eb197fac 100644
--- a/roboquant/src/test/kotlin/org/roboquant/feeds/PriceItemTest.kt
+++ b/roboquant/src/test/kotlin/org/roboquant/feeds/PriceItemTest.kt
@@ -17,6 +17,7 @@
package org.roboquant.feeds
import org.roboquant.TestData
+import org.roboquant.common.Amount
import org.roboquant.common.Stock
import kotlin.test.Test
import kotlin.test.assertContains
@@ -36,6 +37,9 @@ internal class PriceItemTest {
var price = p.getPrice()
assertEquals(9.5, price)
+ val amt = p.getPriceAmount()
+ assertEquals(Amount("USD", 9.5), amt)
+
price = p.getPrice("WEIGHTED")
assertEquals(9.25, price)
diff --git a/roboquant/src/test/kotlin/org/roboquant/traders/CircuitBreakerTest.kt b/roboquant/src/test/kotlin/org/roboquant/traders/CircuitBreakerTest.kt
new file mode 100644
index 00000000..e6c289b2
--- /dev/null
+++ b/roboquant/src/test/kotlin/org/roboquant/traders/CircuitBreakerTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2020-2024 Neural Layer
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.roboquant.traders
+
+import org.roboquant.TestData
+import org.roboquant.brokers.Account
+import org.roboquant.common.*
+import org.roboquant.feeds.Event
+import org.roboquant.orders.MarketOrder
+import org.roboquant.orders.Instruction
+import org.roboquant.strategies.Signal
+import java.time.Instant
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+internal class CircuitBreakerTest {
+
+ private class MyTrader : Trader {
+ override fun create(signals: List, account: Account, event: Event): List {
+ return listOf(
+ MarketOrder(Stock("A"), 10),
+ MarketOrder(Stock("B"), 10),
+ MarketOrder(Stock("C"), 10)
+ )
+ }
+
+ }
+
+ @Test
+ fun test() {
+ val account = TestData.usAccount()
+ val time = Instant.now()
+ val policy = CircuitBreaker(MyTrader(), 8, 1.hours)
+ var orders = policy.create(emptyList(), account, Event.empty(time))
+ assertEquals(3, orders.size)
+
+ orders = policy.create(emptyList(), account, Event.empty(time + 30.minutes))
+ assertEquals(3, orders.size)
+
+ orders = policy.create(emptyList(), account, Event.empty(time + 50.minutes))
+ assertEquals(0, orders.size)
+
+ orders = policy.create(emptyList(), account, Event.empty(time + 51.minutes))
+ assertEquals(0, orders.size)
+
+ orders = policy.create(emptyList(), account, Event.empty(time + 120.minutes))
+ assertEquals(3, orders.size)
+
+ }
+}
diff --git a/roboquant/src/test/kotlin/org/roboquant/traders/FlexTraderTest.kt b/roboquant/src/test/kotlin/org/roboquant/traders/FlexTraderTest.kt
new file mode 100644
index 00000000..9ccbdc81
--- /dev/null
+++ b/roboquant/src/test/kotlin/org/roboquant/traders/FlexTraderTest.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2020-2024 Neural Layer
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.roboquant.traders
+
+import org.roboquant.TestData
+import org.roboquant.brokers.sim.execution.InternalAccount
+import org.roboquant.common.*
+import org.roboquant.feeds.Event
+import org.roboquant.feeds.PriceItem
+import org.roboquant.feeds.TradePrice
+import org.roboquant.orders.*
+import org.roboquant.strategies.Signal
+import java.time.Instant
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+internal class FlexTraderTest {
+
+ @Test
+ fun order() {
+ val policy = FlexTrader()
+ val signals = mutableListOf()
+ val event = Event(Instant.now(), emptyList())
+ val account = InternalAccount(Currency.USD).toAccount()
+ val orders = policy.create(signals, account, event)
+ assertTrue(orders.isEmpty())
+ }
+
+ @Test
+ fun order3() {
+ val policy = FlexTrader()
+ val orders = run(policy)
+ assertTrue(orders.isNotEmpty())
+
+ val order = orders.first()
+ assertTrue(order is MarketOrder)
+ assertEquals("TEST123", order.asset.symbol)
+ assertEquals(Size(204), order.size)
+ }
+
+ @Test
+ fun orderMinPrice() {
+ val policy = FlexTrader {
+ minPrice = 10.USD
+ }
+ val asset = Stock("TEST123")
+ val signals = listOf(Signal.buy(asset))
+
+ val event1 = Event(Instant.now(), listOf(TradePrice(asset, 5.0)))
+ val account = TestData.usAccount()
+ val orders1 = policy.create(signals, account, event1)
+ assertTrue(orders1.isEmpty())
+
+ val event2 = Event(Instant.now(), listOf(TradePrice(asset, 15.0)))
+ val orders2 = policy.create(signals, account, event2)
+ assertTrue(orders2.isNotEmpty())
+ }
+
+ @Test
+ fun order2() {
+
+ class MyTrader(val percentage: Double = 0.05) : FlexTrader() {
+
+ override fun createOrder(signal: Signal, size: Size, priceItem: PriceItem): Instruction {
+ val asset = signal.asset
+ val direction = if (size.isPositive) 1.0 else -1.0
+ val percentage = percentage * direction
+ val price = priceItem.getPrice(config.priceType)
+
+ return BracketOrder(
+ MarketOrder(asset, size),
+ LimitOrder(asset, size, price * (1 + percentage)),
+ StopOrder(asset, size, price * (1 - percentage))
+ )
+ }
+ }
+
+ val policy = MyTrader()
+ val signals = mutableListOf()
+ val event = Event(Instant.now(), emptyList())
+ val account = InternalAccount(Currency.USD).toAccount()
+ val orders = policy.create(signals, account, event)
+ assertTrue(orders.isEmpty())
+
+ }
+
+
+ private fun run(policy: FlexTrader): List {
+ val asset = Stock("TEST123")
+ val signals = listOf(Signal.buy(asset))
+ val event = Event(Instant.now(), listOf(TradePrice(asset, 5.0)))
+ val account = TestData.usAccount()
+ return policy.create(signals, account, event)
+ }
+
+ @Test
+ fun predefined() {
+ val policy = FlexTrader.bracketOrders()
+ val orders = run(policy)
+ assertTrue(orders.isNotEmpty())
+
+ val first = orders.first()
+ assertTrue(first is BracketOrder)
+
+ val entry = first.entry
+ val stop = first.stopLoss
+ val profit = first.takeProfit
+
+ assertTrue(entry is MarketOrder)
+
+ assertTrue(stop is StopOrder)
+ assertEquals(5.0 * 0.99, stop.stop)
+
+ assertTrue(profit is TrailOrder)
+ assertEquals(0.05, profit.trailPercentage)
+ }
+
+ @Test
+ fun predefined2() {
+ val policy = FlexTrader.limitOrders()
+ val orders = run(policy)
+ assertTrue(orders.isNotEmpty())
+
+ val first = orders.first()
+ assertTrue(first is LimitOrder)
+ assertEquals(5 * 0.99, first.limit)
+ }
+
+ @Test
+ fun predefined3() {
+ val policy = FlexTrader.singleAsset()
+ val orders = run(policy)
+ assertTrue(orders.isNotEmpty())
+
+ val first = orders.first()
+ assertTrue(first is MarketOrder)
+ }
+
+ @Test
+ fun chaining() {
+ val policy = FlexTrader()
+ .circuitBreaker(10, 1.days)
+ val signals = mutableListOf()
+ val event = Event(Instant.now(), emptyList())
+ val account = InternalAccount(Currency.USD).toAccount()
+ val orders = policy.create(signals, account, event)
+ assertTrue(orders.isEmpty())
+ }
+
+
+}
diff --git a/roboquant/src/test/kotlin/org/roboquant/traders/SignalResolutionTest.kt b/roboquant/src/test/kotlin/org/roboquant/traders/SignalResolutionTest.kt
new file mode 100644
index 00000000..0db2fbfe
--- /dev/null
+++ b/roboquant/src/test/kotlin/org/roboquant/traders/SignalResolutionTest.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020-2024 Neural Layer
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.roboquant.traders
+
+import org.roboquant.TestData
+import org.roboquant.common.Stock
+import org.roboquant.feeds.Event
+import org.roboquant.feeds.TradePrice
+import org.roboquant.strategies.Signal
+import java.time.Instant
+import kotlin.random.Random
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+internal class SignalResolutionTest {
+
+ @Test
+ fun testSignalShuffle() {
+ val policy = FlexTrader().shuffleSignals(Random(42))
+ val account = TestData.usAccount()
+ val assets = listOf(Stock("A"), Stock("B"), Stock("C"), Stock("D"))
+ val signals = assets.map { Signal(it, 1.0) }
+ val items = assets.map { TradePrice(it, 100.0, 10.0) }
+ val orders = policy.create(signals, account, Event(Instant.now(), items))
+ assertEquals(signals.size, orders.size)
+ }
+
+}