Пример #1
0
class VirtualPurchase(model.Model):
    id = ObjectIdField(unique=True, db_column="_id")
    user = model.ForeignKey(settings.AUTH_USER_MODEL, on_delete=model.CASCADE)
    asx_code = model.TextField(max_length=10)
    buy_date = model.DateField()
    price_at_buy_date = model.FloatField()
    amount = model.FloatField()  # dollar value purchased (assumes no fees)
    n = (
        model.IntegerField()
    )  # number of shares purchased at buy_date (rounded down to nearest whole share)

    objects = DjongoManager()

    def current_price(self):
        assert self.n > 0
        quote, _ = latest_quote(self.asx_code)
        if quote is None:
            raise ValueError()
        buy_price = self.price_at_buy_date
        pct_move = (quote.last_price / buy_price) * 100.0 - 100.0 if buy_price > 0.0 else 0.0
        return (self.n * quote.last_price, pct_move)

    def __str__(self):
        cur_price, pct_move = self.current_price()
        return "Purchase on {}: ${} ({} shares@${:.2f}) is now ${:.2f} ({:.2f}%)".format(
            self.buy_date, self.amount, self.n, self.price_at_buy_date, cur_price, pct_move
        )

    class Meta:
        managed = True  # viewer application
        db_table = "virtual_purchase"
Пример #2
0
class ECBMetadata(models.Model):
    """
    Model responsible for describing allowable values for a given metadata_type (dimension, attribute, measure)
    which is used for form generation and validation
    """

    id = ObjectIdField(db_column="_id", primary_key=True)
    metadata_type = models.TextField(db_column="metadata_type",
                                     null=False,
                                     blank=False)
    flow = models.TextField(db_column="flow_name", null=False, blank=False)
    code = models.TextField(db_column="item_name", null=False, blank=False)
    column_name = models.TextField(db_column="codelist_name",
                                   null=False,
                                   blank=False)
    printable_code = models.TextField(db_column="item_value",
                                      null=False,
                                      blank=False)

    objects = DjongoManager()

    class Meta:
        db_table = "ecb_metadata_index"
        verbose_name = "ECB Metadata"
        verbose_name_plural = "ECB Metadata"
Пример #3
0
class MarketQuoteCache(model.Model):
    # { "_id" : ObjectId("5f44c54457d4bb6dfe6b998f"), "scope" : "all-downloaded",
    # "tag" : "change_price-05-2020-asx", "dataframe_format" : "parquet",
    # "field" : "change_price", "last_updated" : ISODate("2020-08-25T08:01:08.804Z"),
    # "market" : "asx", "n_days" : 3, "n_stocks" : 0,
    # "sha256" : "75d0ad7e057621e6a73508a178615bcc436d97110bcc484f1cfb7d478475abc5",
    # "size_in_bytes" : 2939, "status" : "INCOMPLETE" }
    size_in_bytes = model.IntegerField()
    status = model.TextField()
    tag = model.TextField(db_index=True)
    dataframe_format = model.TextField()
    field = model.TextField()
    last_updated = model.DateTimeField()
    market = model.TextField()
    n_days = model.IntegerField()
    n_stocks = model.IntegerField()
    sha256 = model.TextField()
    _id = ObjectIdField()
    scope = model.TextField()
    dataframe = model.BinaryField()

    objects = DjongoManager()

    class Meta:
        managed = False  # table is managed by persist_dataframes.py
        db_table = "market_quote_cache"
Пример #4
0
class BlacklistedDomain(m.Model):
    _id = ObjectIdField(db_column='_id')
    domain = m.CharField(blank=False, null=False, max_length=255)
    reason = m.CharField(blank=False, null=False, max_length=255)

    # responsibility for this model is not with Django so...
    class Meta:
        db_table = 'blacklisted_domains'
        managed = False
Пример #5
0
class Sector(model.Model):
    """
    Table of ASX sector (GICS) names. Manually curated for now.
    """
    id = ObjectIdField(unique=True, db_column="_id")
    sector_name = model.TextField(unique=True)
    sector_id = model.IntegerField(db_column='id')

    class Meta:
        managed = False
        db_table = "sector"
