Esempio n. 1
0
    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]
Esempio n. 2
0
 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)
Esempio n. 3
0
 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)
Esempio n. 4
0
 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
Esempio n. 5
0
    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
Esempio n. 6
0
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")
Esempio n. 7
0
 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
Esempio n. 8
0
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()