Skip to content
All library documents

Using Custom Security Properties for Indicators and Fee Models

Article Strategy library · Author: QuantConnect

Summary

This example shows how to attach custom data to a security object in a trading algorithm and retrieve it through different interfaces. It stores two exponential moving averages as properties and a Bollinger Bands indicator through an indexer. The strategy uses the moving averages to enter a long position when the slow average is above the fast one, then liquidates when their relationship reverses. It also plots the Bollinger Bands.

Key ideas

  • Security objects can hold custom properties such as indicators and fee parameters.
  • The example demonstrates dynamic-property, generic add/get, and indexer access patterns.
  • A moving-average relationship controls entries and exits for SPY.
  • A custom fee model reads a fee factor from the security and records the price used in its calculation.
  • Order events compare charged fees with expected fees, while the example checks that at least one order occurred.

Tags

Full text
# SecurityCustomPropertiesAlgorithm


# SecurityCustomPropertiesAlgorithm









Demonstration of how to use custom security properties.
    In this algorithm we trade a security based on the values of a slow and fast EMAs which are stored in the security itself.

Demonstration of how to use custom security properties. In this algorithm we trade a security based on the values of a slow and fast EMAs which are stored in the security itself.

## Source (Apache-2.0)

```python
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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 cmath import isclose
from AlgorithmImports import *

### <summary>
### Demonstration of how to use custom security properties.
### In this algorithm we trade a security based on the values of a slow and fast EMAs which are stored in the security itself.
### </summary>
class SecurityCustomPropertiesAlgorithm(QCAlgorithm):
    '''Demonstration of how to use custom security properties.
    In this algorithm we trade a security based on the values of a slow and fast EMAs which are stored in the security itself.'''

    def initialize(self):
        self.set_start_date(2013,10, 7)
        self.set_end_date(2013,10,11)
        self.set_cash(100000)

        self.spy = self.add_equity("SPY", Resolution.MINUTE)

        # Using the dynamic interface to store our indicator as a custom property.
        self.spy.slow_ema = self.ema(self.spy.symbol, 30, Resolution.MINUTE)

        # Using the generic interface to store our indicator as a custom property.
        self.spy.add("fast_ema", self.ema(self.spy.symbol, 60, Resolution.MINUTE))

        # Using the indexer to store our indicator as a custom property
        self.spy["bb"] = self.bb(self.spy.symbol, 20, 1, MovingAverageType.SIMPLE, Resolution.MINUTE)

        # Fee factor to be used by the custom fee model
        self.spy.fee_factor = 0.00002
        self.spy.set_fee_model(CustomFeeModel())

        # This property will be used to store the prices used to calculate the fees in order to assert the correct fee factor is used.
        self.spy.orders_fees_prices = {}

    def on_data(self, data):
        if not self.spy.fast_ema.is_ready:
            return

        if not self.portfolio.invested:
            # Using the property and the generic interface to access our indicator
            if self.spy.slow_ema > self.spy.fast_ema:
                self.set_holdings(self.spy.symbol, 1)
        else:
            if self.spy.get[ExponentialMovingAverage]("slow_ema") < self.spy.get[ExponentialMovingAverage]("fast_ema"):
                self.liquidate(self.spy.symbol)

        # Using the indexer to access our indicator
        bb: BollingerBands = self.spy["bb"]
        self.plot("bb", bb.upper_band, bb.middle_band, bb.lower_band)

    def on_order_event(self, order_event):
        if order_event.status == OrderStatus.FILLED:
            fee = order_event.order_fee
            expected_fee = self.spy.orders_fees_prices[order_event.order_id] * order_event.absolute_fill_quantity * self.spy.fee_factor
            if not isclose(fee.value.amount, expected_fee, rel_tol=1e-15):
                raise AssertionError(f"Custom fee model failed to set the correct fee. Expected: {expected_fee}. Actual: {fee.value.amount}")

    def on_end_of_algorithm(self):
        if self.transactions.orders_count == 0:
            raise AssertionError("No orders executed")

class CustomFeeModel(FeeModel):
    '''This custom fee is implemented for demonstration purposes only.'''

    def get_order_fee(self, parameters):
        security = parameters.security
        # custom fee math using the fee factor stored in security instance
        fee_factor = security.fee_factor
        if fee_factor is None:
            fee_factor = 0.00001

        # Store the price used to calculate the fee for this order
        security["orders_fees_prices"][parameters.order.id] = security.price

        fee = max(1.0, security.price * parameters.order.absolute_quantity * fee_factor)

        return OrderFee(CashAmount(fee, "USD"))

```

Shown in full with attribution under the source's licence. Licence: Apache-2.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.