def test_can_load_full_ohlc_from_partials(self): instr = m.FXInstrument("i") ohlc1 = OHLC(date(2005, 6, 20), open=10.0, low=5.0, high=15.0, close=12.0, num_trades=1, volume=100.0, waprice=11.0) ohlc2 = OHLC(date(2005, 6, 23), open=10.0, low=5.0, high=15.0, close=13.0, num_trades=2, volume=100.0, waprice=11.0) partial_replies = [ OHLCSeries(instr.code, [ohlc1]), OHLCSeries(instr.code, [ohlc2]), OHLCSeries(instr.code, []) ] mocked_loader = MagicMock(side_effect=partial_replies) full_table = instr.load_ohlc_table(None, mocked_loader) assert not full_table.is_empty() assert full_table.ohlc_series == [ohlc1, ohlc2]
def test_can_parse_empty_reply_type1(self, sample_empty_quote1_xml: str): instr = m.FXInstrument("i") today_quotes = instr._parse_intraday_quotes(sample_empty_quote1_xml) assert today_quotes.instrument == "EUR_RUB__TOM" assert today_quotes.last == 0.0 assert today_quotes.num_trades == 0 assert today_quotes.is_trading is False assert today_quotes.time == time(9, 15, 4)
def test_can_parse_day_quotes(self, sample_today_rates_xml: str): instr = m.FXInstrument("i") today_quotes = instr._parse_intraday_quotes(sample_today_rates_xml) assert today_quotes.instrument == "USD000UTSTOM" assert today_quotes.last == 74.415 assert today_quotes.num_trades == 65036 assert today_quotes.is_trading is False assert today_quotes.time == time(23, 49, 59)
def test_can_parse_empty_reply_type2(self, sample_empty_quote2_xml: str): instr = m.FXInstrument("i") datetime_now = datetime.now() now = (datetime_now - timedelta( minutes=15, microseconds=datetime_now.microsecond)).time() today_quotes = instr._parse_intraday_quotes(sample_empty_quote2_xml) assert today_quotes.instrument == "i" assert today_quotes.last == 0.0 assert today_quotes.num_trades == 0 assert today_quotes.is_trading is False # yes, is not guaranteed to be the same... assert today_quotes.time == now
def test_hash_equals(self): fx1 = m.FXInstrument("1234") fx2 = m.FXInstrument("1234") assert fx1 == fx2 b1 = m.BondInstrument("1234") assert fx1 != b1 fx3 = m.FXInstrument("234") assert fx1 != fx3 b2 = m.BondInstrument("1234") assert b1 == b2 b3 = m.BondInstrument("789") assert b1 != b3 s1 = m.ShareInstrument("1234") assert b1 != s1 s2 = m.ShareInstrument("1234") assert s1 == s2 s3 = m.ShareInstrument("567") assert s1 != s3 assert str(fx1) == fx1.code
def main(): parser = argparse.ArgumentParser( description="Downloads EOD OLHC data from Moscow Exchange (MOEX) " "and saves to CSV files", formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument( "--fx-codes", help= "Security ID's of FX instruments on MOEX (e.g. EUR_RUB__TOM USD000UTSTOM). " "To get it, check " "https://iss.moex.com/iss/history/engines/currency/markets/selt/boards/cets/securities.csv" " - you need SECID column", nargs="+", metavar="FX_CODE") parser.add_argument( "--bond-codes", help="ISIN's of bond instruments on MOEX (e.g. RU000A100YG1). ", nargs="+", metavar="BOND_CODE") parser.add_argument( "--share-codes", help= "Security ID's (not ISIN's!) of share instruments on MOEX (e.g. SBMX). ", nargs="+", metavar="SHARE_CODE") args = parser.parse_args() fx_codes = args.fx_codes bond_codes = args.bond_codes share_codes = args.share_codes if fx_codes is None and bond_codes is None and share_codes is None: raise ValueError( "At least one of --fx-codes, --bond-codes, --share-codes must be specified" ) instrums: List[moex.Instrument] = [] instrums.extend([moex.FXInstrument(secid) for secid in fx_codes]) instrums.extend([moex.BondInstrument(isin) for isin in bond_codes]) instrums.extend([moex.ShareInstrument(secid) for secid in share_codes]) for instr in instrums: logger.info(f"Loading OHLC data for {instr}") olhc_series = instr.load_ohlc_table(from_date=None) olhc_series.save_to_csv(f"data/{instr.code}.csv")
def test_can_parse_currency_ohlc(self, sample_ohlc_currency_csv): inst = m.FXInstrument("eurrub") ohlc: OHLCSeries = inst._parse_ohlc_csv(sample_ohlc_currency_csv) assert not ohlc.is_empty() s = ohlc.ohlc_series assert s[0] == OHLC(date(2005, 6, 20), open=34.79, low=34.7701, high=34.83, close=34.81, num_trades=21, volume=157224489.9, waprice=34.8073) assert s[-1] == OHLC(date(2005, 11, 7), open=34.97, low=33.925, high=34.97, close=34.01, num_trades=57, volume=142336864.5, waprice=34.0031) assert ohlc.name == "EURRUB_TOM" assert inst.name == ohlc.name
def main(): parser = argparse.ArgumentParser( description= "Sends mail if avg of intraday quotes for specified instruments deviate " "significantly from previous days average", formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("--email", default="root", help="E-mail address to which send notifications") parser.add_argument( "--hist-window", type=int, default=5, help="Number of business days to calculate historical average") parser.add_argument( "--intra-window", type=int, default=3, help= "Number of ticks (see --ticks-freq-seconds) to calculate intraday average" ) parser.add_argument( "--ticks-freq-seconds", type=int, default=60, help="Check intraday rates with this frequency, in seconds") parser.add_argument( "--saving-freq-hours", type=int, default=24, help="Save collected EOD rates to disk with this frequency, in hours") parser.add_argument( "--num-std-devs-thresh", type=float, default=2.0, help= "Minimum number of standard deviations intraday rate should jump from the mean to " "send warning email") parser.add_argument( "--fx-codes", help= "Security ID's of FX instruments on MOEX (e.g. EUR_RUB__TOM USD000UTSTOM). " "To get it, check " "https://iss.moex.com/iss/history/engines/currency/markets/selt/boards/cets/securities.csv" " - you need SECID column", nargs="+", metavar="FX_CODE") parser.add_argument( "--bond-codes", help="ISIN's of bond instruments on MOEX (e.g. RU000A100YG1). ", nargs="+", metavar="BOND_CODE") parser.add_argument( "--share-codes", help= "Security ID's (not ISIN's!) of share instruments on MOEX (e.g. SBMX). ", nargs="+", metavar="SHARE_CODE") parser.add_argument("--index-codes", help="Security ID's of indexes on MOEX (e.g. MREDC).", nargs="+", metavar="INDEX_CODE") args = parser.parse_args() email = args.email fx_codes = args.fx_codes bond_codes = args.bond_codes share_codes = args.share_codes index_codes = args.index_codes if fx_codes is None and bond_codes is None and share_codes is None and index_codes is None: raise ValueError( "At least one of --fx-codes, --bond-codes, --share-codes, --index-codes must be specified" ) instrums: List[moex.Instrument] = [] if fx_codes is not None: instrums.extend([moex.FXInstrument(secid) for secid in fx_codes]) if bond_codes is not None: instrums.extend([moex.BondInstrument(isin) for isin in bond_codes]) if share_codes is not None: instrums.extend([moex.ShareInstrument(secid) for secid in share_codes]) if index_codes is not None: instrums.extend([moex.IndexInstrument(secid) for secid in index_codes]) hist_window_size = args.hist_window intraday_window_size = args.intra_window num_std_devs_thresh = args.num_std_devs_thresh ticks_freq = args.ticks_freq_seconds saving_freq = datetime.timedelta(hours=args.saving_freq_hours) logger.info(f"PID {os.getpid()}") s = sched.scheduler() initial_series = get_initial_series(instrums) ticker = Ticker(initial_series, email, hist_window_size, intraday_window_size, num_std_devs_thresh, s, ticks_freq, saving_freq) s.enter(delay=0, priority=1, action=ticker.tick, argument=(ticker, )) def on_sigterm(sig, stack): logger.info(f"Received SIGTERM ({sig}), shutting down\n{stack}") # TODO: save series? but should not wait for next tick which can be too far away exit(0) signal.signal(signal.SIGTERM, on_sigterm) s.run()