diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..4ba82926 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.avro binary diff --git a/.gitignore b/.gitignore index f3e08a1e..04481a3b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ build/ .ipynb_checkpoints *.iml .DS_Store -*.avro target/ *.jfr *.log diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index da22a495..0891ea38 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -2,16 +2,17 @@ = Changelog == Major changes in 3.0 -Roboquant version 3.0 has several changes, several of them are breaking changes. One key reason was to clean-up and simplify areas that had grown in complexity in the past few years and were a hird to maintain and extend. +Roboquant version 3.0 has several changes, several of them are breaking changes. One key reason was to clean-up and simplify areas that had grown in complexity in the past few years and were a hard to maintain and extend. -- Signals are simplified and now use a double value for the rating rather than an enum. This is better suited for more advanced ML type of algos. +A non-extensive list of the major changes: -- SimBroker order execution simulation simplified. +- Signals are simplified and now use a double value for the rating rather than an enum. This is better suited for more advanced ML type of algos. - Removed the `Lifecycle` interface for all components (Stratyegy, Policy, Broker). -- Order handling is simplified. For example order-ids are handout by the broker (during the place order method), so not assigned when creating the order. This is more inline how -most brokers work. +- Order handling is simplified. For example, order-ids are handout by the broker (during the place order method). So they are no longer assigned when creating the order. This is more inline how most brokers work. + +- SimBroker order execution is simplified. Every order type has its own executor that is responsible for the whole execution - Improved the `Broker` interface. Only the `broker.sync()` call now provides access to the `account` object. @@ -26,13 +27,15 @@ fun track(event: Event, account: Account, signals: List, orders: Listkotlin-maven-plugin + + + ${basedir}/src/main/resources + false + + diff --git a/roboquant-avro/src/main/kotlin/org/roboquant/avro/AvroFeed.kt b/roboquant-avro/src/main/kotlin/org/roboquant/avro/AvroFeed.kt index c3c8deaa..6faa9cb0 100644 --- a/roboquant-avro/src/main/kotlin/org/roboquant/avro/AvroFeed.kt +++ b/roboquant-avro/src/main/kotlin/org/roboquant/avro/AvroFeed.kt @@ -30,13 +30,14 @@ import org.apache.avro.generic.GenericDatumWriter import org.apache.avro.generic.GenericRecord import org.apache.avro.io.DatumWriter import org.apache.avro.util.Utf8 -import org.roboquant.common.Asset -import org.roboquant.common.Logging -import org.roboquant.common.Timeframe -import org.roboquant.common.compareTo +import org.roboquant.common.* import org.roboquant.feeds.* +import java.io.File +import java.io.InputStream import java.nio.file.Files import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.StandardCopyOption import java.time.Instant import java.util.* @@ -60,17 +61,22 @@ internal const val SCHEMA = """{ * * The internal resolution is nanoseconds and stored as a single Long value * - * @property path the path where the Avro file can be found + * @property file the Avro file * @property template template to use to convert the stored symbols into assets * * @constructor Create new Avro Feed */ -class AvroFeed(private val path: Path, private val template: Asset = Asset("TEMPLATE")) : Feed { +class AvroFeed(private val file: File, private val template: Asset = Asset("TEMPLATE")) : Feed { /** * Instantiate an Avro Feed based on the Avro file at [path] */ - constructor(path: String) : this(Path.of(path)) + constructor(path: String) : this(File(path)) + + /** + * Instantiate an Avro Feed based on the Avro file at [path] + */ + constructor(path: Path) : this(path.toFile()) private val logger = Logging.getLogger(AvroFeed::class) @@ -80,14 +86,14 @@ class AvroFeed(private val path: Path, private val template: Asset = Asset("TEMP init { - logger.info { "New AvroFeed path=$path exist=${exists()}" } + logger.info { "New AvroFeed file=$file exist=${exists()}" } } - fun exists(): Boolean = Files.exists(path) + fun exists(): Boolean = file.exists() private fun getReader(): DataFileReader { - return DataFileReader(path.toFile(), GenericDatumReader()) + return DataFileReader(file, GenericDatumReader()) } /** @@ -104,7 +110,7 @@ class AvroFeed(private val path: Path, private val template: Asset = Asset("TEMP val cache = mutableMapOf() getReader().use { val name = it.schema.fullName - assert(name == "org.roboquant.avro.schema.PriceItemV2") { "invalid avro schema $name"} + assert(name == "org.roboquant.avro.schema.PriceItemV2") { "invalid avro schema $name" } if (timeframe.isFinite()) position(it, timeframe.start) while (it.hasNext()) { val rec = it.next() @@ -141,7 +147,7 @@ class AvroFeed(private val path: Path, private val template: Asset = Asset("TEMP if (key != null) r.seek(index.getValue(key)) } - private fun createIndex() : TreeMap { + private fun createIndex(): TreeMap { val index = TreeMap() getReader().use { while (it.hasNext()) { @@ -149,7 +155,7 @@ class AvroFeed(private val path: Path, private val template: Asset = Asset("TEMP val t = it.next().get(0) as Utf8 it.seek(position) if (it.hasNext()) { - index.putIfAbsent(Instant.parse(t),position) + index.putIfAbsent(Instant.parse(t), position) it.nextBlock() } } @@ -158,7 +164,7 @@ class AvroFeed(private val path: Path, private val template: Asset = Asset("TEMP return index } - private fun calcTimeframe() : Timeframe { + private fun calcTimeframe(): Timeframe { if (index.isEmpty()) return Timeframe.EMPTY val start = index.firstKey() getReader().use { @@ -187,17 +193,17 @@ class AvroFeed(private val path: Path, private val template: Asset = Asset("TEMP compress: Boolean = true, timeframe: Timeframe = Timeframe.INFINITE, append: Boolean = false, - syncInterval: Int = DataFileConstants.DEFAULT_SYNC_INTERVAL + syncInterval: Int = DataFileConstants.DEFAULT_SYNC_INTERVAL, + assetFilter: AssetFilter = AssetFilter.all() ) = runBlocking { val channel = EventChannel(timeframe = timeframe) val schema = Schema.Parser().parse(SCHEMA) val datumWriter: DatumWriter = GenericDatumWriter(schema) val dataFileWriter = DataFileWriter(datumWriter) - val file = path.toFile() if (append) { - require(exists()) {"File $file doesn't exist yet, cannot append"} + require(exists()) { "File $file doesn't exist yet, cannot append" } dataFileWriter.appendTo(file) } else { if (exists()) logger.info { "Overwriting existing Avro file $file" } @@ -222,6 +228,7 @@ class AvroFeed(private val path: Path, private val template: Asset = Asset("TEMP val event = channel.receive() for (action in event.items.filterIsInstance()) { + if (!assetFilter.filter(action.asset, event.time)) continue val asset = action.asset record.put(0, event.time.toString()) @@ -253,6 +260,41 @@ class AvroFeed(private val path: Path, private val template: Asset = Asset("TEMP } } + /** + * Standard set of Avro feeds that come with roboquant and will be downloaded the first time when invoked. They are + * stored at /.roboquant and reused from there later on. + */ + companion object { + + /** + * Get an AvroFeed containing end-of-day [PriceBar] data for the 25 largest US stocks. This feed + * contains a few years of public data. + * + * This is sample data and should NOT be relies on for real back testing. + */ + fun sp25(): AvroFeed { + val path = copy("/sp25.avro") + return AvroFeed(path) + } + + /** + * Copy file from jar to local filesystem + */ + private fun copy(fileName: String): Path { + val path = Paths.get(Config.home.toString(), fileName) + if (Files.notExists(path)) { + val stream = AvroFeed::class.java.getResourceAsStream(fileName) + stream.use { inputStream: InputStream? -> + Files.copy( + inputStream!!, path, StandardCopyOption.REPLACE_EXISTING + ) + } + require(Files.exists(path)) + } + return path + } + + } } diff --git a/roboquant-avro/src/main/resources/sp25.avro b/roboquant-avro/src/main/resources/sp25.avro new file mode 100644 index 00000000..12c2cf06 Binary files /dev/null and b/roboquant-avro/src/main/resources/sp25.avro differ diff --git a/roboquant-avro/src/test/kotlin/org/roboquant/avro/AvroFeedTest.kt b/roboquant-avro/src/test/kotlin/org/roboquant/avro/AvroFeedTest.kt index 3b0e7765..22ff7351 100644 --- a/roboquant-avro/src/test/kotlin/org/roboquant/avro/AvroFeedTest.kt +++ b/roboquant-avro/src/test/kotlin/org/roboquant/avro/AvroFeedTest.kt @@ -23,7 +23,7 @@ import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.io.TempDir import org.roboquant.common.* import org.roboquant.feeds.* -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.feeds.util.AssetSerializer.deserialize import org.roboquant.feeds.util.AssetSerializer.serialize import java.io.File @@ -112,18 +112,16 @@ internal class AvroFeedTest { fun append() { val now = Instant.now() val past = Timeframe(now - 2.years, now - 1.years) - val feed = RandomWalkFeed(past, 1.days) + val feed = RandomWalk(past, 1.days) val fileName = File(folder, "test2.avro").path val feed2 = AvroFeed(fileName) feed2.record(feed, compress = true) val past2 = Timeframe(now - 1.years, now) - val feed3 = RandomWalkFeed(past2, 1.days) + val feed3 = RandomWalk(past2, 1.days) feed2.record(feed3, append = true) } - - @Test fun assetSerialization() { val asset1 = Asset("XYZ") @@ -137,7 +135,15 @@ internal class AvroFeedTest { assertEquals("XYZ\u001FBOND\u001FEUR\u001FAEB\u001F123", str3) val asset4 = str3.deserialize() assertEquals(asset3, asset4) + } + @Test + fun sp25() { + assertDoesNotThrow { + val feed = AvroFeed.sp25() + assertTrue(feed.exists()) + assertEquals(Timeframe.parse("2020-01-02T21:00:00Z", "2023-12-29T21:00:00Z", inclusive = true), feed.timeframe) + } } } diff --git a/roboquant-avro/src/test/kotlin/org/roboquant/samples/AvroSamples.kt b/roboquant-avro/src/test/kotlin/org/roboquant/samples/AvroSamples.kt index b46c4b1f..d61cdcbe 100644 --- a/roboquant-avro/src/test/kotlin/org/roboquant/samples/AvroSamples.kt +++ b/roboquant-avro/src/test/kotlin/org/roboquant/samples/AvroSamples.kt @@ -16,74 +16,30 @@ package org.roboquant.samples -import kotlinx.coroutines.channels.ClosedReceiveChannelException -import kotlinx.coroutines.runBlocking import org.roboquant.Roboquant import org.roboquant.avro.AvroFeed -import org.roboquant.brokers.Account import org.roboquant.brokers.FixedExchangeRates import org.roboquant.brokers.sim.MarginAccount import org.roboquant.brokers.sim.SimBroker import org.roboquant.common.* -import org.roboquant.feeds.Event -import org.roboquant.feeds.EventChannel -import org.roboquant.feeds.PriceItemType +import org.roboquant.feeds.csv.CSVConfig import org.roboquant.feeds.csv.CSVFeed import org.roboquant.feeds.csv.PriceBarParser import org.roboquant.feeds.csv.TimeParser -import org.roboquant.feeds.random.RandomWalkFeed -import org.roboquant.orders.Instruction import org.roboquant.policies.FlexPolicy -import org.roboquant.policies.Policy -import org.roboquant.policies.SignalResolution -import org.roboquant.policies.resolve -import org.roboquant.run -import org.roboquant.strategies.CombinedStrategy -import org.roboquant.strategies.EMAStrategy -import org.roboquant.strategies.Signal +import org.roboquant.strategies.EMACrossover import java.time.Instant import java.time.LocalDateTime import java.time.format.DateTimeFormatter +import kotlin.io.path.Path +import kotlin.io.path.div import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertTrue internal class AvroSamples { - @Test - @Ignore - internal fun quotes_hft() = runBlocking { - val tf = Timeframe.parse("2021-01-01T16:30:00Z", "2021-01-01T23:00:00Z", true) - val f = RandomWalkFeed(tf, 10.millis, nAssets = 1, priceType = PriceItemType.QUOTE) - val feed = AvroFeed("/tmp/test.avro") - feed.record(f, compress = false) - assertTrue(feed.exists()) - assertEquals(tf, feed.timeframe) - - feed.timeframe.sample(1.minutes, 5).forEach { - val account = run(feed, EMAStrategy(), timeframe = it) - println("$it => ${account.equityAmount}") - } - val channel = EventChannel() - feed.playBackground(channel) - var cnt = 0 - val start = Instant.now().toEpochMilli() - try { - while (true) { - val evt = channel.receive() - cnt += evt.items.size - } - } catch (_: ClosedReceiveChannelException) { - // On purpose left empty, expected exception - - } finally { - println(cnt) - println(Instant.now().toEpochMilli() - start) - } - - } @Test @Ignore @@ -105,7 +61,7 @@ internal class AvroSamples { val cash = Wallet(100_000.USD) val broker = SimBroker(cash) - val strategy = EMAStrategy.PERIODS_12_26 + val strategy = EMACrossover.PERIODS_12_26 val policy = FlexPolicy { orderPercentage = 0.02 } @@ -118,7 +74,7 @@ internal class AvroSamples { @Test @Ignore internal fun testingStrategies() { - val strategy = EMAStrategy() + val strategy = EMACrossover() val roboquant = Roboquant(strategy) val feed = CSVFeed("data/US") @@ -139,28 +95,32 @@ internal class AvroSamples { } + @Test @Ignore - internal fun signalsOnly() { - class MyPolicy : Policy { - - override fun act(signals: List, account: Account, event: Event): List { - return emptyList() - } - - } - - val feed = AvroFeed("/tmp/us_full_v3.0.avro") - - val strategy = CombinedStrategy( - EMAStrategy.PERIODS_50_200, - EMAStrategy.PERIODS_12_26 + internal fun generate() { + val path = Path("/tmp/us") + val path1 = path / "nasdaq stocks" + val path2 = path / "nyse stocks" + + val feed = CSVFeed(path1.toString(), CSVConfig.stooq()) + val tmp = CSVFeed(path2.toString(), CSVConfig.stooq()) + feed.merge(tmp) + + val avroFeed = AvroFeed("/tmp/sp25.avro") + + val s = "MSFT,NVDA,AAPL,AMZN,META,GOOGL,GOOG,BRK.B,LLY,AVGO,JPM,XOM,TSLA,UNH,V,PG,MA,COST,JNJ,HD,MRK,NFLX,WMT,ABBV,CVX" + val symbols = s.split(',').toTypedArray() + assertEquals(25, symbols.size) + + val timeframe = Timeframe.fromYears(2020, 2024) + avroFeed.record( + feed, + true, + timeframe, + assetFilter = AssetFilter.includeSymbols(*symbols) ) - val policy = MyPolicy().resolve(SignalResolution.NO_CONFLICTS) - - val roboquant = Roboquant(strategy, policy = policy) - roboquant.run(feed, timeframe = Timeframe.past(5.years)) } @Test @@ -182,7 +142,7 @@ internal class AvroSamples { require(feed.assets.size == 1) - val strategy = EMAStrategy() + val strategy = EMACrossover() // We don't want the default initial deposit in USD, since then we need a currency convertor val initialDeposit = 10_000.EUR.toWallet() diff --git a/roboquant-charts/src/main/kotlin/org/roboquant/charts/AllocationChart.kt b/roboquant-charts/src/main/kotlin/org/roboquant/charts/AllocationChart.kt index c1198daf..83099a1e 100644 --- a/roboquant-charts/src/main/kotlin/org/roboquant/charts/AllocationChart.kt +++ b/roboquant-charts/src/main/kotlin/org/roboquant/charts/AllocationChart.kt @@ -22,23 +22,23 @@ import org.icepear.echarts.Sunburst import org.icepear.echarts.charts.pie.PieSeries import org.icepear.echarts.charts.sunburst.SunburstSeries import org.icepear.echarts.components.tooltip.Tooltip -import org.roboquant.brokers.Position +import org.roboquant.brokers.Account import org.roboquant.common.Currency + import java.math.BigDecimal /** - * Plot the allocation of assets in the provided [positions]. + * Plot the allocation of assets in the provided account. * - * @property positions the positions to use + * @property account the account to use * @property includeAssetClass group per assetClass, default is false - * @property currency the currency to use, default is null. The chart will then use the first currency it encounters * in the positions. * @constructor Create a new asset allocation chart */ class AllocationChart( - private val positions: Collection, + private val account: Account, private val includeAssetClass: Boolean = false, - private val currency: Currency? = null + private val currency: Currency = account.baseCurrency ) : Chart() { private class Entry(val name: String, val value: BigDecimal, val type: String) { @@ -46,13 +46,13 @@ class AllocationChart( } private fun toSeriesData(): List { + val positions = account.positions.values if (positions.isEmpty()) return emptyList() - val curr = currency ?: positions.first().asset.currency val result = mutableListOf() for (position in positions.sortedBy { it.asset.symbol }) { val asset = position.asset - val localAmount = position.exposure.convert(curr).toBigDecimal() + val localAmount = position.exposure.convert(currency).toBigDecimal() result.add(Entry(asset.symbol, localAmount, asset.type.name)) } return result diff --git a/roboquant-charts/src/main/kotlin/org/roboquant/charts/OrderChart.kt b/roboquant-charts/src/main/kotlin/org/roboquant/charts/OrderChart.kt index fc9b1342..d3d8ed35 100644 --- a/roboquant-charts/src/main/kotlin/org/roboquant/charts/OrderChart.kt +++ b/roboquant-charts/src/main/kotlin/org/roboquant/charts/OrderChart.kt @@ -37,7 +37,7 @@ import java.time.Instant * provide more insights, since these also cover more advanced order types. You can use the [TradeChart] for that. */ class OrderChart( - private val orderStates: List, + private val orders: List, ) : Chart() { private fun getTooltip(order: SingleOrder, openedAt: Instant): String { @@ -55,9 +55,9 @@ class OrderChart( } private fun ordersToSeriesData(): List> { - val states = orderStates.filter { it.status != OrderStatus.INITIAL } + val activedOrders = orders.filter { it.status != OrderStatus.INITIAL } val d = mutableListOf>() - for (order in states.sortedBy { it.openedAt }) { + for (order in activedOrders.sortedBy { it.openedAt }) { if (order is SingleOrder) { val value = order.size.toBigDecimal() val tooltip = getTooltip(order, order.openedAt) diff --git a/roboquant-charts/src/test/kotlin/org/roboquant/charts/AllocationChartTest.kt b/roboquant-charts/src/test/kotlin/org/roboquant/charts/AllocationChartTest.kt index ebf5a326..62726c4b 100644 --- a/roboquant-charts/src/test/kotlin/org/roboquant/charts/AllocationChartTest.kt +++ b/roboquant-charts/src/test/kotlin/org/roboquant/charts/AllocationChartTest.kt @@ -26,21 +26,21 @@ internal class AllocationChartTest { @Test fun test() { val account = TestData.usAccount() - val chart = AllocationChart(account.positions.values) + val chart = AllocationChart(account) assertTrue(chart.renderJson().isNotBlank()) } @Test fun testPerAssetClass() { val account = TestData.usAccount() - val chart = AllocationChart(account.positions.values, includeAssetClass = true) + val chart = AllocationChart(account, includeAssetClass = true) assertTrue(chart.renderJson().isNotBlank()) } @Test fun option() { val account = TestData.usAccount() - val series = AllocationChart(account.positions.values).getOption().series + val series = AllocationChart(account).getOption().series assertTrue(series is Array<*> && series.isArrayOf()) assertTrue(series.first() is PieSeries) } diff --git a/roboquant-charts/src/test/kotlin/org/roboquant/charts/ChartTest.kt b/roboquant-charts/src/test/kotlin/org/roboquant/charts/ChartTest.kt index cf9d262b..420772e8 100644 --- a/roboquant-charts/src/test/kotlin/org/roboquant/charts/ChartTest.kt +++ b/roboquant-charts/src/test/kotlin/org/roboquant/charts/ChartTest.kt @@ -18,7 +18,7 @@ package org.roboquant.charts import org.icepear.echarts.Option import org.junit.jupiter.api.assertDoesNotThrow -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertEquals @@ -45,7 +45,7 @@ internal class ChartTest { @Test fun test() { - val f = RandomWalkFeed.lastYears(1, 1) + val f = RandomWalk.lastYears(1, 1) val asset = f.assets.first() val chart = PriceBarChart(f, asset) val html = chart.renderJson() diff --git a/roboquant-charts/src/test/kotlin/org/roboquant/charts/CorrelationChartTest.kt b/roboquant-charts/src/test/kotlin/org/roboquant/charts/CorrelationChartTest.kt index 8356ad7b..878ec54f 100644 --- a/roboquant-charts/src/test/kotlin/org/roboquant/charts/CorrelationChartTest.kt +++ b/roboquant-charts/src/test/kotlin/org/roboquant/charts/CorrelationChartTest.kt @@ -18,7 +18,7 @@ package org.roboquant.charts import org.icepear.echarts.charts.heatmap.HeatmapSeries import org.icepear.echarts.origin.util.SeriesOption -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import kotlin.test.Test import kotlin.test.assertTrue @@ -26,14 +26,14 @@ internal class CorrelationChartTest { @Test fun test() { - val feed = RandomWalkFeed.lastYears(1, 5) + val feed = RandomWalk.lastYears(1, 5) val chart = CorrelationChart(feed, feed.assets) assertTrue(chart.renderJson().isNotBlank()) } @Test fun option() { - val feed = RandomWalkFeed.lastYears(1, 5) + val feed = RandomWalk.lastYears(1, 5) val series = CorrelationChart(feed, feed.assets).getOption().series assertTrue(series is Array<*> && series.isArrayOf()) assertTrue(series.first() is HeatmapSeries) diff --git a/roboquant-charts/src/test/kotlin/org/roboquant/charts/PerformanceChartTest.kt b/roboquant-charts/src/test/kotlin/org/roboquant/charts/PerformanceChartTest.kt index 960b75a2..310c2ccd 100644 --- a/roboquant-charts/src/test/kotlin/org/roboquant/charts/PerformanceChartTest.kt +++ b/roboquant-charts/src/test/kotlin/org/roboquant/charts/PerformanceChartTest.kt @@ -16,7 +16,7 @@ package org.roboquant.charts -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import kotlin.test.Test import kotlin.test.assertTrue @@ -24,7 +24,7 @@ internal class PerformanceChartTest { @Test fun test() { - val feed = RandomWalkFeed.lastYears(1) + val feed = RandomWalk.lastYears(1) val chart = PerformanceChart(feed) assertTrue(chart.renderJson().isNotBlank()) } diff --git a/roboquant-charts/src/test/kotlin/org/roboquant/charts/PriceChartTest.kt b/roboquant-charts/src/test/kotlin/org/roboquant/charts/PriceChartTest.kt index 1a392c92..122c5af5 100644 --- a/roboquant-charts/src/test/kotlin/org/roboquant/charts/PriceChartTest.kt +++ b/roboquant-charts/src/test/kotlin/org/roboquant/charts/PriceChartTest.kt @@ -18,7 +18,7 @@ package org.roboquant.charts import org.roboquant.common.Timeframe import org.roboquant.feeds.Item -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.metrics.Indicator import java.time.Instant import kotlin.test.Test @@ -30,7 +30,7 @@ internal class PriceChartTest { @Test fun test() { - val feed = RandomWalkFeed.lastYears(1) + val feed = RandomWalk.lastYears(1) val asset = feed.assets.first() val chart = PriceChart(feed, asset) val html = chart.renderJson() @@ -52,7 +52,7 @@ internal class PriceChartTest { } val tf = Timeframe.fromYears(2020, 2021) - val feed = RandomWalkFeed(tf) + val feed = RandomWalk(tf) val asset = feed.assets.first() val ind = MyIndicator() diff --git a/roboquant-charts/src/test/kotlin/org/roboquant/charts/SignalChartTest.kt b/roboquant-charts/src/test/kotlin/org/roboquant/charts/SignalChartTest.kt index 89381ffd..0fd90e58 100644 --- a/roboquant-charts/src/test/kotlin/org/roboquant/charts/SignalChartTest.kt +++ b/roboquant-charts/src/test/kotlin/org/roboquant/charts/SignalChartTest.kt @@ -17,16 +17,16 @@ package org.roboquant.charts import org.junit.jupiter.api.assertDoesNotThrow -import org.roboquant.feeds.random.RandomWalkFeed -import org.roboquant.strategies.EMAStrategy +import org.roboquant.feeds.random.RandomWalk +import org.roboquant.strategies.EMACrossover import kotlin.test.Test internal class SignalChartTest { @Test fun test() { - val feed = RandomWalkFeed.lastYears(1) - val strat = EMAStrategy() + val feed = RandomWalk.lastYears(1) + val strat = EMACrossover() val chart = SignalChart(feed, strat) assertDoesNotThrow { chart.getOption() diff --git a/roboquant-charts/src/test/kotlin/org/roboquant/charts/TestData.kt b/roboquant-charts/src/test/kotlin/org/roboquant/charts/TestData.kt index aa39853b..c5653512 100644 --- a/roboquant-charts/src/test/kotlin/org/roboquant/charts/TestData.kt +++ b/roboquant-charts/src/test/kotlin/org/roboquant/charts/TestData.kt @@ -24,12 +24,12 @@ import org.roboquant.common.Amount import org.roboquant.common.Asset import org.roboquant.common.Size import org.roboquant.common.USD -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.feeds.util.HistoricTestFeed import org.roboquant.journals.MemoryJournal import org.roboquant.metrics.AccountMetric import org.roboquant.orders.MarketOrder -import org.roboquant.strategies.EMAStrategy +import org.roboquant.strategies.EMACrossover import org.roboquant.strategies.TestStrategy /** @@ -44,7 +44,7 @@ internal fun String.removeEOL() = this.replace("\n", "").replace("\r", "") object TestData { val fullAccount by lazy { - val feed = RandomWalkFeed.lastYears() + val feed = RandomWalk.lastYears() val rq = Roboquant(TestStrategy()) val account = rq.run(feed) account @@ -67,7 +67,7 @@ object TestData { val data by lazy { val feed = HistoricTestFeed(50..150) val journal = MemoryJournal(AccountMetric()) - org.roboquant.run(feed, EMAStrategy(), journal) + org.roboquant.run(feed, EMACrossover(), journal) journal.getMetric("account.equity") } diff --git a/roboquant-ibkr/src/test/kotlin/org/roboquant/samples/IBKRSamples.kt b/roboquant-ibkr/src/test/kotlin/org/roboquant/samples/IBKRSamples.kt index 88be742f..2ed56a43 100644 --- a/roboquant-ibkr/src/test/kotlin/org/roboquant/samples/IBKRSamples.kt +++ b/roboquant-ibkr/src/test/kotlin/org/roboquant/samples/IBKRSamples.kt @@ -29,7 +29,7 @@ import org.roboquant.orders.BracketOrder import org.roboquant.orders.MarketOrder import org.roboquant.policies.FlexPolicy import org.roboquant.policies.circuitBreaker -import org.roboquant.strategies.EMAStrategy +import org.roboquant.strategies.EMACrossover import kotlin.test.Test internal class IBKRSamples { @@ -120,7 +120,7 @@ internal class IBKRSamples { val feed = IBKRLiveFeed { client = 3 } feed.subscribe(tsla, msft, googl) - val strategy = EMAStrategy.PERIODS_12_26 + val strategy = EMACrossover.PERIODS_12_26 val policy = FlexPolicy().circuitBreaker(2, 5.minutes) val rq = Roboquant( strategy, diff --git a/roboquant-jupyter/src/test/kotlin/org/roboquant/jupyter/JupyterCoreTest.kt b/roboquant-jupyter/src/test/kotlin/org/roboquant/jupyter/JupyterCoreTest.kt index a944aee4..7f3603d2 100644 --- a/roboquant-jupyter/src/test/kotlin/org/roboquant/jupyter/JupyterCoreTest.kt +++ b/roboquant-jupyter/src/test/kotlin/org/roboquant/jupyter/JupyterCoreTest.kt @@ -20,7 +20,7 @@ import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult import org.junit.jupiter.api.assertDoesNotThrow import org.roboquant.charts.PriceChart import org.roboquant.common.RoboquantException -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -46,7 +46,7 @@ internal class JupyterCoreTest { @Test fun extensions() { - val feed = RandomWalkFeed.lastYears(1) + val feed = RandomWalk.lastYears(1) val chart = PriceChart(feed, feed.assets.first()) assertDoesNotThrow { chart.asHTML("dark") diff --git a/roboquant-perf/src/main/kotlin/org/roboquant/perf/Performance.kt b/roboquant-perf/src/main/kotlin/org/roboquant/perf/Performance.kt index 73eaf6b0..1d22537e 100644 --- a/roboquant-perf/src/main/kotlin/org/roboquant/perf/Performance.kt +++ b/roboquant-perf/src/main/kotlin/org/roboquant/perf/Performance.kt @@ -21,10 +21,10 @@ import org.roboquant.brokers.sim.MarginAccount import org.roboquant.brokers.sim.SimBroker import org.roboquant.common.* import org.roboquant.feeds.* -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.policies.FlexPolicy import org.roboquant.strategies.CombinedStrategy -import org.roboquant.strategies.EMAStrategy +import org.roboquant.strategies.EMACrossover import org.roboquant.strategies.Signal import org.roboquant.strategies.Strategy import java.time.Instant @@ -243,8 +243,8 @@ private object Memory { fun test() { Config.printInfo() - val rq = Roboquant(EMAStrategy()) - val feed = RandomWalkFeed.lastYears(5, nAssets = 500) + val rq = Roboquant(EMACrossover()) + val feed = RandomWalk.lastYears(5, nAssets = 500) rq.run(feed) exitProcess(0) } diff --git a/roboquant-questdb/src/main/kotlin/org/roboquant/questdb/QuestDBFeed.kt b/roboquant-questdb/src/main/kotlin/org/roboquant/questdb/QuestDBFeed.kt index 2e374f27..726f0d70 100644 --- a/roboquant-questdb/src/main/kotlin/org/roboquant/questdb/QuestDBFeed.kt +++ b/roboquant-questdb/src/main/kotlin/org/roboquant/questdb/QuestDBFeed.kt @@ -19,19 +19,24 @@ package org.roboquant.questdb import io.questdb.cairo.CairoEngine import io.questdb.cairo.DefaultCairoConfiguration +import io.questdb.cairo.security.AllowAllSecurityContext +import io.questdb.griffin.SqlExecutionContextImpl +import kotlinx.coroutines.channels.ClosedReceiveChannelException +import kotlinx.coroutines.runBlocking import org.roboquant.common.Asset import org.roboquant.common.Config import org.roboquant.common.Logging import org.roboquant.common.Timeframe -import org.roboquant.feeds.AssetFeed -import org.roboquant.feeds.Event -import org.roboquant.feeds.EventChannel -import org.roboquant.feeds.PriceItem +import org.roboquant.feeds.* import org.roboquant.feeds.util.AssetSerializer.deserialize +import org.roboquant.feeds.util.AssetSerializer.serialize +import org.roboquant.questdb.PriceActionHandler.Companion.getHandler +import java.nio.file.Files import java.nio.file.Path import java.util.* import kotlin.io.path.div import kotlin.io.path.isDirectory +import kotlin.reflect.KClass /** * @property tableName the name of table to use @@ -40,21 +45,11 @@ import kotlin.io.path.isDirectory class QuestDBFeed(private val tableName: String, dbPath: Path = Config.home / "questdb-prices" / "db") : AssetFeed { private var engine: CairoEngine - private val logger = Logging.getLogger(this::class) - init { - require(dbPath.isDirectory()) { "dbPath needs to be an existing questdb directory" } - - val configuration = DefaultCairoConfiguration(dbPath.toString()) - engine = CairoEngine(configuration) - - require(engine.tables().contains(tableName)) { "Table not found" } - // Add hook to close engine before JVM shutdown - Runtime.getRuntime().addShutdownHook(Thread { - logger.info { "Closing QuestDB engine" } - engine.close() - }) + init { + require(dbPath.isDirectory()) { "dbPath needs to be an directory" } + engine = getEngine(dbPath) } /** @@ -87,6 +82,72 @@ class QuestDBFeed(private val tableName: String, dbPath: Path = Config.home / "q tf } + + companion object Partition { + + private val logger = Logging.getLogger(this::class) + + /** + * Don't partition + */ + const val NONE = "NONE" + + /** + * Partition per year + */ + const val YEAR = "YEAR" + + /** + * Partition per day + */ + const val DAY = "DAY" + + /** + * Partition per month + */ + const val MONTH = "MONTH" + + /** + * Partition per hour + */ + const val HOUR = "HOUR" + + + private var engines = mutableMapOf() + + @Synchronized + fun getEngine(dbPath: Path): CairoEngine { + if (dbPath !in engines) { + if (Files.notExists(dbPath)) { + Files.createDirectories(dbPath) + } + require(dbPath.isDirectory()) { "dbPath needs to be a directory" } + val config = DefaultCairoConfiguration(dbPath.toString()) + val engine = CairoEngine(config) + engines[dbPath] = engine + + // Add hook to close engine before JVM shutdown + Runtime.getRuntime().addShutdownHook(Thread { + logger.info { "Closing QuestDB engine path=$dbPath" } + engine.close() + }) + } + return engines.getValue(dbPath) + } + + fun getRuns(dbPath: Path): Set { + val engine = getEngine(dbPath) + return engine.tables().toSet() + } + + @Synchronized + fun close(dbPath: Path) { + engines[dbPath]?.close() + } + + } + + /** * (Re)play the events of the feed using the provided [channel] */ @@ -133,4 +194,77 @@ class QuestDBFeed(private val tableName: String, dbPath: Path = Config.home / "q engine.close() } + fun record( + feed: Feed, + type: KClass<*>, + timeframe: Timeframe = Timeframe.INFINITE, + append: Boolean = false, + partition: String = NONE, + ) = runBlocking { + + require(partition in setOf("YEAR", "MONTH", "DAY", "HOUR", "NONE")) { "invalid partition value" } + val handler = getHandler(type) + val channel = EventChannel(timeframe = timeframe) + + // Create a new engine, so it can be released once the recording is done and release + // any locks it has on the database + if (!append) { + engine.update("""DROP TABLE IF EXISTS $tableName""") + handler.createTable(tableName, partition, engine) + } + + val job = feed.playBackground(channel) + + val ctx = SqlExecutionContextImpl(engine, 1).with(AllowAllSecurityContext.INSTANCE, null, null) + val writer = engine.getWriter(ctx.getTableToken(tableName), tableName) + + try { + val lookupTable = mutableMapOf() + while (true) { + val o = channel.receive() + for (action in o.items.filterIsInstance()) { + if (action::class == type) { + val row = writer.newRow(o.time.epochMicro) + val str = lookupTable.getOrPut(action.asset) { action.asset.serialize() } + row.putSym(0, str) + handler.updateRecord(row, action) + row.append() + } + } + writer.commit() + } + + } catch (_: ClosedReceiveChannelException) { + // On purpose left empty, expected exception + } finally { + writer.commit() + channel.close() + if (job.isActive) job.cancel() + ctx.close() + writer.close() + } + + } + + /** + * Generate a new QuestDB table based on the event in the feed and optional limited to the provided timeframe + * Supported price-actions: [PriceBar], [PriceQuote] and [TradePrice] + * + * @param feed the feed you want to record + * @param timeframe the timeframe + * @param append do you want to append to an existing table, default is false + * @param partition partition the table using the specified value. + * This is required when wanting to append timestamps out of order and might result in better overall performance. + * The default value is [NONE] + */ + inline fun record( + feed: Feed, + timeframe: Timeframe = Timeframe.INFINITE, + append: Boolean = false, + partition: String = NONE, + ) { + record(feed = feed, T::class, timeframe, append, partition) + } + + } diff --git a/roboquant-questdb/src/main/kotlin/org/roboquant/questdb/QuestDBJournal.kt b/roboquant-questdb/src/main/kotlin/org/roboquant/questdb/QuestDBJournal.kt index 3527648c..ba8bfbc0 100644 --- a/roboquant-questdb/src/main/kotlin/org/roboquant/questdb/QuestDBJournal.kt +++ b/roboquant-questdb/src/main/kotlin/org/roboquant/questdb/QuestDBJournal.kt @@ -47,7 +47,7 @@ class QuestDBJournal( dbPath: Path = Config.home / "questdb-metrics" / "db", private val table: String = "metrics", workers: Int = 1, - private val partition: String = QuestDBRecorder.NONE, + private val partition: String = QuestDBFeed.NONE, private val truncate: Boolean = false ) : MetricsJournal { @@ -85,6 +85,7 @@ class QuestDBJournal( return engine.tables().toSet() } + @Synchronized fun close(dbPath: Path) { engines[dbPath]?.close() } diff --git a/roboquant-questdb/src/main/kotlin/org/roboquant/questdb/QuestDBRecorder.kt b/roboquant-questdb/src/main/kotlin/org/roboquant/questdb/QuestDBRecorder.kt deleted file mode 100644 index cc810272..00000000 --- a/roboquant-questdb/src/main/kotlin/org/roboquant/questdb/QuestDBRecorder.kt +++ /dev/null @@ -1,210 +0,0 @@ -/* - * 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.questdb - - -import io.questdb.cairo.CairoConfiguration -import io.questdb.cairo.CairoEngine -import io.questdb.cairo.DefaultCairoConfiguration -import io.questdb.cairo.security.AllowAllSecurityContext -import io.questdb.griffin.SqlExecutionContextImpl -import kotlinx.coroutines.channels.ClosedReceiveChannelException -import kotlinx.coroutines.runBlocking -import org.roboquant.common.Asset -import org.roboquant.common.Config -import org.roboquant.common.Logging -import org.roboquant.common.Timeframe -import org.roboquant.feeds.* -import org.roboquant.feeds.util.AssetSerializer.serialize -import org.roboquant.questdb.PriceActionHandler.Companion.getHandler -import java.nio.file.Files -import java.nio.file.Path -import kotlin.io.path.div -import kotlin.io.path.isDirectory -import kotlin.reflect.KClass - - -/** - * Record another feed into a QuestDB database. - * - * - Supports up to micro seconds resolution - * - Supports large datasets - * - Fast random access - * - Limited to a single [PriceItem] type per table - * - * @param dbPath the path to use for the database. - * If it doesn't exist yet, it will be created. - * The default value is `~/.roboquant/questdb-prices/db` - * - */ -class QuestDBRecorder(dbPath: Path = Config.home / "questdb-prices" / "db") { - - private val config: CairoConfiguration - private val logger = Logging.getLogger(this::class) - - init { - if (Files.notExists(dbPath)) { - logger.info { "Creating new database path=$dbPath" } - Files.createDirectories(dbPath) - } - - require(dbPath.isDirectory()) { "dbPath needs to be a directory" } - config = DefaultCairoConfiguration(dbPath.toString()) - } - - /** - * Create a new engine - */ - private fun createEngine() = CairoEngine(config) - - @Suppress("unused") - /** - * Supported database partition schemes - */ - companion object Partition { - - /** - * Don't partition - */ - const val NONE = "NONE" - - /** - * Partition per year - */ - const val YEAR = "YEAR" - - /** - * Partition per day - */ - const val DAY = "DAY" - - /** - * Partition per month - */ - const val MONTH = "MONTH" - - /** - * Partition per hour - */ - const val HOUR = "HOUR" - } - - /** - * Remove all the feeds from the database. This cannot be undone, so use this method with care. - * This will effectively drop all tables that are currently found in the database. - */ - fun removeAllFeeds() { - CairoEngine(config).use { - it.dropAllTables() - logger.info { "Dropped all feeds in db=${config.root}" } - } - } - - /** - * Generate a new QuestDB table based on the event in the feed and optional limited to the provided timeframe - * Supported price-actions: [PriceBar], [PriceQuote] and [TradePrice] - * - * @param feed the feed you want to record - * @param type the type of price-action to capture - * @param tableName the table to use to store the data - * @param timeframe the timeframe - * @param append do you want to append to an existing table, default is false - * @param partition partition the table using the specified value. - * This is required when wanting to append timestamps out of order and might result in better overall performance. - * The default value is [NONE] - */ - fun record( - feed: Feed, - type: KClass<*>, - tableName: String, - timeframe: Timeframe = Timeframe.INFINITE, - append: Boolean = false, - partition: String = NONE, - ) = runBlocking { - - require(partition in setOf("YEAR", "MONTH", "DAY", "HOUR", "NONE")) { "invalid partition value" } - val handler = getHandler(type) - val channel = EventChannel(timeframe = timeframe) - - // Create a new engine, so it can be released once the recording is done and release - // any locks it has on the database - val engine = createEngine() - handler.createTable(tableName, partition, engine) - if (!append) engine.update("TRUNCATE TABLE $tableName") - - val job = feed.playBackground(channel) - - val ctx = SqlExecutionContextImpl(engine, 1).with(AllowAllSecurityContext.INSTANCE, null, null) - val writer = engine.getWriter(ctx.getTableToken(tableName), tableName) - - try { - val lookupTable = mutableMapOf() - while (true) { - val o = channel.receive() - for (action in o.items.filterIsInstance()) { - if (action::class == type) { - val row = writer.newRow(o.time.epochMicro) - val str = lookupTable.getOrPut(action.asset) { action.asset.serialize() } - row.putSym(0, str) - handler.updateRecord(row, action) - row.append() - } - } - writer.commit() - } - - } catch (_: ClosedReceiveChannelException) { - // On purpose left empty, expected exception - } finally { - writer.commit() - channel.close() - if (job.isActive) job.cancel() - ctx.close() - writer.close() - engine.close() - } - - } - - /** - * Generate a new QuestDB table based on the event in the feed and optional limited to the provided timeframe - * Supported price-actions: [PriceBar], [PriceQuote] and [TradePrice] - * - * @param feed the feed you want to record - * @param tableName the table to use to store the data - * @param timeframe the timeframe - * @param append do you want to append to an existing table, default is false - * @param partition partition the table using the specified value. - * This is required when wanting to append timestamps out of order and might result in better overall performance. - * The default value is [NONE] - */ - inline fun record( - feed: Feed, - tableName: String, - timeframe: Timeframe = Timeframe.INFINITE, - append: Boolean = false, - partition: String = NONE, - ) { - record(feed = feed, T::class, tableName, timeframe, append, partition) - } - - -} - - - - diff --git a/roboquant-questdb/src/test/kotlin/org/roboquant/questdb/QuestDBFeedTestIT.kt b/roboquant-questdb/src/test/kotlin/org/roboquant/questdb/QuestDBFeedTestIT.kt index fd6eaddb..b91a8d78 100644 --- a/roboquant-questdb/src/test/kotlin/org/roboquant/questdb/QuestDBFeedTestIT.kt +++ b/roboquant-questdb/src/test/kotlin/org/roboquant/questdb/QuestDBFeedTestIT.kt @@ -20,7 +20,7 @@ import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.io.TempDir import org.roboquant.common.* import org.roboquant.feeds.* -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import java.io.File import java.time.Instant import kotlin.test.Test @@ -32,12 +32,13 @@ internal class QuestDBFeedTestIT { @TempDir lateinit var folder: File + @Test fun basic() { - val recorder = QuestDBRecorder(folder.toPath()) - val inputFeed = RandomWalkFeed.lastYears(1) + val recorder = QuestDBFeed("pricebars", folder.toPath()) + val inputFeed = RandomWalk.lastYears(1) - recorder.record(inputFeed, "pricebars") + recorder.record(inputFeed, ) val outputFeed = QuestDBFeed("pricebars", folder.toPath()) @@ -48,11 +49,11 @@ internal class QuestDBFeedTestIT { @Test fun append() { - val recorder = QuestDBRecorder(folder.toPath()) - val inputFeed = RandomWalkFeed.lastYears(1) + val recorder = QuestDBFeed("pricebars2", folder.toPath()) + val inputFeed = RandomWalk.lastYears(1) val tfs = inputFeed.timeframe.split(3.months) - recorder.record(inputFeed, "pricebars2", tfs.first()) - tfs.drop(1).forEach { recorder.record(inputFeed, "pricebars2", it, true) } + recorder.record(inputFeed, tfs.first()) + tfs.drop(1).forEach { recorder.record(inputFeed, it, true) } val outputFeed = QuestDBFeed("pricebars2", folder.toPath()) @@ -63,14 +64,14 @@ internal class QuestDBFeedTestIT { @Test fun merge() { - val recorder = QuestDBRecorder(folder.toPath()) + val recorder = QuestDBFeed("pricebars3", folder.toPath()) val tf = Timeframe.parse("2020", "2022") - val feed1 = RandomWalkFeed(tf, nAssets = 1, template = Asset("ABC")) - val feed2 = RandomWalkFeed(tf, nAssets = 1, template = Asset("XYZ")) + val feed1 = RandomWalk(tf, nAssets = 1, template = Asset("ABC")) + val feed2 = RandomWalk(tf, nAssets = 1, template = Asset("XYZ")) // Need to partition when adding out-of-order price actions - recorder.record(feed1, "pricebars3", partition = "YEAR") - recorder.record(feed2, "pricebars3", append = true) + recorder.record(feed1, partition = "YEAR") + recorder.record(feed2, append = true) val outputFeed = QuestDBFeed("pricebars3", folder.toPath()) assertEquals(feed1.assets + feed2.assets, outputFeed.assets) @@ -78,8 +79,10 @@ internal class QuestDBFeedTestIT { outputFeed.close() } + + @Test - fun priceQuotes() { + fun record() { class QuoteFeed : Feed { override suspend fun play(channel: EventChannel) { @@ -94,19 +97,18 @@ internal class QuestDBFeedTestIT { } - val recorder = QuestDBRecorder(folder.toPath()) - val feed = QuoteFeed() val name = "quotes" + val feed = QuestDBFeed(name, folder.toPath()) + val origin = QuoteFeed() assertDoesNotThrow { - recorder.record(feed, name) + feed.record(origin) } - val outputFeed = QuestDBFeed(name, folder.toPath()) - assertEquals(1, outputFeed.assets.size) - println(outputFeed.timeframe) - assertEquals(99, outputFeed.timeframe.duration.toMillisPart()) - outputFeed.close() + assertEquals(1, feed.assets.size) + println(feed.timeframe) + assertEquals(99, feed.timeframe.duration.toMillisPart()) + feed.close() } @Test @@ -125,19 +127,17 @@ internal class QuestDBFeedTestIT { } - val recorder = QuestDBRecorder(folder.toPath()) - val feed = TradeFeed() - val name = "trades" + val feed = QuestDBFeed("trades", folder.toPath()) + val origin = TradeFeed() assertDoesNotThrow { - recorder.record(feed, name) + feed.record(origin) } - val outputFeed = QuestDBFeed(name, folder.toPath()) - assertEquals(1, outputFeed.assets.size) - println(outputFeed.timeframe) - assertEquals(99, outputFeed.timeframe.duration.toMillisPart()) - outputFeed.close() + assertEquals(1, feed.assets.size) + println(feed.timeframe) + assertEquals(99, feed.timeframe.duration.toMillisPart()) + feed.close() } diff --git a/roboquant-questdb/src/test/kotlin/org/roboquant/questdb/QuestDBJournalTestIT.kt b/roboquant-questdb/src/test/kotlin/org/roboquant/questdb/QuestDBJournalTestIT.kt index 0795d976..a7b8f5dc 100644 --- a/roboquant-questdb/src/test/kotlin/org/roboquant/questdb/QuestDBJournalTestIT.kt +++ b/roboquant-questdb/src/test/kotlin/org/roboquant/questdb/QuestDBJournalTestIT.kt @@ -20,11 +20,11 @@ import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.io.TempDir import org.roboquant.common.ParallelJobs import org.roboquant.common.years -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.journals.Journal import org.roboquant.journals.MultiRunJournal import org.roboquant.metrics.AccountMetric -import org.roboquant.strategies.EMAStrategy +import org.roboquant.strategies.EMACrossover import java.io.File import kotlin.test.Test import kotlin.test.assertEquals @@ -39,8 +39,8 @@ internal class QuestDBJournalTestIT { lateinit var folder2: File private fun simpleRun(journal: Journal) { - val feed = RandomWalkFeed.lastYears(1) - org.roboquant.run(feed, EMAStrategy(), journal) + val feed = RandomWalk.lastYears(1) + org.roboquant.run(feed, EMACrossover(), journal) } @Test @@ -61,13 +61,13 @@ internal class QuestDBJournalTestIT { val mrj = MultiRunJournal { run -> QuestDBJournal(AccountMetric(), dbPath = folder2.toPath(), table=run) } - val feed = RandomWalkFeed.lastYears(5) + val feed = RandomWalk.lastYears(5) val jobs = ParallelJobs() val tfs = feed.timeframe.split(1.years) for (tf in tfs) { jobs.add { val journal = mrj.getJournal() - org.roboquant.runAsync(feed, EMAStrategy(), journal, tf) + org.roboquant.runAsync(feed, EMACrossover(), journal, tf) } } jobs.joinAll() diff --git a/roboquant-questdb/src/test/kotlin/org/roboquant/samples/QuestDBSamples.kt b/roboquant-questdb/src/test/kotlin/org/roboquant/samples/QuestDBSamples.kt index c0835ee9..703459dd 100644 --- a/roboquant-questdb/src/test/kotlin/org/roboquant/samples/QuestDBSamples.kt +++ b/roboquant-questdb/src/test/kotlin/org/roboquant/samples/QuestDBSamples.kt @@ -20,13 +20,12 @@ import kotlinx.coroutines.runBlocking import org.roboquant.common.* import org.roboquant.feeds.PriceBar import org.roboquant.feeds.filter -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.journals.MultiRunJournal import org.roboquant.questdb.QuestDBFeed import org.roboquant.questdb.QuestDBJournal -import org.roboquant.questdb.QuestDBRecorder import org.roboquant.runAsync -import org.roboquant.strategies.EMAStrategy +import org.roboquant.strategies.EMACrossover import kotlin.system.measureTimeMillis import kotlin.test.Ignore import kotlin.test.Test @@ -46,13 +45,13 @@ internal class QuestDBSamples { @Test @Ignore internal fun parallel() = runBlocking { - val feed = RandomWalkFeed(Timeframe.past(4.years), nAssets = 3) + val feed = RandomWalk(Timeframe.past(4.years), nAssets = 3) val jobs = ParallelJobs() val mrj = MultiRunJournal { run -> QuestDBJournal(table = run) } for (tf in feed.timeframe.split(1.years)) { jobs.add { - val acc = runAsync(feed, EMAStrategy(),mrj.getJournal(), timeframe = tf) + val acc = runAsync(feed, EMACrossover(),mrj.getJournal(), timeframe = tf) println(acc) } } @@ -62,17 +61,6 @@ internal class QuestDBSamples { } - @Test - @Ignore - internal fun create() { - val f = RandomWalkFeed(Timeframe.past(12.months), nAssets = 3, timeSpan = 1.seconds) - - printTimeMillis("create feed") { - val g = QuestDBRecorder() - g.removeAllFeeds() - g.record(f, tableName) - } - } @Test @Ignore diff --git a/roboquant-server/src/test/kotlin/org/roboquant/samples/ServerSamples.kt b/roboquant-server/src/test/kotlin/org/roboquant/samples/ServerSamples.kt index 08af638a..5a41905e 100644 --- a/roboquant-server/src/test/kotlin/org/roboquant/samples/ServerSamples.kt +++ b/roboquant-server/src/test/kotlin/org/roboquant/samples/ServerSamples.kt @@ -23,7 +23,7 @@ import org.roboquant.journals.MemoryJournal import org.roboquant.metrics.AccountMetric import org.roboquant.metrics.PriceMetric import org.roboquant.server.WebServer -import org.roboquant.strategies.EMAStrategy +import org.roboquant.strategies.EMACrossover import kotlin.system.exitProcess import kotlin.test.Ignore import kotlin.test.Test @@ -32,7 +32,7 @@ import kotlin.test.Test internal class ServerSamples { private fun getRoboquant() = - Roboquant(EMAStrategy()) + Roboquant(EMACrossover()) private fun getJournal() = MemoryJournal(PriceMetric("CLOSE"), AccountMetric()) diff --git a/roboquant-server/src/test/kotlin/org/roboquant/server/WebServerTestIT.kt b/roboquant-server/src/test/kotlin/org/roboquant/server/WebServerTestIT.kt index e9e23b5c..69082eea 100644 --- a/roboquant-server/src/test/kotlin/org/roboquant/server/WebServerTestIT.kt +++ b/roboquant-server/src/test/kotlin/org/roboquant/server/WebServerTestIT.kt @@ -20,8 +20,8 @@ import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.assertDoesNotThrow import org.roboquant.Roboquant import org.roboquant.common.Timeframe -import org.roboquant.feeds.random.RandomWalkFeed -import org.roboquant.strategies.EMAStrategy +import org.roboquant.feeds.random.RandomWalk +import org.roboquant.strategies.EMACrossover import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest @@ -51,8 +51,8 @@ internal class WebServerTestIT { @Test fun basic() { // System.setProperty(org.slf4j.simple.SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "TRACE") - val feed = RandomWalkFeed(Timeframe.fromYears(2000, 2001)) - val rq = Roboquant(EMAStrategy()) + val feed = RandomWalk(Timeframe.fromYears(2000, 2001)) + val rq = Roboquant(EMACrossover()) assertDoesNotThrow { diff --git a/roboquant-ssr/src/test/kotlin/org/roboquant/charts/ImageCreatorTestIT.kt b/roboquant-ssr/src/test/kotlin/org/roboquant/charts/ImageCreatorTestIT.kt index cdd24cda..59dabe45 100644 --- a/roboquant-ssr/src/test/kotlin/org/roboquant/charts/ImageCreatorTestIT.kt +++ b/roboquant-ssr/src/test/kotlin/org/roboquant/charts/ImageCreatorTestIT.kt @@ -17,7 +17,7 @@ package org.roboquant.charts import org.junit.jupiter.api.io.TempDir -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import java.io.File import java.nio.file.Files import kotlin.test.Test @@ -27,7 +27,7 @@ import kotlin.test.assertTrue internal class ImageCreatorTestIT { private val imageCreator = ImageCreator() - private val feed = RandomWalkFeed.lastYears(1, nAssets = 5) + private val feed = RandomWalk.lastYears(1, nAssets = 5) @TempDir lateinit var folder: File diff --git a/roboquant-ssr/src/test/kotlin/org/roboquant/samples/SSRSamples.kt b/roboquant-ssr/src/test/kotlin/org/roboquant/samples/SSRSamples.kt index 2f3ff749..f9ba87eb 100644 --- a/roboquant-ssr/src/test/kotlin/org/roboquant/samples/SSRSamples.kt +++ b/roboquant-ssr/src/test/kotlin/org/roboquant/samples/SSRSamples.kt @@ -20,7 +20,7 @@ import org.roboquant.charts.CorrelationChart import org.roboquant.charts.ImageCreator import org.roboquant.charts.PriceBarChart import org.roboquant.charts.transcodeSVG2PNG -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import java.io.File import kotlin.test.Ignore import kotlin.test.Test @@ -49,7 +49,7 @@ class SSRSamples { @Test @Ignore internal fun run() { - val feed = RandomWalkFeed.lastYears(2, nAssets = 5) + val feed = RandomWalk.lastYears(2, nAssets = 5) val chart = CorrelationChart(feed, feed.assets) val creator = ImageCreator() diff --git a/roboquant-ta/src/main/kotlin/org/roboquant/ta/TaLibStrategy.kt b/roboquant-ta/src/main/kotlin/org/roboquant/ta/TaLibStrategy.kt index 45575d1a..26d82d25 100644 --- a/roboquant-ta/src/main/kotlin/org/roboquant/ta/TaLibStrategy.kt +++ b/roboquant-ta/src/main/kotlin/org/roboquant/ta/TaLibStrategy.kt @@ -114,7 +114,7 @@ class TaLibStrategy(initialCapacity: Int = 1) : Strategy { /** * Returns an EMA crossover Strategy using the provided [slow] and [fast] times. * - * See also [org.roboquant.strategies.EMAStrategy] for more efficient implementation. + * See also [org.roboquant.strategies.EMACrossover] for more efficient implementation. */ fun emaCrossover(slow: Int, fast: Int): TaLibStrategy { require(slow > 0 && fast > 0) { "Periods have to be larger than 0" } diff --git a/roboquant-ta/src/test/kotlin/org/roboquant/samples/TaSamples.kt b/roboquant-ta/src/test/kotlin/org/roboquant/samples/TaSamples.kt index 2330ab0f..83294826 100644 --- a/roboquant-ta/src/test/kotlin/org/roboquant/samples/TaSamples.kt +++ b/roboquant-ta/src/test/kotlin/org/roboquant/samples/TaSamples.kt @@ -24,11 +24,11 @@ import org.roboquant.common.Size import org.roboquant.common.percent import org.roboquant.feeds.Event import org.roboquant.feeds.PriceItem -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.orders.LimitOrder import org.roboquant.orders.Instruction import org.roboquant.policies.FlexPolicy -import org.roboquant.strategies.EMAStrategy +import org.roboquant.strategies.EMACrossover import org.roboquant.strategies.Signal import org.roboquant.ta.* import org.ta4j.core.indicators.bollinger.BollingerBandFacade @@ -53,7 +53,7 @@ internal class TaSamples { } val rq = Roboquant(strategy) - val feed = RandomWalkFeed.lastYears(5) + val feed = RandomWalk.lastYears(5) val account = rq.run(feed) println(account) @@ -80,7 +80,7 @@ internal class TaSamples { } val rq = Roboquant(strategy) - val feed = RandomWalkFeed.lastYears(5) + val feed = RandomWalk.lastYears(5) val account = rq.run(feed) println(account) } @@ -131,8 +131,8 @@ internal class TaSamples { } } - val roboquant = Roboquant(EMAStrategy.PERIODS_12_26, policy = SmartLimitPolicy(atrPeriod = 5)) - val feed = RandomWalkFeed.lastYears(5) + val roboquant = Roboquant(EMACrossover.PERIODS_12_26, policy = SmartLimitPolicy(atrPeriod = 5)) + val feed = RandomWalk.lastYears(5) val account = roboquant.run(feed) println(account) } @@ -140,7 +140,7 @@ internal class TaSamples { @Test @Ignore internal fun atrPolicy() { - val strategy = EMAStrategy.PERIODS_5_15 + val strategy = EMACrossover.PERIODS_5_15 val policy = AtrPolicy(10, 6.0, 3.0, null) { orderPercentage = 0.02 shorting = true @@ -148,7 +148,7 @@ internal class TaSamples { val broker = SimBroker(accountModel = MarginAccount(minimumEquity = 50_000.0)) val rq = Roboquant(strategy, broker = broker, policy = policy) - val feed = RandomWalkFeed.lastYears(5) + val feed = RandomWalk.lastYears(5) val account = rq.run(feed) println(account) } @@ -189,7 +189,7 @@ internal class TaSamples { } val rq = Roboquant(strategy) - val feed = RandomWalkFeed.lastYears(5) + val feed = RandomWalk.lastYears(5) println(feed.timeframe) val account = rq.run(feed) diff --git a/roboquant-ta/src/test/kotlin/org/roboquant/ta/BettingAgainstBetaPolicyTest.kt b/roboquant-ta/src/test/kotlin/org/roboquant/ta/BettingAgainstBetaPolicyTest.kt index d3b881a1..0dd0054c 100644 --- a/roboquant-ta/src/test/kotlin/org/roboquant/ta/BettingAgainstBetaPolicyTest.kt +++ b/roboquant-ta/src/test/kotlin/org/roboquant/ta/BettingAgainstBetaPolicyTest.kt @@ -18,7 +18,7 @@ package org.roboquant.ta import org.roboquant.brokers.sim.MarginAccount import org.roboquant.brokers.sim.SimBroker -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.run import org.roboquant.strategies.NoSignalStrategy import kotlin.test.Test @@ -28,7 +28,7 @@ internal class BettingAgainstBetaPolicyTest { @Test fun test() { - val feed = RandomWalkFeed.lastYears(nAssets = 20) + val feed = RandomWalk.lastYears(nAssets = 20) val assets = feed.assets.toList() val marketAsset = assets.first() diff --git a/roboquant-ta/src/test/kotlin/org/roboquant/ta/RSIStrategyTest.kt b/roboquant-ta/src/test/kotlin/org/roboquant/ta/RSIStrategyTest.kt index 36da6c45..c48ec605 100644 --- a/roboquant-ta/src/test/kotlin/org/roboquant/ta/RSIStrategyTest.kt +++ b/roboquant-ta/src/test/kotlin/org/roboquant/ta/RSIStrategyTest.kt @@ -18,12 +18,12 @@ package org.roboquant.ta import kotlinx.coroutines.runBlocking import org.roboquant.Roboquant -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import kotlin.test.Test import kotlin.test.assertEquals internal class RSIStrategyTest { - private val feed = RandomWalkFeed.lastYears(1, 2) + private val feed = RandomWalk.lastYears(1, 2) @Test fun test() = runBlocking { diff --git a/roboquant-ta/src/test/kotlin/org/roboquant/ta/Ta4JStrategyTest.kt b/roboquant-ta/src/test/kotlin/org/roboquant/ta/Ta4JStrategyTest.kt index 88f05a57..5741d087 100644 --- a/roboquant-ta/src/test/kotlin/org/roboquant/ta/Ta4JStrategyTest.kt +++ b/roboquant-ta/src/test/kotlin/org/roboquant/ta/Ta4JStrategyTest.kt @@ -18,7 +18,7 @@ package org.roboquant.ta import org.junit.jupiter.api.assertDoesNotThrow import org.roboquant.Roboquant -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.ta4j.core.indicators.SMAIndicator import org.ta4j.core.indicators.helpers.ClosePriceIndicator import org.ta4j.core.rules.CrossedDownIndicatorRule @@ -51,7 +51,7 @@ internal class Ta4JStrategyTest { } val roboquant = Roboquant(strategy) - val feed = RandomWalkFeed.lastYears(1, nAssets = 2) + val feed = RandomWalk.lastYears(1, nAssets = 2) assertDoesNotThrow { roboquant.run(feed) } @@ -62,7 +62,7 @@ internal class Ta4JStrategyTest { // Default rule is false, meaning no signals val strategy = Ta4jStrategy(maxBarCount = 30) val roboquant = Roboquant(strategy) - val feed = RandomWalkFeed.lastYears(1, nAssets = 2) + val feed = RandomWalk.lastYears(1, nAssets = 2) val account = roboquant.run(feed) assertTrue(account.closedOrders.isEmpty()) } diff --git a/roboquant-ta/src/test/kotlin/org/roboquant/ta/TaLibStrategyTest.kt b/roboquant-ta/src/test/kotlin/org/roboquant/ta/TaLibStrategyTest.kt index 8305f4e5..92390690 100644 --- a/roboquant-ta/src/test/kotlin/org/roboquant/ta/TaLibStrategyTest.kt +++ b/roboquant-ta/src/test/kotlin/org/roboquant/ta/TaLibStrategyTest.kt @@ -26,7 +26,7 @@ import org.roboquant.common.seconds import org.roboquant.feeds.Event import org.roboquant.feeds.PriceBar import org.roboquant.feeds.filter -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.feeds.util.HistoricTestFeed import org.roboquant.strategies.Signal import org.roboquant.strategies.Strategy @@ -201,7 +201,7 @@ internal class TaLibStrategyTest { // Default rule is false, meaning no signals val strategy = TaLibStrategy(30) val roboquant = Roboquant(strategy) - val feed = RandomWalkFeed.lastYears(1, nAssets = 2) + val feed = RandomWalk.lastYears(1, nAssets = 2) val account = roboquant.run(feed) assertTrue(account.closedOrders.isEmpty()) } diff --git a/roboquant-tiingo/src/test/kotlin/org/roboquant/samples/TiingoSamples.kt b/roboquant-tiingo/src/test/kotlin/org/roboquant/samples/TiingoSamples.kt index fb1aee64..15156e9e 100644 --- a/roboquant-tiingo/src/test/kotlin/org/roboquant/samples/TiingoSamples.kt +++ b/roboquant-tiingo/src/test/kotlin/org/roboquant/samples/TiingoSamples.kt @@ -23,7 +23,7 @@ import org.roboquant.common.* import org.roboquant.feeds.* import org.roboquant.journals.BasicJournal import org.roboquant.run -import org.roboquant.strategies.EMAStrategy +import org.roboquant.strategies.EMACrossover import org.roboquant.tiingo.TiingoHistoricFeed import org.roboquant.tiingo.TiingoLiveFeed import java.time.Instant @@ -38,7 +38,7 @@ internal class TiingoSamples { System.setProperty(org.slf4j.simple.SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "TRACE") val feed = TiingoLiveFeed.iex() feed.subscribe("AAPL", "TSLA") - val rq = Roboquant(EMAStrategy()) + val rq = Roboquant(EMACrossover()) val account = rq.run(feed, timeframe = Timeframe.next(10.minutes)) println(account) } @@ -50,7 +50,7 @@ internal class TiingoSamples { iex.subscribe() val feed = AggregatorLiveFeed(iex, 5.seconds, restrictType = TradePrice::class) val tf = Timeframe.next(5.minutes) - val account = run(feed, EMAStrategy(), BasicJournal(true), tf) + val account = run(feed, EMACrossover(), BasicJournal(true), tf) println(account) } @@ -59,7 +59,7 @@ internal class TiingoSamples { internal fun testLiveFeedFX() { val feed = TiingoLiveFeed.fx() feed.subscribe("EURUSD") - val rq = Roboquant(EMAStrategy()) + val rq = Roboquant(EMACrossover()) val account = rq.run(feed, timeframe = Timeframe.next(1.minutes)) println(account) } @@ -72,7 +72,7 @@ internal class TiingoSamples { Config.registerAsset("BNBFDUSD", asset) feed.subscribe("BNBFDUSD") - val rq = Roboquant(EMAStrategy()) + val rq = Roboquant(EMACrossover()) val account = rq.run(feed, timeframe = Timeframe.next(1.minutes)) println(account) } @@ -140,7 +140,7 @@ internal class TiingoSamples { feed.retrieve("AAPL", "TSLA", "ERROR_SYMBOL", timeframe=tf) println(feed.assets) println(feed.timeframe) - val rq = Roboquant(EMAStrategy()) + val rq = Roboquant(EMACrossover()) val account = rq.run(feed) println(account) } @@ -153,7 +153,7 @@ internal class TiingoSamples { feed.retrieveIntraday("AAPL", "TSLA", "ERROR_SYMBOL", timeframe=tf, frequency = "1hour") println(feed.assets) println(feed.timeframe) - val rq = Roboquant(EMAStrategy()) + val rq = Roboquant(EMACrossover()) val account = rq.run(feed) println(account) } diff --git a/roboquant-tiingo/src/test/kotlin/org/roboquant/tiingo/TiingoLiveFeedTestIT.kt b/roboquant-tiingo/src/test/kotlin/org/roboquant/tiingo/TiingoLiveFeedTestIT.kt index 6a31badd..97a67bb9 100644 --- a/roboquant-tiingo/src/test/kotlin/org/roboquant/tiingo/TiingoLiveFeedTestIT.kt +++ b/roboquant-tiingo/src/test/kotlin/org/roboquant/tiingo/TiingoLiveFeedTestIT.kt @@ -19,7 +19,7 @@ package org.roboquant.tiingo import org.roboquant.Roboquant import org.roboquant.common.* import org.roboquant.journals.BasicJournal -import org.roboquant.strategies.EMAStrategy +import org.roboquant.strategies.EMACrossover import kotlin.test.Test import kotlin.test.assertTrue @@ -30,7 +30,7 @@ internal class TiingoLiveFeedTestIT { Config.getProperty("TEST_TIINGO") ?: return val feed = TiingoLiveFeed.iex() feed.subscribe("AAPL", "TSLA") - val rq = Roboquant(EMAStrategy()) + val rq = Roboquant(EMACrossover()) val journal = BasicJournal() rq.run(feed, journal=journal, timeframe = Timeframe.next(1.minutes)) assertTrue(journal.nItems > 0) @@ -41,7 +41,7 @@ internal class TiingoLiveFeedTestIT { Config.getProperty("TEST_TIINGO") ?: return val feed = TiingoLiveFeed.fx() feed.subscribe("EURUSD") - val rq = Roboquant(EMAStrategy()) + val rq = Roboquant(EMACrossover()) rq.run(feed, timeframe = Timeframe.next(1.minutes)) } @@ -52,7 +52,7 @@ internal class TiingoLiveFeedTestIT { val asset = Asset("BNBFDUSD", AssetType.CRYPTO, "FDUSD") Config.registerAsset("BNBFDUSD", asset) feed.subscribe("BNBFDUSD") - val rq = Roboquant(EMAStrategy()) + val rq = Roboquant(EMACrossover()) rq.run(feed, timeframe = Timeframe.next(1.minutes)) } diff --git a/roboquant/src/main/kotlin/org/roboquant/common/Config.kt b/roboquant/src/main/kotlin/org/roboquant/common/Config.kt index 0027f972..aff1679a 100644 --- a/roboquant/src/main/kotlin/org/roboquant/common/Config.kt +++ b/roboquant/src/main/kotlin/org/roboquant/common/Config.kt @@ -69,7 +69,7 @@ object Config { symbolMap[symbol] = asset } - fun getOrPutAsset(symbol: String, defaultValue: () -> Asset) : Asset { + fun getOrPutAsset(symbol: String, defaultValue: () -> Asset): Asset { return symbolMap.getOrPut(symbol, defaultValue) } @@ -145,7 +145,9 @@ object Config { * If it does not exist, it will be created the first time this property is called. */ val home: Path by lazy { - val path: Path = Paths.get(System.getProperty("user.home"), ".roboquant") + val roboquantHome = System.getProperty("roboquant.home") + val path: Path = if (roboquantHome != null) Paths.get(roboquantHome) else + Paths.get(System.getProperty("user.home"), ".roboquant") if (Files.notExists(path)) { Files.createDirectory(path) logger.trace { "Created new home directory $path" } diff --git a/roboquant/src/main/kotlin/org/roboquant/common/Universe.kt b/roboquant/src/main/kotlin/org/roboquant/common/Universe.kt index 3cc9bfc0..ab20c7df 100644 --- a/roboquant/src/main/kotlin/org/roboquant/common/Universe.kt +++ b/roboquant/src/main/kotlin/org/roboquant/common/Universe.kt @@ -48,6 +48,7 @@ interface Universe { } } + /** * SP500 asset collection */ diff --git a/roboquant/src/main/kotlin/org/roboquant/feeds/random/RandomWalkFeed.kt b/roboquant/src/main/kotlin/org/roboquant/feeds/random/RandomWalk.kt similarity index 92% rename from roboquant/src/main/kotlin/org/roboquant/feeds/random/RandomWalkFeed.kt rename to roboquant/src/main/kotlin/org/roboquant/feeds/random/RandomWalk.kt index 735782a9..a1c25922 100644 --- a/roboquant/src/main/kotlin/org/roboquant/feeds/random/RandomWalkFeed.kt +++ b/roboquant/src/main/kotlin/org/roboquant/feeds/random/RandomWalk.kt @@ -46,7 +46,7 @@ import java.time.LocalDate * @param template template to use when generating assets * @property seed seed to use for initializing the random generator, default is 42 */ -class RandomWalkFeed( +class RandomWalk( override val timeframe: Timeframe, private val timeSpan: TimeSpan = 1.days, nAssets: Int = 10, @@ -99,25 +99,25 @@ class RandomWalkFeed( */ companion object { - private val logger = Logging.getLogger(RandomWalkFeed::class) + private val logger = Logging.getLogger(RandomWalk::class) /** * Create a random walk for the last [years], generating daily prices */ - fun lastYears(years: Int = 1, nAssets: Int = 10, priceType: PriceItemType = PriceItemType.BAR): RandomWalkFeed { + fun lastYears(years: Int = 1, nAssets: Int = 10, priceType: PriceItemType = PriceItemType.BAR): RandomWalk { val lastYear = LocalDate.now().year val tf = Timeframe.fromYears(lastYear - years, lastYear) - return RandomWalkFeed(tf, 1.days, nAssets, priceType) + return RandomWalk(tf, 1.days, nAssets, priceType) } /** * Create a random walk for the last [days], generating minute prices. */ - fun lastDays(days: Int = 1, nAssets: Int = 10, priceType: PriceItemType = PriceItemType.BAR): RandomWalkFeed { + fun lastDays(days: Int = 1, nAssets: Int = 10, priceType: PriceItemType = PriceItemType.BAR): RandomWalk { val last = Instant.now() val first = last - days.days val tf = Timeframe(first, last) - return RandomWalkFeed(tf, 1.minutes, nAssets, priceType) + return RandomWalk(tf, 1.minutes, nAssets, priceType) } } diff --git a/roboquant/src/main/kotlin/org/roboquant/feeds/random/RandomWalkLiveFeed.kt b/roboquant/src/main/kotlin/org/roboquant/feeds/random/RandomWalkLiveFeed.kt index 83fcbb05..64be9fbf 100644 --- a/roboquant/src/main/kotlin/org/roboquant/feeds/random/RandomWalkLiveFeed.kt +++ b/roboquant/src/main/kotlin/org/roboquant/feeds/random/RandomWalkLiveFeed.kt @@ -28,7 +28,7 @@ import java.time.Instant * Internally, it uses a seeded random generator. So while it generates random data, the results can be reproduced if * instantiated with the same seed. It can generate [PriceBar] or [TradePrice] prices. * - * @see RandomWalkFeed for a historic data random walk + * @see RandomWalk for a historic data random walk * * @property timeSpan the timeSpan between two events, default is `1.seconds` * @param nAssets the number of assets to generate, default is 10. diff --git a/roboquant/src/main/kotlin/org/roboquant/journals/MultiRunJournal.kt b/roboquant/src/main/kotlin/org/roboquant/journals/MultiRunJournal.kt index fe047b01..21c6369d 100644 --- a/roboquant/src/main/kotlin/org/roboquant/journals/MultiRunJournal.kt +++ b/roboquant/src/main/kotlin/org/roboquant/journals/MultiRunJournal.kt @@ -29,10 +29,10 @@ class MultiRunJournal(private val fn: (String) -> MetricsJournal) { } /** - * Load existing runs + * Load potentially existing runs from MetricsJournals that are persistent * @param runs List */ - fun load(runs: List) { + fun load(runs: Collection) { for (run in runs) {getJournal(run)} } @@ -54,4 +54,12 @@ class MultiRunJournal(private val fn: (String) -> MetricsJournal) { return journals.values.map { it.getMetricNames() }.flatten().toSet() } + /** + * Reset the state of this multi-run, removing already registered runs + */ + @Synchronized + fun reset() { + journals.clear() + } + } diff --git a/roboquant/src/main/kotlin/org/roboquant/strategies/EMAStrategy.kt b/roboquant/src/main/kotlin/org/roboquant/strategies/EMACrossover.kt similarity index 90% rename from roboquant/src/main/kotlin/org/roboquant/strategies/EMAStrategy.kt rename to roboquant/src/main/kotlin/org/roboquant/strategies/EMACrossover.kt index afa3f590..38591179 100644 --- a/roboquant/src/main/kotlin/org/roboquant/strategies/EMAStrategy.kt +++ b/roboquant/src/main/kotlin/org/roboquant/strategies/EMACrossover.kt @@ -31,7 +31,7 @@ import java.time.Instant * * This is a computational and memory efficient implementation since it doesn't store historic prices in memory. * - * @constructor Create a new EMAStrategy strategy + * @constructor Create a new EMACrossover strategy * * @param fastPeriod The shorter (fast) period or fast EMA in number of events, default is 12 * @param slowPeriod The longer (slow) period or slow EMA in number of events, default is 26 @@ -40,7 +40,7 @@ import java.time.Instant * @property priceType the type of price to use, like "CLOSE" or "OPEN", default is "DEFAULT" * as the slow period */ -class EMAStrategy( +class EMACrossover( fastPeriod: Int = 12, slowPeriod: Int = 26, smoothing: Double = 2.0, @@ -59,26 +59,26 @@ class EMAStrategy( /** * Predefined EMA Crossover with 50 steps for the fast trend and 200 steps for slow trend * - * @return new EMAStrategy + * @return new EMACrossover */ - val PERIODS_50_200: EMAStrategy - get() = EMAStrategy(50, 200) + val PERIODS_50_200: EMACrossover + get() = EMACrossover(50, 200) /** * Predefined EMA Crossover with 12 steps for fast EMA and 26 steps for slow EMA * - * @return new EMAStrategy + * @return new EMACrossover */ - val PERIODS_12_26: EMAStrategy - get() = EMAStrategy(12, 26) + val PERIODS_12_26: EMACrossover + get() = EMACrossover(12, 26) /** * Predefined EMA Crossover with 5 steps for fast EMA and 15 steps for slow EMA * - * @return new EMAStrategy + * @return new EMACrossover */ - val PERIODS_5_15: EMAStrategy - get() = EMAStrategy(5, 15) + val PERIODS_5_15: EMACrossover + get() = EMACrossover(5, 15) } private inner class EMACalculator(initialPrice: Double) { diff --git a/roboquant/src/test/kotlin/org/roboquant/RoboquantTest.kt b/roboquant/src/test/kotlin/org/roboquant/RoboquantTest.kt index 0a84cc86..5b3d1f50 100644 --- a/roboquant/src/test/kotlin/org/roboquant/RoboquantTest.kt +++ b/roboquant/src/test/kotlin/org/roboquant/RoboquantTest.kt @@ -20,14 +20,14 @@ import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.assertDoesNotThrow import org.roboquant.brokers.sim.SimBroker import org.roboquant.common.* -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.feeds.util.HistoricTestFeed import org.roboquant.feeds.util.LiveTestFeed import org.roboquant.journals.BasicJournal import org.roboquant.journals.MemoryJournal import org.roboquant.journals.MultiRunJournal import org.roboquant.metrics.PNLMetric -import org.roboquant.strategies.EMAStrategy +import org.roboquant.strategies.EMACrossover import org.roboquant.strategies.TestStrategy import kotlin.test.Test import kotlin.test.assertEquals @@ -37,7 +37,7 @@ internal class RoboquantTest { @Test fun simpleRun() { - val strategy = EMAStrategy() + val strategy = EMACrossover() val roboquant = Roboquant(strategy) assertDoesNotThrow { roboquant.run(TestData.feed) @@ -64,7 +64,7 @@ internal class RoboquantTest { @Test fun runAsync() = runBlocking { - val strategy = EMAStrategy() + val strategy = EMACrossover() val roboquant = Roboquant(strategy) assertDoesNotThrow { @@ -78,7 +78,7 @@ internal class RoboquantTest { @Test fun run2() { assertDoesNotThrow { - val strategy = EMAStrategy() + val strategy = EMACrossover() val journal = BasicJournal() run(TestData.feed, strategy, journal) assertTrue(journal.nEvents > 0) @@ -93,10 +93,10 @@ internal class RoboquantTest { @Test fun walkforward() { - val feed = RandomWalkFeed.lastYears(10, 2) + val feed = RandomWalk.lastYears(10, 2) val tfs = feed.timeframe.split(2.years) for (tf in tfs) { - val strategy = EMAStrategy() + val strategy = EMACrossover() val journal = BasicJournal() run(TestData.feed, strategy, journal, tf) } @@ -105,7 +105,7 @@ internal class RoboquantTest { @Test fun run_with_pb() { assertDoesNotThrow { - val strategy = EMAStrategy() + val strategy = EMACrossover() val journal = BasicJournal() run(TestData.feed, strategy, journal, showProgressBar = true) } @@ -114,7 +114,7 @@ internal class RoboquantTest { @Test fun run4() { assertDoesNotThrow { - val strategy = EMAStrategy() + val strategy = EMACrossover() val feed = LiveTestFeed(delayInMillis = 30) val journal = BasicJournal(false) run(feed, strategy, journal, heartbeatTimeout = 10) @@ -128,7 +128,7 @@ internal class RoboquantTest { val feed = TestData.feed val timeframes = feed.timeframe.split(1.years) for (tf in timeframes) { - val strategy = EMAStrategy() + val strategy = EMACrossover() val run = tf.toString() run(TestData.feed, strategy, journal = mrj.getJournal(run), tf) } @@ -143,7 +143,7 @@ internal class RoboquantTest { repeat(50) { jobs.add { - val roboquant = Roboquant(EMAStrategy()) + val roboquant = Roboquant(EMACrossover()) roboquant.runAsync(feed) } } @@ -160,7 +160,7 @@ internal class RoboquantTest { feed.timeframe.sample(3.months).forEach { jobs.add { val tf = it - val acc = runAsync(feed, EMAStrategy(), timeframe = tf) + val acc = runAsync(feed, EMACrossover(), timeframe = tf) println(acc.lastUpdate) println(tf) assertTrue(acc.lastUpdate in tf) diff --git a/roboquant/src/test/kotlin/org/roboquant/TestData.kt b/roboquant/src/test/kotlin/org/roboquant/TestData.kt index 8c6c7614..f14e8252 100644 --- a/roboquant/src/test/kotlin/org/roboquant/TestData.kt +++ b/roboquant/src/test/kotlin/org/roboquant/TestData.kt @@ -23,7 +23,7 @@ import org.roboquant.brokers.assets import org.roboquant.brokers.sim.execution.InternalAccount import org.roboquant.common.* import org.roboquant.feeds.* -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.feeds.util.HistoricTestFeed import org.roboquant.feeds.util.play import org.roboquant.orders.MarketOrder @@ -113,7 +113,7 @@ internal object TestData { return result } - val feed = RandomWalkFeed.lastYears(1, 2) + val feed = RandomWalk.lastYears(1, 2) } diff --git a/roboquant/src/test/kotlin/org/roboquant/feeds/AggregatorFeedTest.kt b/roboquant/src/test/kotlin/org/roboquant/feeds/AggregatorFeedTest.kt index 0bfe2a1e..baca225a 100644 --- a/roboquant/src/test/kotlin/org/roboquant/feeds/AggregatorFeedTest.kt +++ b/roboquant/src/test/kotlin/org/roboquant/feeds/AggregatorFeedTest.kt @@ -18,9 +18,9 @@ package org.roboquant.feeds import org.roboquant.Roboquant import org.roboquant.common.* -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.feeds.util.LiveTestFeed -import org.roboquant.strategies.EMAStrategy +import org.roboquant.strategies.EMACrossover import java.time.Instant import java.time.temporal.ChronoUnit import kotlin.test.Test @@ -31,7 +31,7 @@ internal class AggregatorFeedTest { @Test fun basic() { - val feed = RandomWalkFeed.lastDays(1) + val feed = RandomWalk.lastDays(1) val items1 = feed.toList() val aggFeed = AggregatorFeed(feed, 15.minutes) @@ -51,7 +51,7 @@ internal class AggregatorFeedTest { @Test fun aggregatorFeed2() { val tf = Timeframe.parse("2022-01-01T12:00:00", "2022-01-01T15:00:00") - val feed = RandomWalkFeed(tf, 1.minutes, nAssets = 1) + val feed = RandomWalk(tf, 1.minutes, nAssets = 1) val ts = 15.minutes val aggFeed = AggregatorFeed(feed, ts) var lastTime: Instant? = null @@ -89,7 +89,7 @@ internal class AggregatorFeedTest { fun basic2() { // 5-seconds window with 1-millisecond resolution val timeframe = Timeframe.parse("2022-01-01T00:00:00Z", "2022-01-01T00:00:05Z") - val feed = RandomWalkFeed(timeframe, 1.millis, priceType = PriceItemType.TRADE) + val feed = RandomWalk(timeframe, 1.millis, priceType = PriceItemType.TRADE) val items1 = feed.toList() val aggFeed = AggregatorFeed(feed, 1.seconds) @@ -109,13 +109,13 @@ internal class AggregatorFeedTest { fun parallel() { // 5-seconds window with 1-millisecond resolution val timeframe = Timeframe.parse("2022-01-01T00:00:00Z", "2022-01-01T00:00:05Z") - val feed = RandomWalkFeed(timeframe, 1.millis, priceType = PriceItemType.TRADE) + val feed = RandomWalk(timeframe, 1.millis, priceType = PriceItemType.TRADE) val aggFeed = AggregatorFeed(feed, 1.seconds) val jobs = ParallelJobs() aggFeed.timeframe.split(3.months).forEach { jobs.add { - val rq = Roboquant(EMAStrategy()) + val rq = Roboquant(EMACrossover()) rq.runAsync(aggFeed, timeframe = it) } } @@ -125,7 +125,7 @@ internal class AggregatorFeedTest { fun combined() { // 5-seconds window with 1-millisecond resolution val timeframe = Timeframe.parse("2022-01-01T00:00:00Z", "2022-01-01T00:00:05Z") - val rw = RandomWalkFeed(timeframe, 1.millis, priceType = PriceItemType.TRADE) + val rw = RandomWalk(timeframe, 1.millis, priceType = PriceItemType.TRADE) val items1 = rw.toList() val aggFeed1 = AggregatorFeed(rw, 1.seconds) diff --git a/roboquant/src/test/kotlin/org/roboquant/feeds/CombinedFeedTest.kt b/roboquant/src/test/kotlin/org/roboquant/feeds/CombinedFeedTest.kt index d24593fa..abbe588e 100644 --- a/roboquant/src/test/kotlin/org/roboquant/feeds/CombinedFeedTest.kt +++ b/roboquant/src/test/kotlin/org/roboquant/feeds/CombinedFeedTest.kt @@ -18,7 +18,7 @@ package org.roboquant.feeds import kotlinx.coroutines.runBlocking import org.roboquant.feedTest -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.feeds.util.play import java.time.Instant import kotlin.test.Test @@ -29,8 +29,8 @@ internal class CombinedFeedTest { @Test fun testCombinedFeed2() = runBlocking { - val f1 = RandomWalkFeed.lastYears(1) - val f2 = RandomWalkFeed.lastYears(2) + val f1 = RandomWalk.lastYears(1) + val f2 = RandomWalk.lastYears(2) val cf = CombinedFeed(f1, f2) assertTrue { cf.timeframe == f2.timeframe } var cnt = 0 @@ -46,8 +46,8 @@ internal class CombinedFeedTest { @Test fun testCombinedFeed4() = runBlocking { - val f1 = RandomWalkFeed.lastYears(1) - val f2 = RandomWalkFeed.lastYears(2) + val f1 = RandomWalk.lastYears(1) + val f2 = RandomWalk.lastYears(2) val cf = CombinedFeed(f1, f2, channelCapacity = 10) assertTrue { cf.timeframe == f2.timeframe } var cnt = 0 @@ -63,8 +63,8 @@ internal class CombinedFeedTest { @Test fun testCombinedFeed5() = runBlocking { - val f1 = RandomWalkFeed.lastYears(1) - val f2 = RandomWalkFeed.lastYears(2) + val f1 = RandomWalk.lastYears(1) + val f2 = RandomWalk.lastYears(2) val cf = CombinedFeed(f1, f2) feedTest(cf) } diff --git a/roboquant/src/test/kotlin/org/roboquant/feeds/HistoricFeedTest.kt b/roboquant/src/test/kotlin/org/roboquant/feeds/HistoricFeedTest.kt index 55d2ccfa..35977737 100644 --- a/roboquant/src/test/kotlin/org/roboquant/feeds/HistoricFeedTest.kt +++ b/roboquant/src/test/kotlin/org/roboquant/feeds/HistoricFeedTest.kt @@ -21,7 +21,7 @@ import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import org.roboquant.TestData import org.roboquant.common.* -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.feeds.util.HistoricTestFeed import java.time.Instant import java.util.* @@ -31,7 +31,7 @@ internal class HistoricFeedTest { @Test fun test() { - val feed = RandomWalkFeed.lastYears() + val feed = RandomWalk.lastYears() val tfs = feed.timeframe.split(1.months) assertEquals(12, tfs.size) assertEquals(10, feed.assets.size) diff --git a/roboquant/src/test/kotlin/org/roboquant/feeds/LiveFeedTest.kt b/roboquant/src/test/kotlin/org/roboquant/feeds/LiveFeedTest.kt index df1d9b9f..27e6e8c1 100644 --- a/roboquant/src/test/kotlin/org/roboquant/feeds/LiveFeedTest.kt +++ b/roboquant/src/test/kotlin/org/roboquant/feeds/LiveFeedTest.kt @@ -21,7 +21,7 @@ import org.junit.jupiter.api.assertDoesNotThrow import org.roboquant.common.* import org.roboquant.journals.MemoryJournal import org.roboquant.metrics.ProgressMetric -import org.roboquant.strategies.EMAStrategy +import org.roboquant.strategies.EMACrossover import java.time.Instant import java.time.temporal.ChronoUnit import kotlin.test.Test @@ -87,7 +87,7 @@ internal class LiveFeedTest { tf.sample(200.millis, 10, resolution = ChronoUnit.MILLIS).forEach { jobs.add { val j = MemoryJournal(ProgressMetric()) - org.roboquant.runAsync(feed, EMAStrategy(), j, it) + org.roboquant.runAsync(feed, EMACrossover(), j, it) val actions = j.getMetric("progress.actions").values.last() assertTrue(actions > 2) } diff --git a/roboquant/src/test/kotlin/org/roboquant/feeds/random/RandomWalkFeedTest.kt b/roboquant/src/test/kotlin/org/roboquant/feeds/random/RandomWalkTest.kt similarity index 87% rename from roboquant/src/test/kotlin/org/roboquant/feeds/random/RandomWalkFeedTest.kt rename to roboquant/src/test/kotlin/org/roboquant/feeds/random/RandomWalkTest.kt index dba5d962..6341997a 100644 --- a/roboquant/src/test/kotlin/org/roboquant/feeds/random/RandomWalkFeedTest.kt +++ b/roboquant/src/test/kotlin/org/roboquant/feeds/random/RandomWalkTest.kt @@ -28,11 +28,11 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -internal class RandomWalkFeedTest { +internal class RandomWalkTest { @Test fun randomly() = runBlocking { - val feed = RandomWalkFeed.lastYears(1, 20) + val feed = RandomWalk.lastYears(1, 20) var cnt = 0 var now = Instant.MIN for (step in play(feed)) { @@ -46,26 +46,26 @@ internal class RandomWalkFeedTest { @Test fun itemTypes() = runBlocking { - val feed = RandomWalkFeed.lastYears(priceType = PriceItemType.TRADE) + val feed = RandomWalk.lastYears(priceType = PriceItemType.TRADE) val event = play(feed).receive() assertTrue(event.items.first() is TradePrice) val tl = Timeframe.fromYears(2010, 2012) - val feed2 = RandomWalkFeed(tl, priceType = PriceItemType.BAR) + val feed2 = RandomWalk(tl, priceType = PriceItemType.BAR) val item2 = play(feed2).receive() assertTrue(item2.items.first() is PriceBar) } @Test fun historic() { - val feed = RandomWalkFeed.lastYears() + val feed = RandomWalk.lastYears() val tf2 = feed.timeframe.split(50.days) assertTrue(tf2.isNotEmpty()) } @Test fun toList() { - val feed = RandomWalkFeed.lastYears() + val feed = RandomWalk.lastYears() val list = feed.toList() assertTrue(list.isNotEmpty()) assertEquals(list.size, feed.toList().size) @@ -74,7 +74,7 @@ internal class RandomWalkFeedTest { @Test fun reproducable() { val timeline = Timeframe.fromYears(2000, 2001) - val feed = RandomWalkFeed(timeline, seed = 10) + val feed = RandomWalk(timeline, seed = 10) val symbol = feed.assets.first().symbol val result1 = feed.filter { it.asset.symbol == symbol } @@ -88,7 +88,7 @@ internal class RandomWalkFeedTest { @Test fun filter() { - val feed = RandomWalkFeed.lastYears() + val feed = RandomWalk.lastYears() val asset = feed.assets.first() val result = feed.filter { it.asset == asset } assertEquals(feed.timeline.size, result.size) diff --git a/roboquant/src/test/kotlin/org/roboquant/journals/MultiRunJournalTest.kt b/roboquant/src/test/kotlin/org/roboquant/journals/MultiRunJournalTest.kt index fa9962a9..1d3ab67a 100644 --- a/roboquant/src/test/kotlin/org/roboquant/journals/MultiRunJournalTest.kt +++ b/roboquant/src/test/kotlin/org/roboquant/journals/MultiRunJournalTest.kt @@ -2,9 +2,9 @@ package org.roboquant.journals import org.roboquant.common.years import org.roboquant.run -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.metrics.AccountMetric -import org.roboquant.strategies.EMAStrategy +import org.roboquant.strategies.EMACrossover import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertEquals @@ -14,13 +14,13 @@ internal class MultiRunJournalTest { @Test fun basic() { - val feed = RandomWalkFeed.lastYears(5) + val feed = RandomWalk.lastYears(5) val mrj = MultiRunJournal { MemoryJournal(AccountMetric()) } val tfs = feed.timeframe.split(1.years) for (tf in tfs) { - run(feed, EMAStrategy(), mrj.getJournal(), timeframe=tf) + run(feed, EMACrossover(), mrj.getJournal(), timeframe=tf) } assertContains(mrj.getMetricNames(), "account.equity") assertEquals(tfs.size, mrj.getRuns().size) diff --git a/roboquant/src/test/kotlin/org/roboquant/metrics/AlphaBetaMetricTest.kt b/roboquant/src/test/kotlin/org/roboquant/metrics/AlphaBetaMetricTest.kt index 0dc5e491..e52e227d 100644 --- a/roboquant/src/test/kotlin/org/roboquant/metrics/AlphaBetaMetricTest.kt +++ b/roboquant/src/test/kotlin/org/roboquant/metrics/AlphaBetaMetricTest.kt @@ -21,10 +21,10 @@ import org.roboquant.brokers.Position import org.roboquant.brokers.sim.execution.InternalAccount import org.roboquant.common.Currency import org.roboquant.common.Size -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.feeds.toList import org.roboquant.journals.MemoryJournal -import org.roboquant.strategies.EMAStrategy +import org.roboquant.strategies.EMACrossover import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertTrue @@ -34,7 +34,7 @@ internal class AlphaBetaMetricTest { @Test fun test() { val feed = TestData.feed - val strategy = EMAStrategy.PERIODS_5_15 + val strategy = EMACrossover.PERIODS_5_15 val alphaBetaMetric = AlphaBetaMetric(50) val logger = MemoryJournal(alphaBetaMetric) org.roboquant.run(feed, strategy,logger) @@ -48,7 +48,7 @@ internal class AlphaBetaMetricTest { @Test fun test2() { - val feed = RandomWalkFeed.lastYears(1, nAssets = 5) + val feed = RandomWalk.lastYears(1, nAssets = 5) val asset = feed.assets.first() val internalAccount = InternalAccount(Currency.USD) val metric = AlphaBetaMetric(50) diff --git a/roboquant/src/test/kotlin/org/roboquant/metrics/ReturnsMetricTest.kt b/roboquant/src/test/kotlin/org/roboquant/metrics/ReturnsMetricTest.kt index 86559694..dc9bac1a 100644 --- a/roboquant/src/test/kotlin/org/roboquant/metrics/ReturnsMetricTest.kt +++ b/roboquant/src/test/kotlin/org/roboquant/metrics/ReturnsMetricTest.kt @@ -18,10 +18,10 @@ package org.roboquant.metrics import org.roboquant.TestData import org.roboquant.common.months -import org.roboquant.feeds.random.RandomWalkFeed +import org.roboquant.feeds.random.RandomWalk import org.roboquant.feeds.util.HistoricTestFeed import org.roboquant.journals.MemoryJournal -import org.roboquant.strategies.EMAStrategy +import org.roboquant.strategies.EMACrossover import org.roboquant.strategies.TestStrategy import kotlin.test.Test import kotlin.test.assertContains @@ -40,9 +40,9 @@ internal class ReturnsMetricTest { @Test fun basic2() { val metric = ReturnsMetric2(minSize = 250) - val feed = RandomWalkFeed.lastYears(2) + val feed = RandomWalk.lastYears(2) val j = MemoryJournal(metric) - org.roboquant.run(feed, EMAStrategy(), j) + org.roboquant.run(feed, EMACrossover(), j) assertContains(j.getMetricNames(), "returns.sharperatio") } diff --git a/roboquant/src/test/kotlin/org/roboquant/strategies/EMAStrategyTest.kt b/roboquant/src/test/kotlin/org/roboquant/strategies/EMACrossoverTest.kt similarity index 82% rename from roboquant/src/test/kotlin/org/roboquant/strategies/EMAStrategyTest.kt rename to roboquant/src/test/kotlin/org/roboquant/strategies/EMACrossoverTest.kt index fad3c0e5..e580f44d 100644 --- a/roboquant/src/test/kotlin/org/roboquant/strategies/EMAStrategyTest.kt +++ b/roboquant/src/test/kotlin/org/roboquant/strategies/EMACrossoverTest.kt @@ -22,20 +22,20 @@ import org.roboquant.TestData import kotlin.test.Test import kotlin.test.assertNotEquals -internal class EMAStrategyTest { +internal class EMACrossoverTest { @Test fun simpleTest() = runBlocking { - val strategy = EMAStrategy() + val strategy = EMACrossover() val roboquant = Roboquant(strategy) roboquant.run(TestData.feed) } @Test fun simple2() { - val strategy1 = EMAStrategy.PERIODS_5_15 - val strategy2 = EMAStrategy.PERIODS_12_26 - val strategy3 = EMAStrategy.PERIODS_50_200 + val strategy1 = EMACrossover.PERIODS_5_15 + val strategy2 = EMACrossover.PERIODS_12_26 + val strategy3 = EMACrossover.PERIODS_50_200 assertNotEquals(strategy1, strategy2) assertNotEquals(strategy2, strategy3) }