Any trading application should support sending orders to the market. When an order is created or sent, it can be fully or partially executed, and deals related to these order shall be created. This order may also become a part of buying or selling strategy. For example, when CAC40 is used, 40 different orders for buying or selling relevant instruments of this index are generated. While the orders are executed, additional orders for buying or selling protective instruments (e.g. index futures) can be created. All this information should be tracked in real time. It should also be presented in hierarchical view and should support rapid change of hierarchy levels. For example, when an index buying strategy is executed, user may want to view all deals that have been made when buying every instrument that is a part of index. User might not be interested in orders by themeselves, i.e. orders level should be skipped and deals should be presented as strategy nodes.

Application business model contains Strategy, Order and Deal classes. This order contains Deal collection that can be added to the business model upon execution of Orders in non-GUI thread. Orders may also be combined in strategies. Order and Deal collections use thread-safe ThreadSafeBindingList<T> collections that are a part of Dapfor library. Strategy, Order and Deal classes implement INotifyPropertyChnaged interface. When a new elements is added to collection, IBindingList subscribes to PropertyChanged notifications of these objects. When INotifyPropertyChanged notification is added, removed or incoming, standard IBindingList implementation generates ListChanged event. Therefore, every strategy can be subscribed to Order change events that in turn subscribe to Deal change events.

The business model is provided below

Order Book 1

The list of strategies and orders is placed in Provider class created earlier.

C# Copy imageCopy
public class Provider
{
    private readonly ThreadSafeBindingList<Order> _singleOrders = new ThreadSafeBindingList<Order>();
    private readonly ThreadSafeBindingList<Strategy> _strategies = new ThreadSafeBindingList<Strategy>();

    public IList<Order> SingleOrders
    {
        get { return _singleOrders; }
    }

    public IList<Strategy> Strategies
    {
        get { return _strategies; }
    }
}

...
IList<Strategy> strategies = Provider.Instance.Strategies;

Now let’s create a new control to display Order book in real time. This control will create filters for displaying strategies, orders and deals bypassing various hierarchy levels and filtering instrument data. We shall also add three buttons that enable creatio of new orders, strategies and deals.

Order Book 2

To build a hierarchy, we can use one of grid features – HierarchicalField. When objects are added, the grid checks whether the added object contains a hierarchical field. If such field exists, the grid gets value of this field. When object returned by property implements IList, the grid adds content as children rows of previously added object. If the returned object also implements INotifyPropertyChanged interface, the grid subscribes to ListChanged events and automatically updates hierarchical content.

To declare a field as a hierarchy field, we shall use HierarchicalFieldAttribute.

C# Copy imageCopy
public class Strategy : INotifyPropertyChanged
{
    private readonly ThreadSafeBindingList<Order> _orders = new ThreadSafeBindingList<Order>();

    [HierarchicalField]
    public IList<Order> Orders
    {
        get { return _orders; }
    }
}

public class Order : INotifyPropertyChanged
{
    private readonly ThreadSafeBindingList<Deal> _deals = new ThreadSafeBindingList<Deal>();

    [HierarchicalField]
    public IList<Deal> Deals
    {
        get { return _deals; }
    }
}

public class Deal
{
    private readonly Order _order;
    ...
}

Besides hierarchical fields, added objects may be composite, i.e. combine properties of multiple classes. This can be convenient for referencing instruments that were subject to transactions.

C# Copy imageCopy
public class Order : INotifyPropertyChanged
{
    private readonly Instrument _instrument;

    [CompositeField]
    public Instrument Instrument
    {
        get { return _instrument; }
    }    
}

Implemented changes do not impact the business model and ensure significant reduction of memory consumption because there are no objects with duplicate information. Let's bind the control to multiple data sources:

C# Copy imageCopy
public class MultipleDataSources : IListSource
{
    private readonly IList _sources;

    public MultipleDataSources(IList<Order> orders, IList<Strategy> strategies)
    {
        _sources = new ArrayList();
        _sources.Add(orders);
        _sources.Add(strategies);
    }