Пример #6
0
class CompanyFinancialMetric(model.Model):
    id = ObjectIdField(db_column="_id", primary_key=True)
    asx_code = model.TextField(blank=False, null=False)
    name = model.TextField(blank=False, null=False, db_column="metric")
    value = model.FloatField()
    as_at = model.DateTimeField(db_column="date")

    objects = DjongoManager()

    class Meta:
        db_table = "asx_company_financial_metrics"
Пример #7
0
class Script(m.Model):
    _id = ObjectIdField(db_column='_id')
    sha256 = m.CharField(blank=False, null=False, max_length=128)
    md5 = m.CharField(blank=False, null=False, max_length=64)
    size_bytes = m.IntegerField()
    code = m.BinaryField()  # not editable by default

    # responsibility for this model is up to etl_upload_artefacts.py
    class Meta:
        db_table = "scripts"
        managed = False
Пример #8
0
class Comments(models.Model):
    _id = ObjectIdField(primary_key=True)
    body = models.TextField()
    questions = models.ForeignKey(Question,
                                  on_delete=models.CASCADE,
                                  related_name='comments')

    def __str__(self):
        return 'Comment Id : {},Question : {}'.format(self._id, self.questions)

    class Meta:
        db_table = 'comments'
Пример #9
0
class Question(models.Model):
    _id = ObjectIdField(primary_key=True)
    title = models.TextField(max_length=1000)
    problem = models.TextField(max_length=100000)
    code = models.TextField(max_length=100000000, blank=True)
    expected = models.TextField(max_length=100000, blank=True)

    def __str__(self):
        return '_id :{}'.format(self._id)

    class Meta:
        db_table = 'questions'
Пример #10
0
class Watchlist(model.Model):
    id = ObjectIdField(unique=True, db_column="_id")
    # record stocks of interest to the user
    user = model.ForeignKey(settings.AUTH_USER_MODEL, on_delete=model.CASCADE)
    asx_code = model.TextField()
    when = model.DateTimeField(auto_now_add=True)

    objects = DjongoManager()

    class Meta:
        managed = True  # viewer application is responsible NOT asxtrade.py
        db_table = "user_watchlist"
Пример #11
0
class JavascriptControlSummary(m.Model):
    _id = ObjectIdField(db_column='_id')
    origin = m.CharField(
        blank=False, null=False, max_length=255
    )  # lookup into JavascriptControl model, although do_not_load=True may be in effect
    last_updated = m.CharField(blank=False, null=False, max_length=64)
    sum_of_ast_features = m.IntegerField()
    sum_of_functions = m.IntegerField()
    sum_of_literals = m.IntegerField()

    # responsibility for this model is up to etl_control_upload*.py so...
    class Meta:
        db_table = "javascript_controls_summary"
        managed = False
Пример #12
0
class WorldBankIndicators(model.Model):
    id = ObjectIdField(primary_key=True, db_column="_id")
    wb_id = model.TextField(
    )  # world bank id, not to be confused with django id/pk
    name = model.TextField()
    last_updated = model.DateTimeField(auto_now_add=True)
    unit = model.TextField()
    source = JSONField()
    source_note = model.TextField()
    topics = ArrayField(WorldBankTopic)
    source_organisation = model.TextField()
    last_successful_data = model.DateTimeField(null=True)
    last_error_when = model.DateTimeField(
        null=True
    )  # date of last ingest error for this indicator (or None if not known)
    last_error_msg = model.TextField()  # eg. Error code 175
    last_error_type = model.TextField()  # eg. class RuntimeError

    objects = DjongoManager()

    def __init__(self, *args, **kwargs):
        self.wb_id = kwargs.pop("id", None)
        self.source_organisation = kwargs.pop("sourceOrganization", None)
        self.source_note = kwargs.pop("sourceNote", None)
        # wb_id=wb_id, source_organisation=source_organisation, source_note=source_note
        super(WorldBankIndicators, self).__init__(*args, **kwargs)

    def __str__(self):
        return f"{self.wb_id} {self.name} {self.source} last_error_when={self.last_error_when} last_updated={self.last_updated}"

    @property
    def tag(self
            ):  # NB: must match the ingest_worldbank_datasets.py tag name...
        return f"{self.wb_id}-yearly-dataframe"

    @property
    def has_data(self):
        obj = WorldBankDataCache.objects.filter(tag=self.tag).first()
        return obj is not None

    def fetch_data(self) -> pd.DataFrame:
        t = self.tag
        print(f"Fetching parquet dataframe for {t}")
        return get_parquet(t)

    class Meta:
        db_table = "world_bank_indicators"
        verbose_name = "World Bank Metric"
        verbose_name_plural = "World Bank Metrics"
