Witam

Niedawno zakończyłem pisanie oprogramowania symulującego strategie inwestycyjne w architekturze MVVM. Teraz dodaje nowe funkcjonalności oraz mocno refactoruje stary kod i pomyślałem, że może dobrym pomysłem było by poproszenie o analizę zastosowanych rozwiązań.

Żeby naświetlić cel oprogramowania pokażę zrzut interfejsu z ostatniej wersji:

user image

W lewym panelu znajdują się notowania wybranych instrumentów finansowych.
W prawym dolnym użytkownik w języku skryptowym pisze strategię.
U góry przedstawiony jest rezultat symulacji strategii użytkownika.

Od strony modelu wygląda to tak:
Mamy 3 rodzaje kolekcji:

  • Ticker - modeluje notowania instrumentu finansowego.
  • MultiTicker - modeluje scalone notowania wielu instrumentów finansowych tak, by miały wspólny przedział czasowy.
  • Result - wynik symulacji strategii.

user image

Wyżej opisane kolekcje składają się z następujących elementów:
Kolekcja Ticker składa się z obiektów TickerQuote, które modelują datę notowania oraz wartość instrumentu w tym czasie.
MultiTickerQuote rezeprentuje notowania wielu instrumentów w określonym czasie.
ResultQuote dokłada do MultiTickerQuote informację o procentowym udziale aktywów w portfelu (TickerShares), przeprowadzonych transakcjach (Transactions) oraz wycenie całego portfela (Value)

user image

Wszystkie elementy są świadome kolekcji w których się znajdują (BaseQuoteCollection w konstruktorze przypisuje swoją instancję do pola Parent hostowanych elementów).

Zastanawia mnie czy zalety tego rozwiązania przewyższają koszty.
Z jednej strony mogę później ładnie delegować operacje na elementach do rodzica - tak jest rozwiązane wskazywanie po nazwie w MultiTickerProxy
(this[string tickerName] sprawdza u rodzica jaki index ma przekazany łańcuch, by później móc odpowiednio zwrócić wartość z tablicy tickerValues).
Z drugiej strony zastanawiam się, czy to nie tylko wygodnictwo zaciemniające rozwiązanie i odbijające się na wydajności.
W końcu jeśli gdzieś pracuję z kolekcją, to znam rodzica hostowanych elementów.

Na kolekcjach i ich elementach pracuje klasa Simulator, która jest sercem modelu. Po przekazaniu do metody RunAsync scalonych notowań instrumentów finansowych oraz strategii gry wyrażonej w postaci skryptu, wykonywane są obliczenia. Swoim zachowaniem przypominać może BackgroundWorkera.
SimulationParameters oraz SimulationEndEventArgs robią to co sugeruje ich nazwa.

user image

Wallet to proxy, przez które użytkownik porozumiewa się z obiektem Simulator.
Przykładowo użytkownik napisał strategię, która w pewnym miejscu pozbywa się wszystkich akcji 'facebook'.
Swoją intencję w skrypcie wyrazi pisząc:

Wallet['facebook'] = 0

(W przestrzeni w której działa skrypt jest dostepny obiekt również o nazwie Wallet).

Jeżeli ktoś woli spojrzeć na projekt z poziomu Visual Studio to jest wrzucony w załączniku. Mało w nim jednak implementacji, a XMLowe opisy mogły się pomieszać.
Będę wdzięczny za wszelkie uwagi :-)

EDIT:
Jak teraz piszę testy to się zastanawiam czy dobrą politykę z tymi wieloma internalami wybrałem. Starałem się wprowadzić maksymalnie restrykcyjny dostęp, by korzystający z biblioteki tworzyli tylko to co jest absolutnie potrzebne i używali tego tylko w przewidziany sposób. Niestety żeby sprawdzić poprawność udostępnianych obiektów test musi przechodzić często cała ścieżkę symulacji.

Pozdrawiam
Kuba