    public IList GetList()
    {
        return _sources;
    }

    public bool ContainsListCollection
    {
        get { return true; }
    }
}

grid.DataSource = new MultipleDataSources(Provider.Instance.SingleOrders, Provider.Instance.Strategies);

A screenshot of application with strategies, orders and deals is provided below.

Order Book 3

Now let’s work on filter implementation. First we shall implement a hierarchical filter. The grid provides a powerful feature of conditional binding that can be used to build any hierarchy. This feature can also remove various levels from the hierarchy. This binding type is based on Grid.RowAdding event that is fired every time when a new row is added to the grid. This happens disregarding whether it was added via Grid.DataSource or via Grid.Rows.Add()/Row.Add() method calls. This event can be used to tell the grid how to process the inserted object.

C# Copy imageCopy
grid.RowAdding += delegate(object sender, GridRowAddingEventArgs e)
{
    Strategy basket = e.DataObject as Strategy;
    if (basket != null)
    {
        e.DataObject = basket.Orders;
        e.InsertionType = !cbSkipStrategies.Checked
                              ? InsertionType.AsChildAndCollection
                              : InsertionType.AsCollection;
    }
    Order order = e.DataObject as Order;
    if (order != null)
    {
        e.DataObject = order.Deals;
        e.InsertionType = !cbSkipOrders.Checked
                              ? InsertionType.AsChildAndCollection
                              : InsertionType.AsCollection;
    }
}

Now we have to rebind the data source to the grid upon user settings change.

C# Copy imageCopy
cbSkipOrders.CheckedChanged += new System.EventHandler(RefreshDataSource);
cbSkipStrategies.CheckedChanged += new System.EventHandler(RefreshDataSource);

private void RefreshDataSource(object sender, EventArgs e)
{
    Object dataSource = grid.DataSource;
    grid.DataSource = null;
    grid.DataSource = dataSource;
}
Order Book 4

If we look at the structure of business objects in .Net Inspector, it will show that Strategy #1 has a collection of 2 Orders. The first order has 2 deals and the second order has only one deal. The figure below demonstrates it very well.

Order Book 5

Now let's review implementation of a product data filter. For this purpose the grid provides a convenient IFilter interface with a single method that is called every time when new data is added to the grid or the grid gets notifications from INotifyPropertyChnaged or IBindingList interfaces.

C# Copy imageCopy
grid.Filter = new Filter(OnFilterRow);

private bool OnFilterRow(Row row)
{
    bool isFiltered = false;
    if (!string.IsNullOrEmpty(tbProduct.Text))
    {
        //Check for the deal
        Deal deal = row.DataObject as Deal;
        if (deal != null)
        {
            isFiltered = IsDealFiltered(deal);
        }

        //Check for the order
        Order order = row.DataObject as Order;
        if (order != null)
        {
            isFiltered = IsOrderFiltered(order);
        }

        //Check for the strategy
        Strategy strategy = row.DataObject as Strategy;
        if (strategy != null)
        {
            foreach (Order item in strategy.Orders)
            {
                isFiltered = IsOrderFiltered(item);
                if(!isFiltered)
                {
                    break;
                }
            }    
        }
    }
    return isFiltered;
}

The above code will work disregarding hierarchy levels and data presentation.

Order Book 6

As it has already been mentioned above, the trading application should support sending orders to market. User should also be able to cancel orders in case of high volatility or if the order price becomes unsatisfactory. This rule should also apply to all orders that are part of strategies. We shall create and editor to demonstrate grid features. This editor shows user information of orders and provides tools for manipulating these orders.

C# Copy imageCopy
 public class OrderStatusEditor : UITypeEditorEx
{
    public override bool GetPaintCellSupported()
    {
        return true;
    }