Пример #13
0
class WorldBankCountry(model.Model):
    """
    Not strictly a country, can include regions eg. africa ex-warzones
    """

    id = ObjectIdField(primary_key=True, db_column="_id")
    country_code = model.TextField()
    name = model.TextField()
    last_updated = model.DateTimeField(auto_now_add=True)

    objects = DjongoManager()

    class Meta:
        db_table = "world_bank_countries"
        verbose_name_plural = "World Bank Countries"
Пример #14
0
class Security(model.Model):
    # eg. { "_id" : ObjectId("5efe83dd4b1fe020d5ba2de8"), "asx_code" : "T3DAC",
    #  "asx_isin_code" : "AU0000T3DAC0", "company_name" : "333D LIMITED",
    # "last_updated" : ISODate("2020-07-26T00:49:11.052Z"),
    # "security_name" : "OPTION EXPIRING 18-AUG-2018 RESTRICTED" }
    _id = ObjectIdField()
    asx_code = model.TextField(blank=False, null=False)
    asx_isin_code = model.TextField()
    company_name = model.TextField()
    last_updated = model.DateField()
    security_name = model.TextField()

    class Meta:
        db_table = "asx_isin"
        managed = False  # managed by asxtrade.py
Пример #15
0
class ABSInvertedIndex(model.Model):
    id = ObjectIdField(primary_key=True, db_column="_id")
    min_date = model.DateTimeField()
    max_date = model.DateTimeField()
    n_attributes = model.IntegerField()
    last_updated = model.DateTimeField(auto_now_add=True)
    tag = model.TextField()
    dataset = model.TextField()
    name = model.TextField()
    scope = model.TextField()  # always 'abs' for now

    objects = DjongoManager()

    class Meta:
        db_table = "abs_inverted_index"
Пример #16
0
class JavascriptControl(m.Model):
    _id = ObjectIdField(db_column='_id')
    family = m.CharField(blank=False, null=False, max_length=100)
    origin = m.CharField(blank=False, null=False, max_length=1024)
    do_not_load = m.BooleanField()
    size_bytes = m.IntegerField()
    variant = m.CharField(max_length=100)
    release = m.CharField(max_length=100)
    content_type = m.CharField(max_length=100)
    provider = m.CharField(max_length=100)
    subfamily = m.CharField(max_length=255)

    # responsibility for this model is up to etl_control_upload*.py so...
    class Meta:
        db_table = 'javascript_controls'
        managed = False
Пример #17
0
class WorldBankInvertedIndex(model.Model):
    id = ObjectIdField(primary_key=True, db_column="_id")
    country = model.TextField()
    topic_id = model.IntegerField()
    topic_name = model.TextField()
    n_attributes = model.IntegerField()
    last_updated = model.DateTimeField()
    tag = model.TextField()
    indicator_id = model.TextField()  # xref into WorldBankIndicators.wb_id
    indicator_name = model.TextField()  # likewise to name field

    objects = DjongoManager()

    class Meta:
        db_table = "world_bank_inverted_index"
        verbose_name_plural = "World Bank Inverted Indexes"
Пример #18
0
class CommitSerializer(serializers.ModelSerializer):
    _id = ObjectIdField()
    modifiedFiles = serializers.SerializerMethodField()
    user = CharField(max_length=128)
    hashID = CharField(max_length=40)
    email = EmailField()
    date = DateTimeField()
    message = CharField(max_length=510)

    class Meta:
        model = Commit
        exclude = ('_id', )

    def get_modifiedFiles(self, obj):
        resources_serializer = ModifiedFilesSerializer(obj.modifiedFiles,
                                                       many=True)
        return resources_serializer.data
