diff --git a/.env b/.env index 0cb9884..d990498 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ export PAIRS="BTC_USDT,TRX_USDT,ETH_USDT,DOGE_USDT,BCH_USDT" export INTERVALS="MINUTE_1,MINUTE_15,HOUR_1,DAY_1" -export DB_NAME="./poloniex_data.db" +export DATABASE_URL="sqlite://./poloniex_data.db" export POLONIEX_REST_URL="https://api.poloniex.com" export POLONIEX_WS_URL="wss://ws.poloniex.com/ws/public" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 7389f91..2bc2491 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -81,6 +87,15 @@ dependencies = [ "tungstenite", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -105,7 +120,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -114,11 +129,20 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" @@ -174,9 +198,24 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "core-foundation" version = "0.9.4" @@ -202,6 +241,36 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crypto-common" version = "0.1.6" @@ -218,6 +287,17 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "digest" version = "0.10.7" @@ -225,7 +305,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -245,6 +327,21 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -280,16 +377,26 @@ dependencies = [ ] [[package]] -name = "fallible-iterator" -version = "0.3.0" +name = "etcetera" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] [[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" +name = "event-listener" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] [[package]] name = "fastrand" @@ -297,6 +404,17 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -340,6 +458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -348,6 +467,28 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -384,9 +525,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", + "futures-io", "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -422,7 +565,7 @@ dependencies = [ "cfg-if", "libc", "wasi 0.13.3+wasi-0.2.2", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -456,6 +599,8 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ + "allocator-api2", + "equivalent", "foldhash", ] @@ -474,6 +619,39 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "http" version = "1.2.0" @@ -780,18 +958,34 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + [[package]] name = "libc" version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + [[package]] name = "libsqlite3-sys" -version = "0.31.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8935b44e7c13394a179a438e0cebba0fe08fe01b54f152e29a93b5cf993fd4" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ + "cc", "pkg-config", "vcpkg", ] @@ -808,12 +1002,32 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.4" @@ -863,6 +1077,43 @@ dependencies = [ "tempfile", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -870,6 +1121,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -931,6 +1183,44 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -949,6 +1239,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.31" @@ -967,10 +1278,10 @@ dependencies = [ "futures-util", "reqwest", "reqwest-websocket", - "rusqlite", "serde", "serde_json", "serde_tuple", + "sqlx", "strum", "thiserror 2.0.11", "tokio", @@ -1034,6 +1345,15 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + [[package]] name = "reqwest" version = "0.12.12" @@ -1111,17 +1431,23 @@ dependencies = [ ] [[package]] -name = "rusqlite" -version = "0.33.0" +name = "rsa" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c6d5e5acb6f6129fe3f7ba0a7fc77bca1942cb568535e18e7bc40262baf3110" +checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" dependencies = [ - "bitflags", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec", + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", ] [[package]] @@ -1203,6 +1529,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "security-framework" version = "2.11.1" @@ -1302,12 +1634,33 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "slab" version = "0.4.9" @@ -1322,6 +1675,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -1338,6 +1694,210 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0" +dependencies = [ + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown", + "hashlink", + "indexmap", + "log", + "memchr", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.11", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.98", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.98", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.11", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.11", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "tracing", + "url", +] [[package]] name = "stable_deref_trait" @@ -1345,6 +1905,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strum" version = "0.26.3" @@ -1500,6 +2071,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.43.0" @@ -1547,6 +2133,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.13" @@ -1594,6 +2191,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1649,12 +2247,33 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + [[package]] name = "untrusted" version = "0.9.0" @@ -1726,6 +2345,12 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1807,13 +2432,23 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "whoami" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +dependencies = [ + "redox_syscall", + "wasite", +] + [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1824,7 +2459,7 @@ checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1833,7 +2468,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1843,7 +2478,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] @@ -1852,7 +2496,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1861,7 +2505,22 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -1870,28 +2529,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1904,24 +2581,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index cf0fb8f..b583538 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,10 @@ envy = "0.4.2" futures-util = "0.3.31" reqwest = { version = "0.12.12", features = ["json"] } reqwest-websocket = "0.4.4" -rusqlite = "0.33.0" serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.138" serde_tuple = "1.1.0" +sqlx = { version = "0.8.3", features = ["chrono", "runtime-tokio", "sqlite"] } strum = { version = "0.26.3", features = ["derive"] } thiserror = "2.0.11" tokio = { version = "1.43.0", features = ["rt-multi-thread", "macros"] } diff --git a/migrations/20250210064035_initial.sql b/migrations/20250210064035_initial.sql new file mode 100644 index 0000000..e02a434 --- /dev/null +++ b/migrations/20250210064035_initial.sql @@ -0,0 +1,31 @@ +-- Add migration script here +CREATE TABLE IF NOT EXISTS trades( + symbol TEXT NOT NULL, + amount REAL NOT NULL, + taker_side TEXT NOT NULL, + quantity REAL NOT NULL, + create_time DATETIME NOT NULL, + price REAL NOT NULL, + id TEXT NOT NULL, + ts INT NOT NULL +); + +CREATE TABLE IF NOT EXISTS candles( + pair TEXT NOT NULL, + interval TEXT NOT NULL, + low REAL NOT NULL, + high REAL NOT NULL, + open REAL NOT NULL, + close REAL NOT NULL, + amount REAL NOT NULL, + quantity REAL NOT NULL, + buy_taker_amount REAL NOT NULL, + buy_taker_quantity REAL NOT NULL, + trade_count INT NOT NULL, + ts INT NOT NULL, + weighted_average REAL NOT NULL, + start_time DATETIME NOT NULL, + close_time DATETIME NOT NULL, + + PRIMARY KEY(pair, interval, start_time) +); \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 86b7ed4..e35a09a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,7 +14,7 @@ pub struct Config { pub poloniex_rest_url: Url, #[serde(deserialize_with = "deser_url")] pub poloniex_ws_url: Url, - pub db_name: String, + pub database_url: String, } fn deser_url<'de, D: Deserializer<'de>>(deserialize: D) -> Result { diff --git a/src/error.rs b/src/error.rs index fac890d..297cf89 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,7 +17,7 @@ pub enum AppError { SerdeError(#[from] serde_json::Error), #[error(transparent)] - DbError(#[from] rusqlite::Error), + DbError(#[from] sqlx::Error), #[error(transparent)] StrumError(#[from] strum::ParseError), diff --git a/src/main.rs b/src/main.rs index 1ece8c0..719ae7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use config::get_config; use error::AppResult; use futures_util::{future::try_join_all, StreamExt}; use markets::{poloniex::PoloniexClient, Market}; -use models::{Candle, CandleExtended, CandleInterval, TradeDirection}; +use models::{Candle, CandleExtended, CandleInterval, Trade, TradeDirection}; use repos::{sqlite::SqliteRepo, Repo}; mod config; @@ -27,7 +27,11 @@ async fn fetch_candles_until_now( let limit = 500; loop { - println!("pulling candles from {start_time}"); + println!( + "pulling {}:{} candles from {start_time}", + pair, + interval.as_ref() + ); let candles = market_client .get_historical_candles(&pair, interval, start_time, Utc::now().naive_utc(), limit) .await?; @@ -64,19 +68,56 @@ async fn fetch_candles_until_now( Ok((result, pair.to_string())) } -async fn trades_processor( +async fn calculate_new_candles( repo: Arc, - market_client: Arc, - pairs: &[String], interval: CandleInterval, + trade: Trade, ) -> AppResult<()> { - let mut trades = market_client.recent_trades_stream(&pairs).await?; + let is_buy = matches!(trade.taker_side, TradeDirection::Buy); + let insert_new_candle = || async { + let interval_secs = match interval { + CandleInterval::M1 => 60, + CandleInterval::M15 => 60 * 15, + CandleInterval::H1 => 60 * 60, + CandleInterval::D1 => 60 * 60 * 24, + }; + let new_candle_ts = DateTime::from_timestamp( + (trade.ts.and_utc().timestamp() / interval_secs) * interval_secs, + 0, + ) + .unwrap() + .naive_utc(); + + let new_candle = CandleExtended { + candle: Candle { + low: trade.price, + high: trade.price, + open: trade.price, + close: trade.price, + amount: trade.amount, + quantity: trade.quantity, + buy_taker_amount: if is_buy { trade.amount } else { 0.0 }, + buy_taker_quantity: if is_buy { trade.quantity } else { 0.0 }, + trade_count: 1, + ts: trade.ts, + weighted_average: trade.amount / trade.quantity, + interval, + start_time: new_candle_ts, + close_time: NaiveDateTime::UNIX_EPOCH, + }, + pair: trade.symbol.clone(), + }; - while let Some(t) = trades.next().await { - println!("{t:?}"); + repo.upsert_candles(&[new_candle]).await?; - let Ok(trade) = t else { break }; - let mut last_candle = repo.get_latest_candle_from_interval(&trade.symbol, interval)?; + AppResult::Ok(()) + }; + + let last_candle = repo + .get_latest_candle_from_interval(&trade.symbol, interval) + .await?; + + if let Some(mut last_candle) = last_candle { let interval_delta = match last_candle.candle.interval { CandleInterval::M1 => TimeDelta::minutes(1), CandleInterval::M15 => TimeDelta::minutes(15), @@ -84,44 +125,9 @@ async fn trades_processor( CandleInterval::D1 => TimeDelta::days(1), }; - let is_buy = matches!(trade.taker_side, TradeDirection::Buy); - // если трейд не входит в интервал последней свечи, то создаём новую свечу, иначе обновляем предыдущую if trade.ts > (last_candle.candle.ts + interval_delta) { - let interval_secs = match last_candle.candle.interval { - CandleInterval::M1 => 60, - CandleInterval::M15 => 60 * 15, - CandleInterval::H1 => 60 * 60, - CandleInterval::D1 => 60 * 60 * 24, - }; - let new_candle_ts = DateTime::from_timestamp( - (trade.ts.and_utc().timestamp() / interval_secs) * interval_secs, - 0, - ) - .unwrap() - .naive_utc(); - - let new_candle = CandleExtended { - candle: Candle { - low: trade.price, - high: trade.price, - open: trade.price, - close: trade.price, - amount: trade.amount, - quantity: trade.quantity, - buy_taker_amount: if is_buy { trade.amount } else { 0.0 }, - buy_taker_quantity: if is_buy { trade.quantity } else { 0.0 }, - trade_count: 1, - ts: trade.ts, - weighted_average: trade.amount / trade.quantity, - interval, - start_time: new_candle_ts, - close_time: NaiveDateTime::UNIX_EPOCH, - }, - pair: trade.symbol.clone(), - }; - - repo.upsert_candle(&new_candle)?; + insert_new_candle().await?; } else { last_candle.candle.low = last_candle.candle.low.min(trade.price); last_candle.candle.high = last_candle.candle.high.max(trade.price); @@ -140,12 +146,14 @@ async fn trades_processor( last_candle.candle.buy_taker_quantity += trade.quantity; } - repo.upsert_candle(&last_candle)?; + repo.upsert_candles(&[last_candle]).await?; } - - repo.insert_trade(&trade)?; + } else { + insert_new_candle().await?; } + repo.insert_trade(&trade).await?; + Ok(()) } @@ -157,9 +165,8 @@ async fn _main() -> AppResult<()> { &config.poloniex_rest_url, &config.poloniex_ws_url, )); - let repo = Arc::new(SqliteRepo::new_init(config.db_name)?); - - let start_time = NaiveDate::from_ymd_opt(2024, 12, 1) + let repo = Arc::new(SqliteRepo::new(&config.database_url).await?); + let base_start_time = NaiveDate::from_ymd_opt(2024, 12, 1) .unwrap() .and_hms_opt(0, 0, 0) .unwrap(); @@ -168,6 +175,17 @@ async fn _main() -> AppResult<()> { for pair in &config.pairs { for interval in &config.intervals { + let start_time = { + let last_candle = repo + .get_latest_candle_from_interval(&pair, *interval) + .await?; + + match last_candle { + Some(c) => c.candle.close_time + TimeDelta::seconds(1), + None => base_start_time, + } + }; + let fetcher = fetch_candles_until_now( poloniex_client.clone(), pair.to_string(), @@ -185,31 +203,41 @@ async fn _main() -> AppResult<()> { // config.interval.as_ref() // ); - // нельзя так делать, нужно использовать транзакцию - // и батч-вставку для уменьшения количества обращений к бд, - // но в контексте тестового и так сойдёт - for (candles, pair) in fetched_candles { - for candle in candles { - repo.upsert_candle(&CandleExtended { - candle, - pair: pair.clone(), - })?; - } - } + let candles_to_upsert = fetched_candles + .into_iter() + .flat_map(|(candles, pair)| { + candles + .into_iter() + .map(|candle| CandleExtended { + candle, + pair: pair.clone(), + }) + .collect::>() + }) + .collect::>(); + + repo.upsert_candles(&candles_to_upsert).await?; - for interval in &config.intervals { - tokio::spawn({ - let poloniex_client = poloniex_client.clone(); - let repo = repo.clone(); - let pairs = config.pairs.clone(); - let interval = *interval; - async move { - let result = trades_processor(repo, poloniex_client, &pairs, interval).await; - if let Err(e) = result { - eprintln!("processor stopped with error: {e}") + let mut trades = poloniex_client.recent_trades_stream(&config.pairs).await?; + + while let Some(t) = trades.next().await { + println!("{t:?}"); + + let Ok(trade) = t else { break }; + + for interval in &config.intervals { + tokio::spawn({ + let repo = repo.clone(); + let interval = *interval; + let trade = trade.clone(); + async move { + let result = calculate_new_candles(repo, interval, trade).await; + if let Err(e) = result { + eprintln!("processor stopped with error: {e:?}") + } } - } - }); + }); + } } Ok(()) } @@ -217,6 +245,6 @@ async fn _main() -> AppResult<()> { #[tokio::main] async fn main() { if let Err(e) = _main().await { - eprintln!("{e}"); + eprintln!("{e:?}"); } } diff --git a/src/models.rs b/src/models.rs index 6628518..dda8e8c 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,8 +1,9 @@ use chrono::{DateTime, NaiveDateTime}; use serde::{Deserialize, Deserializer, Serialize}; use serde_tuple::Deserialize_tuple; +use sqlx::prelude::{FromRow, Type}; -#[derive(strum::EnumString, strum::AsRefStr, Clone, Copy, Deserialize, Debug)] +#[derive(strum::EnumString, strum::AsRefStr, Clone, Copy, Deserialize, Debug, Type)] pub enum CandleInterval { #[strum(serialize = "MINUTE_1")] #[serde(rename = "MINUTE_1")] @@ -18,7 +19,7 @@ pub enum CandleInterval { D1, } -#[derive(Debug, Deserialize_tuple)] +#[derive(Debug, Deserialize_tuple, FromRow)] pub struct Candle { #[serde(deserialize_with = "deser_str_to_int")] pub low: f64, @@ -48,7 +49,9 @@ pub struct Candle { pub close_time: NaiveDateTime, } +#[derive(FromRow)] pub struct CandleExtended { + #[sqlx(flatten)] pub candle: Candle, pub pair: String, } @@ -66,7 +69,7 @@ fn deser_naive_dt<'de, D: Deserializer<'de>>(deserialize: D) -> Result AppResult; +#[async_trait] +pub trait Repo: Send + Sync { + async fn upsert_candles(&self, candles: &[CandleExtended]) -> AppResult; - fn insert_trade(&self, trade: &Trade) -> AppResult; + async fn insert_trade(&self, trade: &Trade) -> AppResult; - fn get_latest_candle_from_interval( + async fn get_latest_candle_from_interval( &self, pair: &str, interval: CandleInterval, - ) -> AppResult; + ) -> AppResult>; } diff --git a/src/repos/sqlite.rs b/src/repos/sqlite.rs index 9fca5ae..641015d 100644 --- a/src/repos/sqlite.rs +++ b/src/repos/sqlite.rs @@ -1,71 +1,27 @@ -use std::{fs, path::Path, str::FromStr}; - -use chrono::DateTime; -use rusqlite::{self, params, Connection}; +use sqlx::SqlitePool; use crate::{ error::{AppError, AppResult}, - models::{Candle, CandleExtended, CandleInterval, Trade}, + models::{CandleExtended, CandleInterval, Trade}, }; use super::Repo; pub struct SqliteRepo { - conn: Connection, + pool: SqlitePool, } impl SqliteRepo { - pub fn new_init(db_path: impl AsRef) -> AppResult { - let path = db_path.as_ref(); - - // постоянно создаём новую бд для упрощения тестирования - fs::remove_file(path).ok(); - - let conn = Connection::open(path)?; - - conn.execute_batch( - "BEGIN; - - CREATE TABLE IF NOT EXISTS trades( - symbol TEXT NOT NULL, - amount REAL NOT NULL, - taker_side TEXT NOT NULL, - quantity REAL NOT NULL, - create_time INT NOT NULL, - price REAL NOT NULL, - id TEXT NOT NULL, - ts INT NOT NULL - ); - - CREATE TABLE IF NOT EXISTS candles( - low REAL NOT NULL, - high REAL NOT NULL, - open REAL NOT NULL, - close REAL NOT NULL, - amount REAL NOT NULL, - quantity REAL NOT NULL, - buy_taker_amount REAL NOT NULL, - buy_taker_quantity REAL NOT NULL, - trade_count INT NOT NULL, - ts INT NOT NULL, - weighted_average REAL NOT NULL, - interval TEXT NOT NULL, - start_time INT NOT NULL, - close_time INT NOT NULL, - pair TEXT NOT NULL, + pub async fn new(db_url: &str) -> AppResult { + let pool = SqlitePool::connect(db_url).await?; - PRIMARY KEY(pair, interval, start_time) - ); - - COMMIT;", - )?; - - Ok(Self { conn }) + Ok(Self { pool }) } } +#[async_trait] impl Repo for SqliteRepo { - fn upsert_candle(&self, candle: &CandleExtended) -> AppResult { + async fn upsert_candles(&self, candles: &[CandleExtended]) -> AppResult { let q = " REPLACE INTO candles( low, @@ -84,48 +40,53 @@ impl Repo for SqliteRepo { close_time, pair ) VALUES ( - ?1, - ?2, - ?3, - ?4, - ?5, - ?6, - ?7, - ?8, - ?9, - ?10, - ?11, - ?12, - ?13, - ?14, - ?15 + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12, + $13, + $14, + $15 ) "; - self.conn - .execute( - q, - params![ - &candle.candle.low, - &candle.candle.high, - &candle.candle.open, - &candle.candle.close, - &candle.candle.amount, - &candle.candle.quantity, - &candle.candle.buy_taker_amount, - &candle.candle.buy_taker_quantity, - &candle.candle.trade_count, - &candle.candle.ts.and_utc().timestamp_millis(), - &candle.candle.weighted_average, - &candle.candle.interval.as_ref(), - &candle.candle.start_time.and_utc().timestamp_millis(), - &candle.candle.close_time.and_utc().timestamp_millis(), - &candle.pair - ], - ) - .map_err(AppError::from) + + let mut tx = self.pool.begin().await?; + let mut affected = 0; + for candle in candles { + affected += sqlx::query(q) + .bind(&candle.candle.low) + .bind(&candle.candle.high) + .bind(&candle.candle.open) + .bind(&candle.candle.close) + .bind(&candle.candle.amount) + .bind(&candle.candle.quantity) + .bind(&candle.candle.buy_taker_amount) + .bind(&candle.candle.buy_taker_quantity) + .bind(&candle.candle.trade_count) + .bind(&candle.candle.ts) + .bind(&candle.candle.weighted_average) + .bind(&candle.candle.interval) + .bind(&candle.candle.start_time) + .bind(&candle.candle.close_time) + .bind(&candle.pair) + .execute(&mut *tx) + .await? + .rows_affected(); + } + + tx.commit().await?; + Ok(affected) } - fn insert_trade(&self, trade: &Trade) -> AppResult { + async fn insert_trade(&self, trade: &Trade) -> AppResult { let q = " INSERT INTO trades( symbol, @@ -137,72 +98,49 @@ impl Repo for SqliteRepo { id, ts ) VALUES ( - ?1, - ?2, - ?3, - ?4, - ?5, - ?6, - ?7, - ?8 + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8 ); "; - self.conn - .execute( - &q, - params![ - &trade.symbol, - &trade.amount, - &trade.taker_side.as_ref(), - &trade.quantity, - &trade.create_time.and_utc().timestamp_millis(), - &trade.price, - &trade.id, - &trade.ts.and_utc().timestamp_millis() - ], - ) + sqlx::query(&q) + .bind(&trade.symbol) + .bind(&trade.amount) + .bind(&trade.taker_side) + .bind(&trade.quantity) + .bind(&trade.create_time) + .bind(&trade.price) + .bind(&trade.id) + .bind(&trade.ts) + .execute(&self.pool) + .await .map_err(AppError::from) + .map(|r| r.rows_affected()) } - fn get_latest_candle_from_interval( + async fn get_latest_candle_from_interval( &self, pair: &str, interval: CandleInterval, - ) -> AppResult { + ) -> AppResult> { let q = " SELECT * FROM candles - WHERE pair = ?1 AND interval = ?2 + WHERE pair = $1 AND interval = $2 + ORDER BY start_time DESC + LIMIT 1 "; - self.conn - .query_row(&q, params![pair, interval.as_ref()], |row| { - Ok(CandleExtended { - candle: Candle { - low: row.get(0)?, - high: row.get(1)?, - open: row.get(2)?, - close: row.get(3)?, - amount: row.get(4)?, - quantity: row.get(5)?, - buy_taker_amount: row.get(6)?, - buy_taker_quantity: row.get(7)?, - trade_count: row.get(8)?, - ts: DateTime::from_timestamp(row.get(9)?, 0) - .unwrap() - .naive_local(), - weighted_average: row.get(10)?, - interval: FromStr::from_str(&row.get::<_, String>(11)?).unwrap(), - start_time: DateTime::from_timestamp(row.get(12)?, 0) - .unwrap() - .naive_local(), - close_time: DateTime::from_timestamp(row.get(13)?, 0) - .unwrap() - .naive_local(), - }, - pair: row.get(14)?, - }) - }) + sqlx::query_as::<_, CandleExtended>(q) + .bind(pair) + .bind(interval) + .fetch_optional(&self.pool) + .await .map_err(AppError::from) } }