diff --git a/src/main/kotlin/com/option/domain/collection/entity/RawFile.kt b/src/main/kotlin/com/option/domain/collection/entity/RawFile.kt new file mode 100644 index 0000000..af3a1d5 --- /dev/null +++ b/src/main/kotlin/com/option/domain/collection/entity/RawFile.kt @@ -0,0 +1,51 @@ +package com.option.domain.collection.entity + +import com.option.global.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table +import java.time.Instant +import java.time.LocalDate + +// 원본파일 위치 저장용 +@Entity +@Table(name = "raw_file") +class RawFile( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "raw_file_id") + val rawFileId: Long? = null, + + @Column(nullable = false) + val job: String, + + @Column(name = "session_date", nullable = false) + val sessionDate: LocalDate, + + @Column(nullable = false) + val endpoint: String, + + @Column(nullable = false) + val symbol: String, + + @Column(nullable = false) + val page: Int, + + @Column(nullable = false, length = 400) + val path: String, + + @Column(nullable = false) + val sha256: String, + + @Column(nullable = false) + val bytes: Long, + + @Column(name = "request_params", columnDefinition = "text") + val requestParams: String?, + + @Column(name = "received_at", nullable = false) + val receivedAt: Instant, +) : BaseEntity() diff --git a/src/main/kotlin/com/option/domain/collection/vo/CollectionEnums.kt b/src/main/kotlin/com/option/domain/collection/vo/CollectionEnums.kt new file mode 100644 index 0000000..4eec839 --- /dev/null +++ b/src/main/kotlin/com/option/domain/collection/vo/CollectionEnums.kt @@ -0,0 +1,6 @@ +package com.option.domain.collection.vo + +// 디스크가 모자랄 때 멈추는 기준이다. 백필이 먼저 멈추고, 그다음 본 수집이 멈춘다. 계산과 채점은 안 멈춘다. +enum class DiskRule { BACKGROUND, COLLECTION, NONE } + +enum class FailureCategory { TIMEOUT, NETWORK, RATE_LIMIT, SERVER_ERROR, CLIENT_ERROR, PARSE, DB_CONSTRAINT, DISK, OTHER } diff --git a/src/main/kotlin/com/option/domain/journal/entity/JournalEntry.kt b/src/main/kotlin/com/option/domain/journal/entity/JournalEntry.kt new file mode 100644 index 0000000..ba2d8ee --- /dev/null +++ b/src/main/kotlin/com/option/domain/journal/entity/JournalEntry.kt @@ -0,0 +1,39 @@ +package com.option.domain.journal.entity + +import com.option.domain.journal.vo.JournalDirection +import com.option.global.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint +import java.time.LocalDate + +// 옵션 보고 상방하방 맞추는 용도, 종목당 하나 +@Entity +@Table( + name = "journal_entry", + uniqueConstraints = [UniqueConstraint(name = "uk_journal", columnNames = ["symbol", "target_session"])], +) +class JournalEntry( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(nullable = false) + val symbol: String, + + @Column(name = "target_session", nullable = false) + val targetSession: LocalDate, + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + var direction: JournalDirection, + + @Column(length = 500) + var memo: String?, +) : BaseEntity() diff --git a/src/main/kotlin/com/option/domain/journal/vo/JournalDirection.kt b/src/main/kotlin/com/option/domain/journal/vo/JournalDirection.kt new file mode 100644 index 0000000..6df222b --- /dev/null +++ b/src/main/kotlin/com/option/domain/journal/vo/JournalDirection.kt @@ -0,0 +1,8 @@ +package com.option.domain.journal.vo + +enum class JournalDirection { + UP, + DOWN, + FLAT, + HOLD +} diff --git a/src/main/kotlin/com/option/domain/market/entity/EventCalendar.kt b/src/main/kotlin/com/option/domain/market/entity/EventCalendar.kt new file mode 100644 index 0000000..b503df5 --- /dev/null +++ b/src/main/kotlin/com/option/domain/market/entity/EventCalendar.kt @@ -0,0 +1,37 @@ +package com.option.domain.market.entity + +import com.option.domain.market.vo.EventType +import com.option.global.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table +import java.time.LocalDate + +// 시장 이벤트 +@Entity +@Table(name = "event_calendar") +class EventCalendar( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(name = "event_date", nullable = false) + val eventDate: LocalDate, + + @Enumerated(EnumType.STRING) + @Column(name = "event_type", nullable = false) + val eventType: EventType, + + @Column(nullable = false) + val symbol: String, + + val title: String?, + + @Column(nullable = false) + val source: String, +) : BaseEntity() diff --git a/src/main/kotlin/com/option/domain/market/entity/Instrument.kt b/src/main/kotlin/com/option/domain/market/entity/Instrument.kt new file mode 100644 index 0000000..b113d6c --- /dev/null +++ b/src/main/kotlin/com/option/domain/market/entity/Instrument.kt @@ -0,0 +1,37 @@ +package com.option.domain.market.entity + +import com.option.domain.market.vo.SymbolGroup +import com.option.global.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table +import java.time.LocalDate + +// 종목 +@Entity +@Table(name = "instrument") +class Instrument( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(nullable = false, unique = true) + val symbol: String, + + var name: String?, + + @Enumerated(EnumType.STRING) + @Column(name = "symbol_group", nullable = false) + var symbolGroup: SymbolGroup, + + @Column(name = "in_sp500", nullable = false) + var inSp500: Boolean, + + @Column(name = "as_of_date") + var asOfDate: LocalDate?, +) : BaseEntity() diff --git a/src/main/kotlin/com/option/domain/market/entity/MacroDaily.kt b/src/main/kotlin/com/option/domain/market/entity/MacroDaily.kt new file mode 100644 index 0000000..455f6a0 --- /dev/null +++ b/src/main/kotlin/com/option/domain/market/entity/MacroDaily.kt @@ -0,0 +1,36 @@ +package com.option.domain.market.entity + +import com.option.global.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table +import java.time.Instant +import java.time.LocalDate + + +// vix나 하이일드 스프레드, 국채금리 등 매크로 +@Entity +@Table(name = "macro_daily") +class MacroDaily( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(nullable = false) + val series: String, + + @Column(name = "obs_date", nullable = false) + val obsDate: LocalDate, + + @Column(nullable = false) + val value: Double, + + @Column(nullable = false) + val provider: String, + + @Column(name = "received_at", nullable = false) + val receivedAt: Instant, +) : BaseEntity() diff --git a/src/main/kotlin/com/option/domain/market/entity/MarketSession.kt b/src/main/kotlin/com/option/domain/market/entity/MarketSession.kt new file mode 100644 index 0000000..2efdb1d --- /dev/null +++ b/src/main/kotlin/com/option/domain/market/entity/MarketSession.kt @@ -0,0 +1,24 @@ +package com.option.domain.market.entity + +import com.option.global.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Table +import java.time.Instant +import java.time.LocalDate + +// 거래일 +@Entity +@Table(name = "market_session") +class MarketSession( + @Id + @Column(name = "session_date") + val sessionDate: LocalDate, + + @Column(name = "open_at", nullable = false) + val openAt: Instant, + + @Column(name = "close_at", nullable = false) + val closeAt: Instant, +) : BaseEntity() diff --git a/src/main/kotlin/com/option/domain/market/entity/OptionDaily.kt b/src/main/kotlin/com/option/domain/market/entity/OptionDaily.kt new file mode 100644 index 0000000..da9e55a --- /dev/null +++ b/src/main/kotlin/com/option/domain/market/entity/OptionDaily.kt @@ -0,0 +1,105 @@ +package com.option.domain.market.entity + +import com.option.domain.market.vo.OptionType +import com.option.global.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table +import java.time.Instant +import java.time.LocalDate + +// 종목 각각에서 체결된 계약 하나 +@Entity +@Table(name = "option_daily") +class OptionDaily( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(name = "contract_symbol", nullable = false) + val contractSymbol: String, + + @Column(nullable = false) + val underlying: String, + + @Column(name = "expiration_date", nullable = false) + val expirationDate: LocalDate, + + @Column(nullable = false) + val strike: Double, + + @Enumerated(EnumType.STRING) + @Column(name = "option_type", nullable = false) + val optionType: OptionType, + + @Column(name = "session_date", nullable = false) + val sessionDate: LocalDate, + + @Column(nullable = false) + val provider: String, + + @Column(nullable = false) + val feed: String, + + @Column(name = "received_at", nullable = false) + val receivedAt: Instant, + + @Column(name = "bar_date") + val barDate: LocalDate? = null, + + @Column(name = "open_price") + val openPrice: Double? = null, + + @Column(name = "high_price") + val highPrice: Double? = null, + + @Column(name = "low_price") + val lowPrice: Double? = null, + + @Column(name = "close_price") + val closePrice: Double? = null, + + val volume: Long? = null, + + @Column(name = "trade_count") + val tradeCount: Long? = null, + + val vwap: Double? = null, + + val bid: Double? = null, + + val ask: Double? = null, + + @Column(name = "last_price") + val lastPrice: Double? = null, + + @Column(name = "implied_vol") + val impliedVol: Double? = null, + + val delta: Double? = null, + + val gamma: Double? = null, + + val theta: Double? = null, + + val vega: Double? = null, + + val rho: Double? = null, + + @Column(name = "price_as_of") + val priceAsOf: Instant? = null, + + @Column(name = "open_interest") + var openInterest: Long? = null, + + @Column(name = "oi_as_of_date") + var oiAsOfDate: LocalDate? = null, + + @Column(name = "oi_delay_days") + var oiDelayDays: Int? = null, +) : BaseEntity() diff --git a/src/main/kotlin/com/option/domain/market/entity/StockDaily.kt b/src/main/kotlin/com/option/domain/market/entity/StockDaily.kt new file mode 100644 index 0000000..30c7670 --- /dev/null +++ b/src/main/kotlin/com/option/domain/market/entity/StockDaily.kt @@ -0,0 +1,55 @@ +package com.option.domain.market.entity + +import com.option.global.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table +import java.time.Instant +import java.time.LocalDate + +// 일일 주가 +@Entity +@Table(name = "stock_daily") +class StockDaily( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(nullable = false) + val symbol: String, + + @Column(name = "session_date", nullable = false) + val sessionDate: LocalDate, + + @Column(nullable = false) + val feed: String, + + @Column(name = "open_price", nullable = false) + val openPrice: Double, + + @Column(name = "high_price", nullable = false) + val highPrice: Double, + + @Column(name = "low_price", nullable = false) + val lowPrice: Double, + + @Column(name = "close_raw", nullable = false) + val closeRaw: Double, + + @Column(name = "close_adj", nullable = false) + var closeAdj: Double, + + @Column(nullable = false) + val volume: Long, + + @Column(name = "trade_count") + val tradeCount: Long?, + + val vwap: Double?, + + @Column(name = "received_at", nullable = false) + val receivedAt: Instant, +) : BaseEntity() diff --git a/src/main/kotlin/com/option/domain/market/vo/MarketEnums.kt b/src/main/kotlin/com/option/domain/market/vo/MarketEnums.kt new file mode 100644 index 0000000..62b5862 --- /dev/null +++ b/src/main/kotlin/com/option/domain/market/vo/MarketEnums.kt @@ -0,0 +1,21 @@ +package com.option.domain.market.vo + +enum class OptionType { CALL, PUT } + +enum class SymbolGroup { INDEX_ETF, SECTOR_ETF, OTHER_ETF, STOCK } + +enum class EventType { EARNINGS, FOMC, CPI, JOBS } + +object Feeds { // 데이터 출처 + const val PROVIDER_ALPACA = "alpaca" + const val INDICATIVE = "indicative" + const val OPRA = "opra" + const val SIP = "sip" +} + +object MacroSeries { // 매크로 관련 + const val VIX = "VIXCLS" + const val HY_SPREAD = "BAMLH0A0HYM2" + const val TBILL_3M = "DTB3" + const val CBOE_TOTAL_PCR = "CBOE_TOTAL_PCR" +} diff --git a/src/main/kotlin/com/option/domain/model/entity/HypothesisSignal.kt b/src/main/kotlin/com/option/domain/model/entity/HypothesisSignal.kt new file mode 100644 index 0000000..846d21c --- /dev/null +++ b/src/main/kotlin/com/option/domain/model/entity/HypothesisSignal.kt @@ -0,0 +1,50 @@ +package com.option.domain.model.entity + +import com.option.domain.model.vo.Horizon +import com.option.domain.model.vo.Signal +import com.option.global.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint +import java.time.LocalDate + +// 이론별로 상승 하강 예측 +@Entity +@Table( + name = "hypothesis_signal", + uniqueConstraints = [UniqueConstraint(name = "uk_signal", columnNames = ["publication_id", "symbol", "hypothesis", "horizon"])], +) +class HypothesisSignal( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(name = "publication_id", nullable = false) + val publicationId: Long, + + @Column(nullable = false) + val symbol: String, + + @Column(nullable = false) + val hypothesis: String, + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + val horizon: Horizon, + + @Column(name = "target_session", nullable = false) + val targetSession: LocalDate, + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + val signal: Signal, + + @Column(name = "input_value") + val inputValue: Double?, +) : BaseEntity() diff --git a/src/main/kotlin/com/option/domain/model/entity/MarketSentiment.kt b/src/main/kotlin/com/option/domain/model/entity/MarketSentiment.kt new file mode 100644 index 0000000..f197125 --- /dev/null +++ b/src/main/kotlin/com/option/domain/model/entity/MarketSentiment.kt @@ -0,0 +1,36 @@ +package com.option.domain.model.entity + +import com.option.domain.model.vo.SentimentComponent +import com.option.global.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table +import org.hibernate.annotations.JdbcTypeCode +import org.hibernate.type.SqlTypes + +// 시장 심리지수 +@Entity +@Table(name = "market_sentiment") +class MarketSentiment( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(name = "publication_id", nullable = false, unique = true) + val publicationId: Long, + + val score: Double?, + + @Column(name = "used_bitset", nullable = false) + val usedBitset: Int, + + @Column(name = "used_count", nullable = false) + val usedCount: Int, + + @JdbcTypeCode(SqlTypes.JSON) + @Column(columnDefinition = "jsonb", nullable = false) + val components: List, +) : BaseEntity() diff --git a/src/main/kotlin/com/option/domain/model/entity/Publication.kt b/src/main/kotlin/com/option/domain/model/entity/Publication.kt new file mode 100644 index 0000000..617c0a8 --- /dev/null +++ b/src/main/kotlin/com/option/domain/model/entity/Publication.kt @@ -0,0 +1,37 @@ +package com.option.domain.model.entity + +import com.option.domain.model.vo.PublicationStatus +import com.option.global.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table +import java.time.Instant +import java.time.LocalDate + +// 화면에 띄우는 결과물들 +@Entity +@Table(name = "publication") +class Publication( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "publication_id") + val publicationId: Long? = null, + + @Column(name = "session_date", nullable = false) + val sessionDate: LocalDate, + + @Column(name = "model_version", nullable = false) + val modelVersion: String, + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + var status: PublicationStatus, + + @Column(name = "published_at") + var publishedAt: Instant? = null, +) : BaseEntity() diff --git a/src/main/kotlin/com/option/domain/model/entity/Score.kt b/src/main/kotlin/com/option/domain/model/entity/Score.kt new file mode 100644 index 0000000..badde23 --- /dev/null +++ b/src/main/kotlin/com/option/domain/model/entity/Score.kt @@ -0,0 +1,77 @@ +package com.option.domain.model.entity + +import com.option.domain.market.vo.SymbolGroup +import com.option.domain.model.vo.Horizon +import com.option.domain.model.vo.IvSource +import com.option.domain.model.vo.Signal +import com.option.global.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table +import java.time.LocalDate + +// 예측 점수 +@Entity +@Table(name = "score") +class Score( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(name = "publication_id") + val publicationId: Long?, + + @Column(nullable = false) + val symbol: String, + + @Enumerated(EnumType.STRING) + @Column(name = "symbol_group", nullable = false) + val symbolGroup: SymbolGroup, + + @Column(nullable = false) + val subject: String, + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + val horizon: Horizon, + + @Enumerated(EnumType.STRING) + @Column(name = "iv_source", nullable = false) + val ivSource: IvSource, + + @Column(name = "base_session", nullable = false) + val baseSession: LocalDate, + + @Column(name = "target_session", nullable = false) + val targetSession: LocalDate, + + @Column(name = "issued_weekday", nullable = false) + val issuedWeekday: Int, + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + val prediction: Signal, + + @Column(name = "event_day", nullable = false) + val eventDay: Boolean, + + @Column(name = "actual_return") + val actualReturn: Double? = null, + + val hit: Boolean? = null, + + val width: Double? = null, + + @Column(name = "outside_error") + val outsideError: Double? = null, + + val distance: Double? = null, + + @Column(name = "range_pct") + val rangePct: Double? = null, +) : BaseEntity() diff --git a/src/main/kotlin/com/option/domain/model/entity/TickerDailyMetric.kt b/src/main/kotlin/com/option/domain/model/entity/TickerDailyMetric.kt new file mode 100644 index 0000000..e17c5b6 --- /dev/null +++ b/src/main/kotlin/com/option/domain/model/entity/TickerDailyMetric.kt @@ -0,0 +1,156 @@ +package com.option.domain.model.entity + +import com.option.domain.model.vo.GexDetail +import com.option.global.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint +import org.hibernate.annotations.JdbcTypeCode +import org.hibernate.type.SqlTypes +import java.time.LocalDate + +// 종목별 계산 결과 +@Entity +@Table( + name = "ticker_daily_metric", + uniqueConstraints = [UniqueConstraint(name = "uk_metric", columnNames = ["publication_id", "symbol"])], +) +class TickerDailyMetric( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(name = "publication_id", nullable = false) + val publicationId: Long, + + @Column(nullable = false) + val symbol: String, + + @Column(name = "base_session", nullable = false) + val baseSession: LocalDate, + + @Column(name = "target_session", nullable = false) + val targetSession: LocalDate, + + @Column(name = "week_target_session", nullable = false) + val weekTargetSession: LocalDate, + + @Column(name = "base_close") + val baseClose: Double? = null, + + @Column(name = "oi_as_of_date") + val oiAsOfDate: LocalDate? = null, + + @Column(name = "oi_delay_days") + val oiDelayDays: Int? = null, + + @Column(name = "day_iv") + val dayIv: Double? = null, + + @Column(name = "day_iv_expiry") + val dayIvExpiry: LocalDate? = null, + + @Column(name = "day_iv_fallback") + val dayIvFallback: Boolean? = null, + + @Column(name = "day_width") + val dayWidth: Double? = null, + + @Column(name = "week_iv") + val weekIv: Double? = null, + + @Column(name = "week_iv_expiry") + val weekIvExpiry: LocalDate? = null, + + @Column(name = "week_iv_fallback") + val weekIvFallback: Boolean? = null, + + @Column(name = "week_width") + val weekWidth: Double? = null, + + @Column(name = "day_rv_width") + val dayRvWidth: Double? = null, + + @Column(name = "week_rv_width") + val weekRvWidth: Double? = null, + + @Column(name = "maxpain_expiry") + val maxpainExpiry: LocalDate? = null, + + @Column(name = "maxpain_strike") + val maxpainStrike: Double? = null, + + @Column(name = "maxpain_nearest_strike") + val maxpainNearestStrike: Double? = null, + + @Column(name = "call_wall_strike") + val callWallStrike: Double? = null, + + @Column(name = "put_wall_strike") + val putWallStrike: Double? = null, + + @Column(name = "monthly_expiry") + val monthlyExpiry: LocalDate? = null, + + @Column(name = "monthly_maxpain_strike") + val monthlyMaxpainStrike: Double? = null, + + @Column(name = "monthly_call_wall_strike") + val monthlyCallWallStrike: Double? = null, + + @Column(name = "monthly_put_wall_strike") + val monthlyPutWallStrike: Double? = null, + + @Column(name = "gex_day") + val gexDay: Double? = null, + + @Column(name = "gex_week") + val gexWeek: Double? = null, + + @Column(name = "gex_all") + val gexAll: Double? = null, + + @Column(name = "gex_contracts") + val gexContracts: Int? = null, + + @Column(name = "gex_contract_missing_rate") + val gexContractMissingRate: Double? = null, + + @Column(name = "gex_oi_missing_rate") + val gexOiMissingRate: Double? = null, + + @Column(name = "gex_atm_missing") + val gexAtmMissing: Boolean? = null, + + @Column(name = "gex_official", nullable = false) + val gexOfficial: Boolean = false, + + @JdbcTypeCode(SqlTypes.JSON) + @Column(name = "gex_detail", columnDefinition = "jsonb") + val gexDetail: GexDetail? = null, + + @Column(name = "pcr_volume") + val pcrVolume: Double? = null, + + @Column(name = "pcr_oi") + val pcrOi: Double? = null, + + @Column(name = "pcr_volume_week") + val pcrVolumeWeek: Double? = null, + + @Column(name = "pcr_oi_week") + val pcrOiWeek: Double? = null, + + @Column(name = "pcr_percentile") + val pcrPercentile: Double? = null, + + @Column(name = "day_event", nullable = false) + val dayEvent: Boolean = false, + + @Column(name = "week_event", nullable = false) + val weekEvent: Boolean = false, +) : BaseEntity() diff --git a/src/main/kotlin/com/option/domain/model/vo/GexDetail.kt b/src/main/kotlin/com/option/domain/model/vo/GexDetail.kt new file mode 100644 index 0000000..bcf11a8 --- /dev/null +++ b/src/main/kotlin/com/option/domain/model/vo/GexDetail.kt @@ -0,0 +1,11 @@ +package com.option.domain.model.vo + +data class ExpiryQuality(val contracts: Int, val missingContracts: Int, val oiMissingRate: Double) + +// 화면 차트와 결측 분석에 쓰는 값, 행사가가 너무 많아 그냥 josnb로 저장 +data class GexDetail( + val byStrike: Map = emptyMap(), + val byExpiry: Map = emptyMap(), + val callOiMissingRate: Double? = null, + val putOiMissingRate: Double? = null, +) diff --git a/src/main/kotlin/com/option/domain/model/vo/ModelEnums.kt b/src/main/kotlin/com/option/domain/model/vo/ModelEnums.kt new file mode 100644 index 0000000..347ec4b --- /dev/null +++ b/src/main/kotlin/com/option/domain/model/vo/ModelEnums.kt @@ -0,0 +1,34 @@ +package com.option.domain.model.vo + +enum class PublicationStatus { COMPUTING, COMPUTED, PUBLISHED, BACKFILL } + +enum class Horizon { NEXT_DAY, WEEK } + +enum class Signal { UP, DOWN, FLAT, ABSTAIN, NO_DATA, POSITIVE, NEGATIVE, IN_RANGE } + +enum class IvSource { PROVIDER, BACKFILL } + +object ModelVersions { + const val EXPECTED_MOVE = "em-v1" + const val GEX = "gex-v1" + const val PCR = "pcr-v1" + const val SENTIMENT = "sent-v1" + const val MAXPAIN_PULL = "maxpain-pull-v1" + const val PCR_CONTRARIAN = "pcr-contrarian-v1" + const val IV_BACKFILL = "iv-backfill-v1" + + val LIVE = listOf(EXPECTED_MOVE, GEX, PCR, SENTIMENT, MAXPAIN_PULL, PCR_CONTRARIAN).joinToString(",") + val BACKFILL = listOf(IV_BACKFILL, EXPECTED_MOVE, PCR, PCR_CONTRARIAN).joinToString(",") +} + +object ScoreSubjects { + const val ALWAYS_UP = "baseline-always-up" + const val PREV_DIRECTION = "baseline-prev-direction" + const val EXPECTED_MOVE = "em-range" + const val RV_RANGE = "rv-range" + const val MAXPAIN = "maxpain" + const val MAXPAIN_BASE_SPOT = "maxpain-base-spot" + const val MAXPAIN_BASE_STRIKE = "maxpain-base-strike" + const val GEX_REGIME = "gex-regime" + const val JOURNAL = "journal" +} diff --git a/src/main/kotlin/com/option/domain/model/vo/SentimentComponent.kt b/src/main/kotlin/com/option/domain/model/vo/SentimentComponent.kt new file mode 100644 index 0000000..a33da81 --- /dev/null +++ b/src/main/kotlin/com/option/domain/model/vo/SentimentComponent.kt @@ -0,0 +1,12 @@ +package com.option.domain.model.vo + +import java.time.LocalDate + +// 시장심리지수 지표 그냥 보관용이라 jsonb로 +data class SentimentComponent( + val name: String, + val value: Double?, + val percentile: Double?, + val asOf: LocalDate?, + val used: Boolean, +) diff --git a/src/main/kotlin/com/option/domain/user/entity/User.kt b/src/main/kotlin/com/option/domain/user/entity/User.kt new file mode 100644 index 0000000..d7ac8fa --- /dev/null +++ b/src/main/kotlin/com/option/domain/user/entity/User.kt @@ -0,0 +1,28 @@ +package com.option.domain.user.entity + +import com.option.global.common.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table + +@Entity +@Table(name = "users") +class User( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(nullable = false, unique = true) + val email: String, + + // 해시한 값만 저장한다. + @Column(nullable = false) + var password: String, + + // 마지막으로 발급한 리프레시 토큰. 재발급할 때 이것과 같아야 한다. + @Column(name = "refresh_token") + var refreshToken: String? = null, +) : BaseEntity() diff --git a/src/main/kotlin/com/option/global/common/BaseEntity.kt b/src/main/kotlin/com/option/global/common/BaseEntity.kt new file mode 100644 index 0000000..a5bb6a1 --- /dev/null +++ b/src/main/kotlin/com/option/global/common/BaseEntity.kt @@ -0,0 +1,24 @@ +package com.option.global.common + +import jakarta.persistence.Column +import jakarta.persistence.EntityListeners +import jakarta.persistence.MappedSuperclass +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import java.time.Instant + +// 모든 엔티티에 만든 시간과 고친 시간을 붙인다. 값은 저장할 때 JPA가 채운다. +@MappedSuperclass +@EntityListeners(AuditingEntityListener::class) +abstract class BaseEntity { + @CreatedDate + @Column(name = "created_at", nullable = false, updatable = false) + lateinit var createdAt: Instant + protected set + + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + lateinit var updatedAt: Instant + protected set +} diff --git a/src/main/kotlin/com/option/global/config/JpaConfig.kt b/src/main/kotlin/com/option/global/config/JpaConfig.kt new file mode 100644 index 0000000..a6e28aa --- /dev/null +++ b/src/main/kotlin/com/option/global/config/JpaConfig.kt @@ -0,0 +1,15 @@ +package com.option.global.config + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.data.auditing.DateTimeProvider +import org.springframework.data.jpa.repository.config.EnableJpaAuditing +import java.time.Clock +import java.util.Optional + +@Configuration +@EnableJpaAuditing(dateTimeProviderRef = "auditingDateTimeProvider") +class JpaConfig { + @Bean + fun auditingDateTimeProvider(clock: Clock): DateTimeProvider = DateTimeProvider { Optional.of(clock.instant()) } +} diff --git a/src/main/kotlin/com/option/global/properties/JobProperties.kt b/src/main/kotlin/com/option/global/properties/JobProperties.kt index 57a6b5a..65dd754 100644 --- a/src/main/kotlin/com/option/global/properties/JobProperties.kt +++ b/src/main/kotlin/com/option/global/properties/JobProperties.kt @@ -1,13 +1,10 @@ package com.option.global.properties import org.springframework.boot.context.properties.ConfigurationProperties -import java.time.Duration import java.time.LocalDate @ConfigurationProperties("job") data class JobProperties( - val lease: Duration, - val tradeTimeBudget: Duration, val backfillStart: LocalDate, val backfillExpirationStart: LocalDate, ) diff --git a/src/main/kotlin/com/option/global/properties/StorageProperties.kt b/src/main/kotlin/com/option/global/properties/StorageProperties.kt index 104b038..401a3f1 100644 --- a/src/main/kotlin/com/option/global/properties/StorageProperties.kt +++ b/src/main/kotlin/com/option/global/properties/StorageProperties.kt @@ -5,7 +5,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties @ConfigurationProperties("storage") data class StorageProperties( val rawDir: String, - val tradesDir: String, val stopBackgroundFreeRatio: Double, val stopCollectionFreeRatio: Double, val minFreeBytes: Long, diff --git a/src/main/kotlin/com/option/global/properties/UniverseProperties.kt b/src/main/kotlin/com/option/global/properties/UniverseProperties.kt index 2ff7e14..3768422 100644 --- a/src/main/kotlin/com/option/global/properties/UniverseProperties.kt +++ b/src/main/kotlin/com/option/global/properties/UniverseProperties.kt @@ -4,7 +4,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties @ConfigurationProperties("universe") data class UniverseProperties( - val tradeSymbols: List, val backfillSymbols: List, // 이거 빼면 S&P 500과 아래 ETF 전부 수집 val only: List = emptyList(), diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 63ab13a..b32fada 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -33,7 +33,7 @@ management: endpoints: web: exposure: - include: health, info, prometheus, metrics + include: health, info, prometheus, metrics, jobs metrics: distribution: percentiles-histogram: @@ -61,19 +61,15 @@ finnhub: storage: raw-dir: ${OPTION_RAW_DIR:./data/raw} - trades-dir: ${OPTION_TRADES_DIR:./data/trades} # 디스크 용량 확보용 stop-background-free-ratio: 0.20 stop-collection-free-ratio: 0.10 min-free-bytes: 2147483648 job: - lease: 10m - trade-time-budget: 6h backfill-start: 2024-01-01 backfill-expiration-start: 2024-02-01 universe: only: ${OPTION_UNIVERSE_ONLY:} - trade-symbols: SPY, QQQ backfill-symbols: SPY, QQQ