|
|
@ -1,30 +1,35 @@ |
|
|
|
|
|
|
|
use std::sync::Arc; |
|
|
|
|
|
|
|
|
|
|
|
use chrono::{DateTime, NaiveDate, NaiveDateTime, TimeDelta, Utc}; |
|
|
|
use chrono::{DateTime, NaiveDate, NaiveDateTime, TimeDelta, Utc}; |
|
|
|
use config::get_config; |
|
|
|
use config::get_config; |
|
|
|
use error::AppResult; |
|
|
|
use error::AppResult; |
|
|
|
use futures_util::StreamExt; |
|
|
|
use futures_util::{future::try_join_all, StreamExt}; |
|
|
|
use models::{Candle, CandleInterval, PoloniuxCandle, TradeDirection}; |
|
|
|
use markets::{poloniex::PoloniexClient, Market}; |
|
|
|
use poloniex::PoloniexClient; |
|
|
|
use models::{Candle, CandleExtended, CandleInterval, TradeDirection}; |
|
|
|
use repo::Repo; |
|
|
|
use repos::{sqlite::SqliteRepo, Repo}; |
|
|
|
|
|
|
|
|
|
|
|
mod config; |
|
|
|
mod config; |
|
|
|
mod error; |
|
|
|
mod error; |
|
|
|
|
|
|
|
mod markets; |
|
|
|
mod models; |
|
|
|
mod models; |
|
|
|
mod poloniex; |
|
|
|
mod repos; |
|
|
|
mod repo; |
|
|
|
|
|
|
|
|
|
|
|
#[macro_use] |
|
|
|
|
|
|
|
extern crate async_trait; |
|
|
|
|
|
|
|
|
|
|
|
async fn fetch_candles_until_now( |
|
|
|
async fn fetch_candles_until_now( |
|
|
|
poloniex_client: &PoloniexClient, |
|
|
|
market_client: Arc<impl Market>, |
|
|
|
pair: &str, |
|
|
|
pair: String, |
|
|
|
interval: CandleInterval, |
|
|
|
interval: CandleInterval, |
|
|
|
mut start_time: NaiveDateTime, |
|
|
|
mut start_time: NaiveDateTime, |
|
|
|
) -> AppResult<Vec<PoloniuxCandle>> { |
|
|
|
) -> AppResult<(Vec<Candle>, String)> { |
|
|
|
let mut result = vec![]; |
|
|
|
let mut result = vec![]; |
|
|
|
let limit = 500; |
|
|
|
let limit = 500; |
|
|
|
|
|
|
|
|
|
|
|
loop { |
|
|
|
loop { |
|
|
|
println!("pulling candles from {start_time}"); |
|
|
|
println!("pulling candles from {start_time}"); |
|
|
|
let candles = poloniex_client |
|
|
|
let candles = market_client |
|
|
|
.get_historical_candles(pair, interval, start_time, Utc::now().naive_utc(), limit) |
|
|
|
.get_historical_candles(&pair, interval, start_time, Utc::now().naive_utc(), limit) |
|
|
|
.await?; |
|
|
|
.await?; |
|
|
|
|
|
|
|
|
|
|
|
let Some(last_candle) = candles.last() else { |
|
|
|
let Some(last_candle) = candles.last() else { |
|
|
@ -56,48 +61,22 @@ async fn fetch_candles_until_now( |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Ok(result) |
|
|
|
Ok((result, pair.to_string())) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async fn _main() -> AppResult<()> { |
|
|
|
async fn trades_processor( |
|
|
|
dotenv::dotenv().ok(); |
|
|
|
repo: Arc<impl Repo>, |
|
|
|
|
|
|
|
market_client: Arc<impl Market>, |
|
|
|
let config = get_config(); |
|
|
|
pairs: &[String], |
|
|
|
let poloniex_client = PoloniexClient::new(&config.poloniex_rest_url, &config.poloniex_ws_url); |
|
|
|
interval: CandleInterval, |
|
|
|
let repo = Repo::new_init(config.db_name)?; |
|
|
|
) -> AppResult<()> { |
|
|
|
|
|
|
|
let mut trades = market_client.recent_trades_stream(&pairs).await?; |
|
|
|
let start_time = NaiveDate::from_ymd_opt(2024, 12, 1) |
|
|
|
|
|
|
|
.unwrap() |
|
|
|
|
|
|
|
.and_hms_opt(0, 0, 0) |
|
|
|
|
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let pair = config.pair.clone(); |
|
|
|
|
|
|
|
let candles = |
|
|
|
|
|
|
|
fetch_candles_until_now(&poloniex_client, &pair, config.interval, start_time).await?; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
println!( |
|
|
|
|
|
|
|
"{pair}: fetched {} candles with interval {}", |
|
|
|
|
|
|
|
candles.len(), |
|
|
|
|
|
|
|
config.interval.as_ref() |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// нельзя так делать, нужно использовать транзакцию
|
|
|
|
|
|
|
|
// и батч-вставку для уменьшения количества обращений к бд,
|
|
|
|
|
|
|
|
// но в контексте тестового и так сойдёт
|
|
|
|
|
|
|
|
for candle in candles { |
|
|
|
|
|
|
|
repo.upsert_candle(&Candle { |
|
|
|
|
|
|
|
candle, |
|
|
|
|
|
|
|
pair: pair.to_string(), |
|
|
|
|
|
|
|
})?; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mut trades = poloniex_client.recent_trades_stream(&pair).await?; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while let Some(t) = trades.next().await { |
|
|
|
while let Some(t) = trades.next().await { |
|
|
|
println!("{t:?}"); |
|
|
|
println!("{t:?}"); |
|
|
|
|
|
|
|
|
|
|
|
let Ok(trade) = t else { break }; |
|
|
|
let Ok(trade) = t else { break }; |
|
|
|
let mut last_candle = repo.get_latest_candle_from_interval(&pair, config.interval)?; |
|
|
|
let mut last_candle = repo.get_latest_candle_from_interval(&trade.symbol, interval)?; |
|
|
|
let interval_delta = match last_candle.candle.interval { |
|
|
|
let interval_delta = match last_candle.candle.interval { |
|
|
|
CandleInterval::M1 => TimeDelta::minutes(1), |
|
|
|
CandleInterval::M1 => TimeDelta::minutes(1), |
|
|
|
CandleInterval::M15 => TimeDelta::minutes(15), |
|
|
|
CandleInterval::M15 => TimeDelta::minutes(15), |
|
|
@ -122,8 +101,8 @@ async fn _main() -> AppResult<()> { |
|
|
|
.unwrap() |
|
|
|
.unwrap() |
|
|
|
.naive_utc(); |
|
|
|
.naive_utc(); |
|
|
|
|
|
|
|
|
|
|
|
let new_candle = Candle { |
|
|
|
let new_candle = CandleExtended { |
|
|
|
candle: PoloniuxCandle { |
|
|
|
candle: Candle { |
|
|
|
low: trade.price, |
|
|
|
low: trade.price, |
|
|
|
high: trade.price, |
|
|
|
high: trade.price, |
|
|
|
open: trade.price, |
|
|
|
open: trade.price, |
|
|
@ -135,7 +114,7 @@ async fn _main() -> AppResult<()> { |
|
|
|
trade_count: 1, |
|
|
|
trade_count: 1, |
|
|
|
ts: trade.ts, |
|
|
|
ts: trade.ts, |
|
|
|
weighted_average: trade.amount / trade.quantity, |
|
|
|
weighted_average: trade.amount / trade.quantity, |
|
|
|
interval: config.interval, |
|
|
|
interval, |
|
|
|
start_time: new_candle_ts, |
|
|
|
start_time: new_candle_ts, |
|
|
|
close_time: NaiveDateTime::UNIX_EPOCH, |
|
|
|
close_time: NaiveDateTime::UNIX_EPOCH, |
|
|
|
}, |
|
|
|
}, |
|
|
@ -153,7 +132,7 @@ async fn _main() -> AppResult<()> { |
|
|
|
last_candle.candle.ts = trade.ts; |
|
|
|
last_candle.candle.ts = trade.ts; |
|
|
|
last_candle.candle.weighted_average = |
|
|
|
last_candle.candle.weighted_average = |
|
|
|
last_candle.candle.amount / last_candle.candle.quantity; |
|
|
|
last_candle.candle.amount / last_candle.candle.quantity; |
|
|
|
last_candle.candle.interval = config.interval; |
|
|
|
last_candle.candle.interval = interval; |
|
|
|
last_candle.candle.close_time = trade.ts; |
|
|
|
last_candle.candle.close_time = trade.ts; |
|
|
|
|
|
|
|
|
|
|
|
if is_buy { |
|
|
|
if is_buy { |
|
|
@ -166,6 +145,72 @@ async fn _main() -> AppResult<()> { |
|
|
|
|
|
|
|
|
|
|
|
repo.insert_trade(&trade)?; |
|
|
|
repo.insert_trade(&trade)?; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Ok(()) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async fn _main() -> AppResult<()> { |
|
|
|
|
|
|
|
dotenv::dotenv().ok(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let config = get_config()?; |
|
|
|
|
|
|
|
let poloniex_client = Arc::new(PoloniexClient::new( |
|
|
|
|
|
|
|
&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) |
|
|
|
|
|
|
|
.unwrap() |
|
|
|
|
|
|
|
.and_hms_opt(0, 0, 0) |
|
|
|
|
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mut fetchers = vec![]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for pair in &config.pairs { |
|
|
|
|
|
|
|
for interval in &config.intervals { |
|
|
|
|
|
|
|
let fetcher = fetch_candles_until_now( |
|
|
|
|
|
|
|
poloniex_client.clone(), |
|
|
|
|
|
|
|
pair.to_string(), |
|
|
|
|
|
|
|
*interval, |
|
|
|
|
|
|
|
start_time, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
fetchers.push(fetcher); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
let fetched_candles = try_join_all(fetchers).await?; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// println!(
|
|
|
|
|
|
|
|
// "{pair}: fetched {} candles with interval {}",
|
|
|
|
|
|
|
|
// candles.len(),
|
|
|
|
|
|
|
|
// config.interval.as_ref()
|
|
|
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// нельзя так делать, нужно использовать транзакцию
|
|
|
|
|
|
|
|
// и батч-вставку для уменьшения количества обращений к бд,
|
|
|
|
|
|
|
|
// но в контексте тестового и так сойдёт
|
|
|
|
|
|
|
|
for (candles, pair) in fetched_candles { |
|
|
|
|
|
|
|
for candle in candles { |
|
|
|
|
|
|
|
repo.upsert_candle(&CandleExtended { |
|
|
|
|
|
|
|
candle, |
|
|
|
|
|
|
|
pair: pair.clone(), |
|
|
|
|
|
|
|
})?; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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}") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
Ok(()) |
|
|
|
Ok(()) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|