Пример #19
0
class FeaturesModel(m.Model):
    # { "_id" : ObjectId("5ee1f3b8a4b98699c0917c8f"), "js_id" : "5e3cff0baf4b1411a1429ff4", "byte_content_sha256" : "bae214050996634a9a61dae7e12a3afd3fed131db5d90a14abfa66b49dcf7eea", "content_type" : "text/javascript", "inline" : false, "last_updated" : ISODate("2020-06-22T06:37:34.977Z"), "md5" : "2882cbfe23dc5802cf598e7a20409304", "origin" : "https://clubs.canberra.edu.au/Account/Register", "sha256" : "ff0ada03194dbd93b98c88e54b6e1637c2b4b4458907401f869187d90efd08ba", "size_bytes" : 23828, "url" : "https://clubs.canberra.edu.au/Scripts/expressive.annotations.validate.js", "when" : "2020-06-21 22:42:10.323637" }
    _id = ObjectIdField(db_column='_id')
    js_id = m.CharField(max_length=24)
    content_type = m.CharField(max_length=100)
    inline = m.BooleanField()
    last_updated = m.DateTimeField()
    md5 = m.CharField(blank=False, null=False, max_length=64)
    origin = m.CharField(max_length=1024)
    sha256 = m.CharField(blank=False, null=False, max_length=128)
    size_bytes = m.IntegerField()
    url = m.CharField(max_length=1024)
    when = m.CharField(max_length=64)
    analysis_bytes = m.BinaryField()

    class Meta:
        db_table = "analysis_content"
        managed = False
Пример #20
0
class Vehicles(models.Model):
    _id = ObjectIdField()
    estimated_price = models.IntegerField()
    car_name = models.CharField(max_length=100)
    miles = models.CharField(max_length=500)
    vin = models.CharField(max_length=500)
    body = models.CharField(max_length=500)
    primary_damage = models.CharField(max_length=500)
    secondary_damage = models.CharField(max_length=500)
    start_code = models.CharField(max_length=500)
    key_fob = models.CharField(max_length=500)
    airbags = models.CharField(max_length=500)
    car_image = models.CharField(max_length=500)
    url = models.CharField(max_length=500)
    value_est = models.FloatField()

    class Meta:
        db_table = "scrapy_items"
Пример #21
0
class VetAgainstControl(m.Model):
    # unlike ControlHit which is the "best" hit for a given control-origin pair, this is a point-in-time datapoint
    # { "_id" : ObjectId("5ecb5cbed7161ef4963d4498"), "origin_url" : "https://www.playfootball.com.au/sites/play/files/js/js_BaupD1b1RyIB49fG7a7PVMg8ZvbSJmHIlTWnZmKr9L8.js", "ast_dist" : Infinity, "cited_on" : "https://www.playfootball.com.au/?id=172", "control_url" : "", "diff_functions" : "", "diff_literals" : "", "function_dist" : Infinity, "literal_dist" : 0, "literals_not_in_control" : -1, "literals_not_in_origin" : -1, "n_diff_literals" : -1, "origin_js_id" : "5e8da771af53ffdf7ee0b88b", "sha256_matched" : false, "xref" : null, "origin_vectors_sha256" : "8edf3e27259d16c1430b28ee2ad609c0ebda25626d089f4fcc35e6e6911bee0c" }
    _id = ObjectIdField(db_column='_id')
    control_url = m.CharField(max_length=1024)
    origin_url = m.CharField(max_length=1024)
    ast_dist = m.FloatField()
    function_dist = m.FloatField()
    literal_dist = m.FloatField()
    literals_not_in_control = m.IntegerField()
    literals_not_in_origin = m.IntegerField()
    n_diff_literals = m.IntegerField()
    origin_js_id = m.CharField(max_length=24)
    xref = m.CharField(max_length=24)
    sha256_matched = m.BooleanField()

    class Meta:
        db_table = "vet_against_control"
        managed = False
Пример #22
0
class ImageCache(model.Model):
    # some images in viewer app are expensive to compute, so we cache them
    # and if less than a week old, use them rather than recompute. The views
    # automatically check the cache for expensive images eg. sector performance plot
    _id = ObjectIdField()
    base64 = model.TextField(max_length=400 * 1024, null=False, blank=False)
    tag = model.TextField(
        max_length=100, null=False,
        blank=False)  # used to identify if image is in ImageCache
    last_updated = model.DateTimeField(auto_now_add=True)
    valid_until = model.DateTimeField(
        auto_now_add=True
    )  # image is cached until is_outdated(), up to caller to set this value correctly

    def is_outdated(self):
        return self.last_updated > self.valid_until

    class Meta:
        managed = True
        db_table = "image_cache"