    public override void PaintCell(PaintCellEventArgs e)
    {
        Order order = e.Cell.Row.DataObject as Order;
        if (order != null)
        {
            e.Parts ^= e.Parts & PaintPart.Text;
            e.PaintAll();
            e.Handled = true;

            Rectangle bounds = e.Cell.VirtualBounds;
            bounds.Inflate(-2, -1);

            Color backColor = Color.LightGreen;
            string text = "Stop";
            switch (order.Status)
            {
                case Status.Created:
                case Status.Cancelled:
                    backColor = ControlPaint.LightLight(Color.OrangeRed);
                    text = "Start";
                    break;
            }

            e.Render.DrawCaption(new Appearance(SystemColors.WindowText, backColor), ElementState.Normal, bounds, BorderSide.All, e.Graphics);
            e.PaintText(text, bounds);
        }
    }

    public override StopEditReason EditCell(IGridEditorService service, Cell cell, StartEditReason reason)
    {
        Order order = cell.Row.DataObject as Order;
        if (order != null)
        {
            switch (order.Status)
            {
                case Status.Created:
                case Status.Cancelled:
                    order.Status = Status.Active;
                    break;

                case Status.Active:
                    order.Status = Status.Cancelled;
                    break;
            }
        }
        return StopEditReason.UserStop;
    }
}

There are multiple ways to install an editor. One of this ways is using attributes over data object properties. One of the advantages of such approach is ability to add objects to any grid. This feature supports composite objects and hierarchical fields. In such case an editor related to this business object is used.

C# Copy imageCopy
public class Order : INotifyPropertyChanged
{
    private Status _status;

    [Editor(typeof(OrderStatusEditor), typeof(UITypeEditor))]
    public Status Status
    {
        get { return _status; }
        set
        {
            if (!Equals(_status, value))
            {
                _status = value;
                FirePropertyChanged("Status");
            }
        }
    }
}

Now let’s add a similar editor for Strategies editing. Let’s note that code for stopping or launching strategies and orders is placed in the editor and not in a business object. In our model business objects reflect market conditions and their state should change basing on market notifications and not on user input. However, there is nothing that prevents placement of this code in the business object.

The example below demonstrates work of editors.

Order Book 7

To improve usability we shall also add a small piece of code that prevents hierarchy expansion or collapse on double-click in editor cell.

C# Copy imageCopy
grid.RowExpansionChanging += delegate(object sender, GridRowExpansionEventArgs e)
{
    Cell cell = e.Grid.HitTests.CellTest(e.Grid.PointToClient(Cursor.Position));
    if (cell != null && cell.Column != null && cell.Column.Id == "Status")
    {
        e.Cancel = true;
    }
}

Now all we have to do is to work a bit on data appearance. A screenshot of application with modified appearance and standard Microsoft styles is shown below.

Order Book 8

Summary.

.Net Grid provides very convenient practical tools enabling different methods of interpreting application business logic. They can easily change hierarchy, filtering and data presentation. The objective of this tutorial was to introduce the grid to the programmer and to provide practical recommendations on separating business logic from its presentation.

When business logic is used directly in the grids, memory and CPU resource consumption is significantly reduced. It is recommended to use composite objects to ensure better data presentation and remove duplicate code, and hierarchy fields to automatically build hierarchies. Let’s note that today there are almost no grids that support objects implementing INotifyPropertyChanged interface. IBindingList is the main method of getting notifications. Besides, existing grids don’t support receiving IBindingList notifications in non-GUI thread, which significantly limits their use.

Nevertheless, INotifyPropertyChanged and IBindingList are unique features that significantly reduce volume of application code, shorten development time and improve code visibility. This concerns large applications with huge volumes of code and non-transparent workflow. This causes time loss and negatively impacts the application.

Finally, let’s say a few words of performance. People think that using INotifyPropertyChanged causes overuse of CPU resources. However, this is not true. When this interface is used, neither business logic nor the grid use reflection. Besides, the grid gets notifications containing links to modified business object. Therefore, the grid doesn’t have to search for this object in its internal structure consuming high processor resources. So, if data volume is less than 100,000 elements, it is recommended to use INotifyPropertyChanged for process automation.

We hope that this tutorial was useful for better understanding .Net Grid API and its practical use not only in real-time applications, but also everywhere, where a convenient and efficient API is needed to represent various data.