Skip to content

Commit

Permalink
Merge d98e265 into f4624bd
Browse files Browse the repository at this point in the history
  • Loading branch information
Scott Sanderson committed Nov 22, 2019
2 parents f4624bd + d98e265 commit 68b1cae
Show file tree
Hide file tree
Showing 23 changed files with 932 additions and 148 deletions.
39 changes: 39 additions & 0 deletions conda/iso4217/meta.yaml
@@ -0,0 +1,39 @@
{% set name = "iso4217" %}
{% set version = "1.6.20180829" %}

package:
name: "{{ name|lower }}"
version: "{{ version }}"

source:
url: "https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/{{ name }}-{{ version }}.tar.gz"
sha256: 33f404b5eeb3cb8572f132b7c697782eccffeb00630900f305244ffa058e875c

build:
number: 0
script: "python -m pip install . -vv"

requirements:
host:
- pip
- python
- setuptools
run:
- python
- setuptools

test:
imports:
- iso4217

about:
home: "https://github.com/dahlia/iso4217"
license: Public Domain
license_family: PUBLIC-DOMAIN
license_file:
summary: "ISO 4217 currency data package for Python"
doc_url:
dev_url:

extra:
recipe-maintainers: ''
3 changes: 3 additions & 0 deletions etc/requirements.txt
Expand Up @@ -97,3 +97,6 @@ typing==3.6.2

# Country Codes
iso3166==0.9

# Currency Codes
iso4217==1.6.20180829
22 changes: 15 additions & 7 deletions tests/data/test_adjustments.py
Expand Up @@ -20,14 +20,14 @@
nat = pd.Timestamp('nat')


class TestSQLiteAdjustementsWriter(WithTradingCalendars,
WithInstanceTmpDir,
WithLogger,
ZiplineTestCase):
class TestSQLiteAdjustmentsWriter(WithTradingCalendars,
WithInstanceTmpDir,
WithLogger,
ZiplineTestCase):
make_log_handler = logbook.TestHandler

def init_instance_fixtures(self):
super(TestSQLiteAdjustementsWriter, self).init_instance_fixtures()
super(TestSQLiteAdjustmentsWriter, self).init_instance_fixtures()
self.db_path = self.instance_tmpdir.getpath('adjustments.db')

def writer(self, session_bar_reader):
Expand All @@ -54,7 +54,11 @@ def empty_in_memory_reader(self, dates, sids):
for key in ('open', 'high', 'low', 'close', 'volume')
}

return InMemoryDailyBarReader(frames, self.trading_calendar)
return InMemoryDailyBarReader(
frames,
self.trading_calendar,
currency_codes=pd.Series(index=sids, data='USD'),
)

def writer_without_pricing(self, dates, sids):
return self.writer(self.empty_in_memory_reader(dates, sids))
Expand All @@ -68,7 +72,11 @@ def in_memory_reader_for_close(self, close):
frames = {'close': close}
for key in 'open', 'high', 'low', 'volume':
frames[key] = nan_frame
return InMemoryDailyBarReader(frames, self.trading_calendar)
return InMemoryDailyBarReader(
frames,
self.trading_calendar,
currency_codes=pd.Series(index=close.columns, data='USD'),
)

def writer_from_close(self, close):
return self.writer(self.in_memory_reader_for_close(close))
Expand Down
24 changes: 24 additions & 0 deletions tests/data/test_daily_bars.py
Expand Up @@ -12,6 +12,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from itertools import cycle, islice
from sys import maxsize
import re

Expand All @@ -27,6 +28,7 @@
concat,
DataFrame,
NaT,
Series,
Timestamp,
)
from six import iteritems
Expand Down Expand Up @@ -145,6 +147,12 @@ class _DailyBarsTestCase(WithEquityDailyBarData,
# The country under which these tests should be run.
DAILY_BARS_TEST_QUERY_COUNTRY_CODE = 'US'

# Currencies to use for assets in these tests.
DAILY_BARS_TEST_CURRENCIES = {
'US': ['USD'],
'CA': ['USD', 'CAD']
}

@classmethod
def init_class_fixtures(cls):
super(_DailyBarsTestCase, cls).init_class_fixtures()
Expand Down Expand Up @@ -174,6 +182,13 @@ def make_equity_daily_bar_data(cls, country_code, sids):
holes=merge(HOLES.values()),
)