Пример #23
0
class ECBDataCache(models.Model):
    """ Similar to worldbank data cache """

    id = ObjectIdField(db_column="_id", primary_key=True)
    size_in_bytes = models.IntegerField()
    status = models.TextField()
    tag = models.TextField()
    dataframe_format = models.TextField()
    field = models.TextField()
    last_updated = models.DateTimeField()
    market = models.TextField()
    n_days = models.IntegerField()
    n_stocks = models.IntegerField()
    sha256 = models.TextField()
    scope = models.TextField()
    dataframe = models.BinaryField()

    objects = DjongoManager()

    class Meta:
        db_table = "ecb_data_cache"
        verbose_name = "ECB Data Cache"
        verbose_name_plural = "ECB Data Cache"
Пример #24
0
class ECBFlow(models.Model):
    """
    Describe each dataflow ingested from ECB SDMX REST API
    """

    id = ObjectIdField(db_column="_id", primary_key=True)
    name = models.TextField(db_column="flow_name", null=False, blank=False)
    description = models.TextField(db_column="flow_descr",
                                   null=False,
                                   blank=False)
    is_test_data = models.BooleanField()
    last_updated = models.DateTimeField()
    prepared = models.DateTimeField()
    sender = models.TextField()
    source = models.TextField()
    data_available = models.BooleanField()

    objects = DjongoManager()

    class Meta:
        db_table = "ecb_flow_index"
        verbose_name = "ECB Flow"
        verbose_name_plural = "ECB Flow"
Пример #25
0
class ControlHit(m.Model):
    _id = ObjectIdField(db_column='_id')
    control_url = m.CharField(blank=False, null=False, max_length=1024)
    origin_url = m.CharField(blank=False, null=False, max_length=1024)
    sha256_matched = m.BooleanField()
    ast_dist = m.FloatField()
    function_dist = m.FloatField()
    literal_dist = m.FloatField()
    cited_on = m.CharField(blank=False, null=False, max_length=1024)
    #origin_js_id = m.ForeignKey(Script, on_delete=m.DO_NOTHING)
    #xref = m.ForeignKey(VetAgainstControl, on_delete=m.DO_NOTHING)
    origin_js_id = m.CharField(blank=False, null=False, max_length=24)
    xref = m.CharField(blank=False, null=False, max_length=24)
    literals_not_in_control = m.IntegerField()
    literals_not_in_origin = m.IntegerField()
    n_diff_literals = m.IntegerField()
    diff_literals = m.CharField(null=False, max_length=1000000)
    origin_host = m.CharField(max_length=255)
    origin_has_query = m.BooleanField()
    origin_port = m.IntegerField()
    origin_scheme = m.CharField(max_length=10)
    origin_path = m.CharField(max_length=1024)
    cited_on_host = m.CharField(max_length=255)
    cited_on_has_query = m.BooleanField()
    cited_on_port = m.IntegerField()
    cited_on_scheme = m.CharField(max_length=10)
    cited_on_path = m.CharField(max_length=1024)
    control_family = m.CharField(max_length=255)
    diff_functions = JSONField()

    # permit direct access to mongo for convenient API
    objects = DjongoManager()

    # responsibility for this data is left entirely to etl_publish_hits.py
    class Meta:
        db_table = "etl_hits"
        managed = False
Пример #26
0
class ABSDataCache(model.Model):
    """
    Similar to, but separate from, app.MarketQuoteCache, this keeps track of pandas dataframes (parquet format) which have
    been downloaded, cleaned and ingested into MongoDB
    """

    size_in_bytes = model.IntegerField()
    status = model.TextField()
    tag = model.TextField()
    dataframe_format = model.TextField()
    field = model.TextField()
    last_updated = model.DateTimeField()
    market = model.TextField()
    n_days = model.IntegerField()
    n_stocks = model.IntegerField()
    sha256 = model.TextField()
    _id = ObjectIdField()
    scope = model.TextField()
    dataframe = model.BinaryField()

    objects = DjongoManager()

    class Meta:
        db_table = "abs_data_cache"
