Skip to content

Commit

Permalink
Implement stock download from Stooq. #3
Browse files Browse the repository at this point in the history
  • Loading branch information
slomkowski committed Oct 3, 2020
1 parent 357e1a7 commit c858f1b
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 213 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.github.kittinunf.fuel.core.ResponseDeserializable
import com.uchuhimo.konf.Config
import eu.slomkowski.octoglow.octoglowd.hardware.Hardware
import io.dvlopt.linux.i2c.I2CBuffer
import kotlinx.coroutines.delay
import mu.KLogger
import org.apache.commons.csv.CSVFormat
import org.apache.commons.csv.CSVParser
import org.apache.commons.csv.CSVRecord
import org.shredzone.commons.suncalc.SunTimes
import java.io.InputStream
import java.io.*
import java.nio.charset.StandardCharsets
import java.time.*
import java.util.*
Expand Down Expand Up @@ -132,3 +136,13 @@ fun I2CBuffer.contentToString(): String = (0 until this.length).map { this[it] }
fun I2CBuffer.toList(): List<Int> = (0 until this.length).map { this[it] }.toList()

fun InputStream.readToString(): String = this.bufferedReader(StandardCharsets.UTF_8).readText()

inline fun <reified T : Any> csvDeserializerOf(csvFormat: CSVFormat = CSVFormat.DEFAULT, crossinline recordMappingFunction: (CSVRecord) -> T) = object : ResponseDeserializable<List<T>> {
override fun deserialize(reader: Reader): List<T> = CSVParser(reader, csvFormat).records.map(recordMappingFunction)

override fun deserialize(content: String): List<T> = StringReader(content).use { deserialize(it) }

override fun deserialize(bytes: ByteArray): List<T> = ByteArrayInputStream(bytes).use { deserialize(it) }

override fun deserialize(inputStream: InputStream): List<T> = InputStreamReader(inputStream, StandardCharsets.UTF_8).use { deserialize(it) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package eu.slomkowski.octoglow.octoglowd.daemon.frontdisplay

import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.coroutines.awaitObject
import com.uchuhimo.konf.Config
import eu.slomkowski.octoglow.octoglowd.csvDeserializerOf
import eu.slomkowski.octoglow.octoglowd.hardware.Hardware
import kotlinx.coroutines.coroutineScope
import mu.KLogging
import org.apache.commons.csv.CSVFormat
import java.math.BigDecimal
import java.time.Duration
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import java.util.*

class StockView(
private val config: Config,
hardware: Hardware)
: FrontDisplayView(hardware,
"Warsaw Stock Exchange index",
Duration.ofMinutes(15),
Duration.ofSeconds(15),
Duration.ofSeconds(13)) {


data class StockInfoDto(
val ticker: String,
val interval: Duration,
val timestamp: LocalDateTime,
val open: BigDecimal,
val high: BigDecimal,
val low: BigDecimal,
val close: BigDecimal,
val volume: Int,
val openInt: Int) {
init {
require(ticker.isNotBlank())
require(interval > Duration.ZERO)
require(high >= low)
require(volume >= 0)
}
}

companion object : KLogging() {
private val cookie: String = UUID.randomUUID().toString()

private val shortTimeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("HHmmss")

private const val STOOQ_URL = "https://stooq.pl/db/d/"

suspend fun downloadStockData(date: LocalDate): List<StockInfoDto> {
val parameters = listOf(
"t" to "h",
"d" to date.format(DateTimeFormatter.BASIC_ISO_DATE),
"u" to cookie)

logger.debug("Downloading stock data for $date from stooq.pl.")

return Fuel.get(STOOQ_URL, parameters).awaitObject(csvDeserializerOf(CSVFormat.DEFAULT.withFirstRecordAsHeader()) { record ->
val rowDate = LocalDate.parse(record[2], DateTimeFormatter.BASIC_ISO_DATE)
val time = LocalTime.parse(record[3], shortTimeFormatter)
StockInfoDto(
record.get(0),
Duration.ofMinutes(record[1].toLong()),
LocalDateTime.of(rowDate, time),
record[4].toBigDecimal(),
record[5].toBigDecimal(),
record[6].toBigDecimal(),
record[7].toBigDecimal(),
record[8].toInt(),
record[9].toInt())
})
}
}

override suspend fun redrawDisplay(redrawStatic: Boolean, redrawStatus: Boolean) = coroutineScope {
TODO()

Unit
}

/**
* Progress bar is dependent only on current time so always success.
*/
override suspend fun poolInstantData(): UpdateStatus = UpdateStatus.FULL_SUCCESS

override suspend fun poolStatusData(): UpdateStatus = coroutineScope {
TODO()
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package eu.slomkowski.octoglow.octoglowd.daemon.frontdisplay

import kotlinx.coroutines.runBlocking
import mu.KLogging
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.time.DayOfWeek
import java.time.LocalDate

internal class StockViewTest {

companion object : KLogging()

@Test
fun testDownloadStockData() {
val lastWeekDay = checkNotNull(object : Iterator<LocalDate> {
var d = LocalDate.now().minusDays(7)

override fun hasNext() = d <= LocalDate.now()

override fun next(): LocalDate {
d = d.plusDays(1)
return d
}
}.asSequence().findLast { it.dayOfWeek !in setOf(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY) })

val stockData = runBlocking { StockView.downloadStockData(lastWeekDay) }
assertTrue(stockData.isNotEmpty())
logger.info("Downloaded {} stocks.", stockData.size)
}
}

0 comments on commit c858f1b

Please sign in to comment.