@classmethod
def make_equity_daily_bar_currency_codes(cls, country_code, sids):
# Evenly distribute choices among ``sids``.
choices = cls.DAILY_BARS_TEST_CURRENCIES[country_code]
codes = list(islice(cycle(choices), len(sids)))
return Series(index=sids, data=np.array(codes, dtype='S3'))

@classproperty
def holes(cls):
return HOLES[cls.DAILY_BARS_TEST_QUERY_COUNTRY_CODE]
Expand Down Expand Up @@ -506,6 +521,15 @@ def test_get_last_traded_dt(self):
NaT,
)

def test_listing_currency(self):
assets = np.array(list(self.assets))
# TODO: Test loading codes for missing assets.
results = self.daily_bar_reader.currency_codes(assets)
expected = self.make_equity_daily_bar_currency_codes(
self.DAILY_BARS_TEST_QUERY_COUNTRY_CODE, assets,
).values
assert_equal(results, expected)


class BcolzDailyBarTestCase(WithBcolzEquityDailyBarReader, _DailyBarsTestCase):
EQUITY_DAILY_BAR_COUNTRY_CODES = ['US']
Expand Down
2 changes: 1 addition & 1 deletion tests/pipeline/test_engine.py
Expand Up @@ -945,7 +945,7 @@ def init_class_fixtures(cls):
super(SyntheticBcolzTestCase, cls).init_class_fixtures()
cls.all_asset_ids = cls.asset_finder.sids
cls.last_asset_end = cls.equity_info['end_date'].max()
cls.pipeline_loader = EquityPricingLoader(
cls.pipeline_loader = EquityPricingLoader.without_fx(
cls.bcolz_equity_daily_bar_reader,
cls.adjustment_reader,
)
Expand Down
95 changes: 94 additions & 1 deletion tests/pipeline/test_international_markets.py
@@ -1,5 +1,8 @@
"""Tests for pipelines on international markets.
"""
from itertools import cycle, islice

from nose_parameterized import parameterized
import numpy as np
import pandas as pd

Expand Down Expand Up @@ -43,6 +46,18 @@ class WithInternationalDailyBarData(zf.WithAssetFinder):
'XTSE': 50, # Toronto Stock Exchange
'XLON': 25, # London Stock Exchange
}
# Assets in these countries will be quoted in one of the listed currencies.
INTERNATIONAL_PRICING_CURRENCIES = {
'XNYS': ['USD'],
'XTSE': ['CAD'],
'XLON': ['GBP', 'EUR', 'USD'],
}
assert (
INTERNATIONAL_PRICING_STARTING_PRICES.keys()
== INTERNATIONAL_PRICING_CURRENCIES.keys()
)

FX_RATES_CURRENCIES = ["USD", "CAD", "GBP", "EUR"]

@classmethod
def make_daily_bar_data(cls, assets, calendar, sessions):
Expand All @@ -68,13 +83,22 @@ def make_daily_bar_data(cls, assets, calendar, sessions):
sid = asset.sid
yield sid, base_frame + sid

@classmethod
def make_currency_codes(cls, calendar, assets):
currencies = cls.INTERNATIONAL_PRICING_CURRENCIES[calendar.name]
return pd.Series(
index=assets,
data=list(islice(cycle(currencies), len(assets)))
)

@classmethod
def init_class_fixtures(cls):
super(WithInternationalDailyBarData, cls).init_class_fixtures()

cls.daily_bar_sessions = {}
cls.daily_bar_data = {}
cls.daily_bar_readers = {}
cls.daily_bar_currency_codes = {}

for calendar, assets, in cls.assets_by_calendar.items():
name = calendar.name
Expand All @@ -92,13 +116,21 @@ def init_class_fixtures(cls):

panel = (pd.Panel.from_dict(cls.daily_bar_data[name])
.transpose(2, 1, 0))

cls.daily_bar_currency_codes[name] = cls.make_currency_codes(
calendar,
assets,
)

cls.daily_bar_readers[name] = InMemoryDailyBarReader.from_panel(
panel,
calendar,
currency_codes=cls.daily_bar_currency_codes[name],
)


class WithInternationalPricingPipelineEngine(WithInternationalDailyBarData):
class WithInternationalPricingPipelineEngine(zf.WithFXRates,
WithInternationalDailyBarData):