Пример #27
0
class Quotation(model.Model):
    _id = ObjectIdField()
    error_code = model.TextField(
        max_length=100)  # ignore record iff set to non-empty
    error_descr = model.TextField(max_length=100)
    fetch_date = model.TextField(
        null=False, blank=False)  # used as an index (string) always YYYY-mm-dd
    asx_code = model.TextField(blank=False, null=False, max_length=20)
    annual_dividend_yield = model.FloatField()
    average_daily_volume = model.IntegerField()
    bid_price = model.FloatField()
    change_in_percent = model.FloatField()
    change_price = model.FloatField()
    code = model.TextField(blank=False, null=False, max_length=20)
    day_high_price = model.FloatField()
    day_low_price = model.FloatField()
    deprecated_market_cap = (model.IntegerField()
                             )  # NB: deprecated use market_cap instead
    deprecated_number_of_shares = model.IntegerField()
    descr_full = model.TextField(max_length=100)  # eg. Ordinary Fully Paid
    eps = model.FloatField()
    isin_code = model.TextField(max_length=100)
    last_price = model.FloatField()
    last_trade_date = model.DateField()
    market_cap = model.IntegerField()
    number_of_shares = model.IntegerField()
    offer_price = model.FloatField()
    open_price = model.FloatField()
    pe = model.FloatField()
    previous_close_price = model.FloatField()
    previous_day_percentage_change = model.FloatField()
    suspended = model.BooleanField(default=False)
    volume = model.IntegerField()
    year_high_date = model.DateField()
    year_high_price = model.FloatField()
    year_low_date = model.DateField()
    year_low_price = model.FloatField()

    objects = DjongoManager()  # convenient access to mongo API

    def __str__(self):
        assert self is not None
        return str(model_to_dict(self))

    def is_error(self):
        if self.error_code is None:
            return False
        return len(self.error_code) > 0

    def eps_as_cents(self):
        if any([self.is_error(), self.eps is None]):
            return 0.0
        return self.eps * 100.0

    def volume_as_millions(self):
        """
        Return the volume as a formatted string (rounded to 2 decimal place)
        represent the millions of dollars transacted for a given quote
        """
        if any([self.is_error(), self.volume is None,
                self.last_price is None]):
            return ""

        return "{:.2f}".format(self.volume * self.last_price / 1000000.0)

    class Meta:
        db_table = "asx_prices"
        managed = False  # managed by asxtrade.py
        indexes = [
            model.Index(fields=["asx_code"]),
            model.Index(fields=["fetch_date"])
        ]
Пример #28
0
class CompanyDetails(model.Model):
    # { "_id" : ObjectId("5eff01d14b1fe020d5453e8f"), "asx_code" : "NIC", "delisting_date" : null,
    # "fax_number" : "02 9221 6333", "fiscal_year_end" : "31/12", "foreign_exempt" : false,
    # "industry_group_name" : "Materials",
    # "latest_annual_reports" : [ { "id" : "02229616", "document_release_date" : "2020-04-29T14:45:12+1000",
    # "document_date" : "2020-04-29T14:39:36+1000", "url" : "http://www.asx.com.au/asxpdf/20200429/pdf/44hc5731pmh9mw.pdf", "relative_url" : "/asxpdf/20200429/pdf/44hc5731pmh9mw.pdf", "header" : "Annual Report and Notice of AGM", "market_sensitive" : false, "number_of_pages" : 118, "size" : "4.0MB", "legacy_announcement" : false }, { "id" : "02209126", "document_release_date" : "2020-02-28T18:09:26+1100", "document_date" : "2020-02-28T18:06:25+1100", "url" : "http://www.asx.com.au/asxpdf/20200228/pdf/44fm8tp5qy0k7x.pdf", "relative_url" : "/asxpdf/20200228/pdf/44fm8tp5qy0k7x.pdf",
    # "header" : "Annual Report and Appendix 4E", "market_sensitive" : true, "number_of_pages" : 64,
    # "size" : "1.6MB", "legacy_announcement" : false }, { "id" : "02163933", "document_release_date" :
    # "2019-10-25T11:50:50+1100", "document_date" : "2019-10-25T11:48:43+1100",
    # "url" : "http://www.asx.com.au/asxpdf/20191025/pdf/449w6d0phvgr05.pdf",
    # "relative_url" : "/asxpdf/20191025/pdf/449w6d0phvgr05.pdf", "header" : "Annual Report and Notice of AGM",
    # "market_sensitive" : false, "number_of_pages" : 74, "size" : "2.5MB", "legacy_announcement" : false } ],
    # "listing_date" : "2018-08-20T00:00:00+1000",
    # "mailing_address" : "Level 2, 66 Hunter Street, SYDNEY, NSW, AUSTRALIA, 2000",
    # "name_abbrev" : "NICKELMINESLIMITED", "name_full" : "NICKEL MINES LIMITED",
    # "name_short" : "NICKLMINES", "phone_number" : "02 9300 3311",
    # "primary_share" : { "code" : "NIC", "isin_code" : "AU0000018236", "desc_full" : "Ordinary Fully Paid",
    # "last_price" : 0.61, "open_price" : 0.595, "day_high_price" : 0.615, "day_low_price" : 0.585,
    # "change_price" : 0.02, "change_in_percent" : "3.39%", "volume" : 3127893, "bid_price" : 0.605,
    # "offer_price" : 0.61, "previous_close_price" : 0.59, "previous_day_percentage_change" : "1.724%",
    # "year_high_price" : 0.731, "last_trade_date" : "2020-07-24T00:00:00+1000",
    # "year_high_date" : "2019-09-24T00:00:00+1000", "year_low_price" : 0.293,
    # "year_low_date" : "2020-03-18T00:00:00+1100", "pe" : 8.73, "eps" : 0.0699,
    # "average_daily_volume" : 7504062, "annual_dividend_yield" : 0, "market_cap" : 1255578789,
    # "number_of_shares" : 2128099642, "deprecated_market_cap" : 1127131000,
    # "deprecated_number_of_shares" : 1847755410, "suspended" : false,
    # "indices" : [ { "index_code" : "XKO", "name_full" : "S&P/ASX 300", "name_short" : "S&P/ASX300",
    # "name_abrev" : "S&P/ASX 300" }, { "index_code" : "XAO", "name_full" : "ALL ORDINARIES",
    # "name_short" : "ALL ORDS", "name_abrev" : "All Ordinaries" }, { "index_code" : "XSO",
    # "name_full" : "S&P/ASX SMALL ORDINARIES", "name_short" : "Small Ords",
    # "name_abrev" : "S&P/ASX Small Ords" }, { "index_code" : "XMM",
    # "name_full" : "S&P/ASX 300 Metals and Mining (Industry)", "name_short" : "MTL&MINING",
    # "name_abrev" : "Metals and Mining" } ] }, "primary_share_code" : "NIC",
    # "principal_activities" : "Nickel pig iron and nickel ore production.",
    # "products" : [ "shares" ], "recent_announcement" : false,
    # "registry_address" : "Level 3, 60 Carrington Street, SYDNEY, NSW, AUSTRALIA, 2000",
    # "registry_name" : "COMPUTERSHARE INVESTOR SERVICES PTY LIMITED",
    # "registry_phone_number" : "02 8234 5000", "sector_name" : "Metals & Mining",
    # "web_address" : "http://www.nickelmines.com.au/" }
    _id = ObjectIdField()
    asx_code = model.TextField(null=False, blank=False)
    delisting_date = model.TextField(null=True)  # if null, not delisted
    name_full = model.TextField(null=False, blank=False)
    phone_number = model.TextField(null=False, blank=True)
    bid_price = model.FloatField()
    offer_price = model.FloatField()
    latest_annual_reports = JSONField()
    previous_close_price = model.FloatField()
    average_daily_volume = model.IntegerField()
    number_of_shares = model.IntegerField()
    suspended = model.BooleanField()
    indices = JSONField()
    primary_share_code = model.TextField()
    principal_activities = model.TextField()
    products = JSONField()
    recent_announcement = model.BooleanField()
    registry_address = model.TextField()
    registry_name = model.TextField()
    registry_phone_number = model.TextField()
    sector_name = model.TextField()
    web_address = model.TextField()

    objects = DjongoManager()

    class Meta:
        managed = False
        db_table = "asx_company_details"
        verbose_name = "Company Detail"
        verbose_name_plural = "Company Details"