@classmethod
def init_class_fixtures(cls):
Expand All @@ -110,14 +142,17 @@ def init_class_fixtures(cls):
GB_EQUITIES: EquityPricingLoader(
cls.daily_bar_readers['XLON'],
adjustments,
cls.in_memory_fx_rate_reader,
),
US_EQUITIES: EquityPricingLoader(
cls.daily_bar_readers['XNYS'],
adjustments,
cls.in_memory_fx_rate_reader,
),
CA_EQUITIES: EquityPricingLoader(
cls.daily_bar_readers['XTSE'],
adjustments,
cls.in_memory_fx_rate_reader,
)
}
cls.engine = SimplePipelineEngine(
Expand Down Expand Up @@ -236,6 +271,64 @@ def test_generic_pipeline_with_explicit_domain(self, domain):
calendar, col, date, asset, value,
)

@parameterized.expand([
('US', US_EQUITIES, 'XNYS'),
('CA', CA_EQUITIES, 'XTSE'),
('GB', GB_EQUITIES, 'XLON'),
])
def test_currency_convert_prices(self, name, domain, calendar_name):
# Test running a pipeline on a domain whose assets are all denominated
# in the same currency.

pipe = Pipeline({
'close': EquityPricing.close.latest,
'close_USD': EquityPricing.close.fx('USD').latest,
'close_CAD': EquityPricing.close.fx('CAD').latest,
'close_EUR': EquityPricing.close.fx('EUR').latest,
'close_GBP': EquityPricing.close.fx('GBP').latest,
}, domain=domain)

sessions = self.daily_bar_sessions[calendar_name]

start, end = sessions[[-17, -10]]
result = self.run_pipeline(pipe, start, end)

# Raw closes as a (dates, assets) dataframe.
closes_2d = result['close'].unstack(fill_value=np.nan)

# Currency codes for all sids on this domain.
all_currency_codes = self.daily_bar_currency_codes[calendar_name]

# Currency codes for sids in the pipeline result.
currency_codes = all_currency_codes.loc[[
a.sid for a in closes_2d.columns
]]

# For each possible target currency, we should be able to reconstruct
# the currency-converted pipeline result by manually fetching exchange
# rate values and multiplying by the unconverted pricing values.
fx_reader = self.in_memory_fx_rate_reader
for target in self.FX_RATES_CURRENCIES:

# Closes, converted to target currency, as reported by pipeline, as
# a (dates, assets) dataframe.
result_2d = result['close_' + target].unstack(fill_value=np.nan)

# (dates, sids) dataframe giving the exchange rate from each
# asset's currency to the target currency.
expected_rates = fx_reader.get_rates(
field='mid',
quote=target,
bases=np.array(currency_codes, dtype='S3'),
# Exchange rates used for pipeline output with label N should
# be from day N - 1.
dates=sessions[-18:-10],
)

expected_result_2d = closes_2d * expected_rates.values

assert_equal(result_2d, expected_result_2d)

def test_explicit_specialization_matches_implicit(self):
pipeline_specialized = Pipeline({
'open': EquityPricing.open.latest,
Expand Down
2 changes: 1 addition & 1 deletion tests/pipeline/test_pipeline_algo.py
Expand Up @@ -496,7 +496,7 @@ def make_dividends_data(cls):
@classmethod
def init_class_fixtures(cls):
super(PipelineAlgorithmTestCase, cls).init_class_fixtures()
cls.pipeline_loader = USEquityPricingLoader(
cls.pipeline_loader = USEquityPricingLoader.without_fx(
cls.bcolz_equity_daily_bar_reader,
cls.adjustment_reader,
)
Expand Down
4 changes: 2 additions & 2 deletions tests/pipeline/test_us_equity_pricing_loader.py
Expand Up @@ -509,7 +509,7 @@ def test_read_no_adjustments(self):
)
self.assertEqual(adjustments, [{}, {}])

pricing_loader = USEquityPricingLoader(
pricing_loader = USEquityPricingLoader.without_fx(
self.bcolz_equity_daily_bar_reader,
adjustment_reader,
)
Expand Down Expand Up @@ -589,7 +589,7 @@ def test_read_with_adjustments(self):
shift=-1,
)

pricing_loader = USEquityPricingLoader(
pricing_loader = USEquityPricingLoader.without_fx(
self.bcolz_equity_daily_bar_reader,
self.adjustment_reader,
)
Expand Down

0 comments on commit 68b1cae

Please sign in to comment.