Investment Trading Simulator

Technical Documentation — Project Handoff & Portfolio Reference

Laravel 12 PHP 8.2 SQLite v1.0 — March 2026 Beginner Practice Platform
Project Purpose: A fully functional web-based trading simulator for beginners to practise investment decisions using virtual money — no real funds involved. Users explore live simulated price charts, execute buy/sell trades, track portfolio performance, and complete a guided tutorial. The system deliberately implements custom data structures and algorithms from scratch to demonstrate core computer-science concepts.

1. Technology Stack

Backend
Laravel 12 (PHP 8.2+)
Database
SQLite
Frontend
Blade + Vite
Auth
Fully Manual (Custom)
Testing
PHPUnit 11
Packages
Composer + npm

Composer Dependencies

PackageVersionPurpose
laravel/framework^12.0Core framework
laravel/tinker^2.10REPL for dev tasks
phpunit/phpunit^11.5 (dev)Testing
laravel/pint^1.24 (dev)Code style linter

Note: No third-party packages are used for authentication, password hashing, sorting, searching, or graph traversal — all implemented from scratch.

2. High-Level Architecture

The application follows MVC (Model-View-Controller) augmented with a Service Layer and a custom Support layer for data structures and algorithms.

Browser (User/Admin)HTTP Requests
Router (routes/web.php)Guest | auth.manual | auth.manual + role:admin
Middleware LayerManualAuthenticate — checks session user_id | EnsureUserHasRole — checks session user_role
Controller LayerAuthController, DashboardController, MarketController, TradeController, PortfolioController, SessionSummaryController, MarketHistoryController, TutorialController, AdminController, AssetAdminController
Service LayerPriceEngine, MarketState, SimulationSessionManager, PasswordHasher
Support LayerSimpleList, SimpleQueue, SimpleStack, MergeSort, BinarySearch, TutorialGraph
Model Layer (ORM)User, Portfolio, Asset, Candle, Trade, Holding, SimulationSession, TutorialProgress
Database (SQLite)users, portfolios, assets, candles, trades, holdings, simulation_sessions, tutorial_progress, sessions, cache, jobs

Data Flow: A Typical Trade

1
User submits buy/sell form on the Market page.
2
POST /market/tradeTradeController@store
3
ManualAuthenticate verifies user_id in session.
4
Validate: asset_id, side (buy|sell), quantity (0.01–1,000,000).
5
DB transaction: check balance/holdings → update balance → update Holding (weighted avg cost) → create Trade → sync SimulationSession balance.
6
Redirect to market with flash message.

Data Flow: Market Tick (Price Simulation)

1
Browser JS polls GET /market/tick every few seconds.
2
MarketController@tickPriceEngine@tick()
3
For each active asset: noise_impact + trade_impact → new price (floor $0.01) → append to SimpleList tick buffer.
4
Every 5 ticks: closeCandle() → compute OHLCV → persist Candle to DB → reset tick buffer.
5
Returns JSON { "prices": { "assetId": latestPrice } } for DOM update.

3. Directory Structure

investment-trading-simulator/
├── app/
│   ├── Http/
│   │   ├── Controllers/        (10 controller files)
│   │   └── Middleware/         ManualAuthenticate.php, EnsureUserHasRole.php
│   ├── Models/             (8 Eloquent model files)
│   ├── Providers/          AppServiceProvider.php
│   ├── Services/           PriceEngine.php, MarketState.php,
│   │                       SimulationSessionManager.php, PasswordHasher.php
│   └── Support/
│       ├── Algorithms/         BinarySearch.php, MergeSort.php
│       ├── Collections/        SimpleList.php, SimpleQueue.php, SimpleStack.php
│       └── Graphs/             TutorialGraph.php
├── config/             app, auth, cache, database, logging...
├── database/
│   ├── migrations/         (11 migration files)
│   ├── seeders/            AssetSeeder.php, DatabaseSeeder.php
│   └── database.sqlite     SQLite database file
├── public/             Web root: index.php, compiled CSS/JS
├── resources/
│   ├── views/
│       ├── admin/          users.blade.php, assets/index.blade.php
│       ├── auth/           login.blade.php, register.blade.php
│       ├── dashboard/      user, admin, portfolio, session-summary
│       ├── layouts/        app.blade.php (master layout)
│       ├── market/         live, charts, chart, history
│       ├── partials/       sidebar.blade.php
│       └── tutorial/       index.blade.php, step.blade.php
├── routes/web.php      All application routes
├── tests/              Feature/ and Unit/ stubs
├── .env.example
├── composer.json
└── vite.config.js

4. Installation & Setup Guide

Prerequisites

PHP 8.2+ with: pdo, pdo_sqlite, mbstring, xml, openssl, tokenizer  •  Composer  •  Node.js 18+ & npm  •  Git

1

Clone the Repository

git clone <repository-url> investment-trading-simulator
cd investment-trading-simulator
2

Automated Setup (Recommended)

composer run setup

Runs: composer install → copy .env → generate app key → migrate DB → npm install → npm run build

3

Seed the Database

php artisan db:seed

Inserts 4 practice assets: BLUE, TECH, REIT, BTC.

4

Start the Development Server

composer run dev

Starts PHP server at http://127.0.0.1:8000, queue worker, log watcher, Vite hot-reload.

5

Create an Admin User (via Tinker)

php artisan tinker
>>> $h = (new App\Services\PasswordHasher)->hash('password');
>>> App\Models\User::create([
...   'name' => 'Admin', 'email' => 'admin@example.com',
...   'password' => $h['hash'], 'password_salt' => $h['salt'],
...   'role' => 'admin', 'initial_balance' => 1000, 'current_balance' => 1000
... ]);

Regular users register at /register and receive $1,000 virtual cash automatically.

Key Environment Variables

VariableDefaultDescription
APP_KEY(generated)Application encryption key
DB_CONNECTIONsqliteFile-based SQLite database
SESSION_DRIVERdatabaseSession storage backend
QUEUE_CONNECTIONdatabaseJob queue backend
APP_DEBUGtrueSet to false in production

5. Database Design

Entity-Relationship Overview

USER
 ├── has one  ──► PORTFOLIO
 ├── has many ──► HOLDING ──► belongs to ──► ASSET
 ├── has many ──► TRADE   ──► belongs to ──► ASSET
 │                └───────── belongs to ──► SIMULATION_SESSION
 ├── has many ──► SIMULATION_SESSION
 └── has many ──► TUTORIAL_PROGRESS

ASSET
 └── has many ──► CANDLE

Table: users

ColumnTypeNotes
idbigint PKAuto-increment
namevarcharDisplay name
emailvarchar UNIQUELogin identifier
passwordvarcharSHA-256 iterated hash (1000 rounds)
password_saltvarchar(64)Random 32-char hex salt (unique per user)
rolevarchar(20)‘user’ or ‘admin’. Default: ‘user’
initial_balancedecimal(15,2)Starting budget. Default: $1,000.00
current_balancedecimal(15,2)Live cash balance

Table: assets

ColumnTypeNotes
symbolvarchar UNIQUETicker e.g. ‘BLUE’, ‘BTC’
base_pricedecimal(15,2)Starting/reference price
volatilitydecimal(8,4)Max swing fraction. 0.07 = 7% per tick
liquiditydecimal(15,2)Volume before significant price impact
behaviour_profilevarchar‘stable’,‘growth’,‘defensive’,‘crypto’
is_cryptobooleanTrue for cryptocurrency assets
is_activebooleanFalse = disabled from trading

Table: candles

ColumnTypeNotes
asset_idbigint FKReferences assets.id (cascade)
time_indexbigint uintSequential candle number (0,1,2…)
opendecimal(15,4)First tick price in period
highdecimal(15,4)Maximum tick price in period
lowdecimal(15,4)Minimum tick price in period
closedecimal(15,4)Last tick price in period
volumedecimal(18,4)Trade volume (currently 0, placeholder)
drivervarchar‘user’, ‘noise’, or ‘mixed’
UNIQUE: (asset_id, time_index)

Table: trades

ColumnTypeNotes
user_idbigint FKReferences users.id
asset_idbigint FKReferences assets.id
sidevarchar‘buy’ or ‘sell’
quantitydecimal(18,4)Units traded (min 0.01)
pricedecimal(15,4)Execution price per unit
simulation_session_idbigint nullableLinks to active session
statusvarchar‘executed’,‘pending’,‘cancelled’
executed_attimestampTime of execution
INDEX: (user_id, asset_id), (simulation_session_id)

Table: holdings

ColumnTypeNotes
user_idbigint FKReferences users.id
asset_idbigint FKReferences assets.id
quantitydecimal(18,4)Units currently owned
average_costdecimal(15,4)Weighted average purchase price
UNIQUE: (user_id, asset_id)

Table: simulation_sessions

ColumnTypeNotes
user_idbigint FKReferences users.id
starting_balancedecimal(15,2)Balance when session began
current_balancedecimal(15,2)Synced after every trade
modevarchar‘standard’, ‘tutorial’, ‘scenario’
started_attimestampSession start time
ended_attimestamp NULLNULL = session currently active

Table: tutorial_progress

ColumnTypeNotes
user_idbigint FKReferences users.id
step_keyvarchar‘intro’,‘candles’,‘risk’,‘practice’
completedbooleanHas user marked this done?
completed_attimestamp NULLWhen it was completed
UNIQUE: (user_id, step_key)

6. Routing Map

Guest Routes

MethodURIHandler
GET/Redirect to /login
GET/registerAuthController@showRegister
POST/registerAuthController@register
GET/loginAuthController@showLogin
POST/loginAuthController@login

Authenticated Routes (middleware: auth.manual)

MethodURIHandler
GET/dashboardDashboardController@index
GET/marketMarketController@index
GET/market/chartsMarketController@charts
GET/market/tickMarketController@tick — JSON
GET/market/history/{asset}MarketHistoryController@show
GET/market/history/{asset}/jsonMarketHistoryController@json — JSON
GET/market/chart/{asset}market.chart view
POST/market/tradeTradeController@store
GET/portfolioPortfolioController@index
GET/session/summarySessionSummaryController@show
POST/session/completeSessionSummaryController@complete
GET/tutorialTutorialController@index
GET/tutorial/step/{step}TutorialController@show
POST/tutorial/step/{step}/completeTutorialController@complete
POST/logoutAuthController@logout

Admin Routes (middleware: auth.manual + role:admin)

MethodURIHandler
GET/admin/dashboardDashboardController@admin
GET/admin/usersAdminController@index
GET/admin/assetsAssetAdminController@index
POST/admin/assetsAssetAdminController@store
PUT/admin/assets/{id}AssetAdminController@update

7. Controllers

ControllerMethods & Responsibilities
AuthControllershowRegister() — return form. register() — manual validation (name≥3, email regex, password≥6, match, unique), hash with PasswordHasher, create User ($1000 balance) + Portfolio. showLogin() — return form. login() — verify hash, store session keys, route by role. logout() — clear session, regenerate CSRF.
DashboardControllerindex() — load user+portfolio+holdings, calculate totalValue, unrealizedPnL, overallPnL&%, get/create session. admin() — admin welcome view.
MarketControllerindex() — live market view. charts() — charts hub. tick() — advance PriceEngine, return JSON prices.
TradeControllerstore() — validate, DB transaction: BUY checks balance, deducts cost, updates Holding (weighted avg), saves Trade; SELL checks qty, reduces Holding, adds proceeds, saves Trade. Syncs session.
PortfolioControllerindex() — all holdings, last 20 trades, compute unrealized/realized/overall P&L.
SessionSummaryControllershow() — session trades, compute counts/cashflow/avg size/P&L. complete() — set ended_at=now().
MarketHistoryControllershow() — fetch candles, MergeSort by time_index, optional BinarySearch by ?time_index=N. json() — same + return JSON.
TutorialControllerindex() — TutorialGraph DFS order, load progress. show() — single step view. complete() — mark done, find next via DFS, redirect.
AdminControllerindex() — all users, platform totals (users, trades, assets, balances).
AssetAdminControllerindex() list assets. store() create asset. update() update price/volatility/liquidity/active.

8. Models & Relationships

ModelRelationshipsKey Notes
UserhasOne Portfolio; hasMany Holding, Trade, SimulationSessionNOT Authenticatable. password+salt hidden from JSON.
PortfoliobelongsTo UserOne per user. Total invested + P&L.
AssethasMany Candle, Trade, HoldingVolatility & liquidity drive price simulation.
CandlebelongsTo AssetOHLCV. One per 5 ticks. Unique (asset_id, time_index).
TradebelongsTo User, Asset, SimulationSessionImmutable execution record.
HoldingbelongsTo User, AssetLive ownership. avg_cost recalculated on buys.
SimulationSessionbelongsTo User; hasMany Tradeended_at=NULL means active.
TutorialProgressbelongsTo UserPer-user step completion. Unique (user_id, step_key).

9. Services

PriceEngine

Core simulation engine. One MarketState per active asset. Uses SimpleQueue (trade queue) and SimpleList (tick buffer).

tick() for each active asset:
  noise  = currentPrice * (mt_rand(-1000,1000)/1000 * volatility)
  impact = sum( direction * currentPrice * (qty/liquidity) ) per queued trade
  price  = current + noise + impact   (floor $0.01)
  append to currentCandleTicks (SimpleList)
  if ticks >= 5: closeCandle() -> OHLCV -> DB -> reset SimpleList

MarketState

Per-asset in-memory value object: asset, currentPrice, currentCandleTicks (SimpleList), recentCandles (SimpleList, max 200), nextTimeIndex.

SimulationSessionManager

MethodDescription
getOrCreateActiveSession(User)Find open session (ended_at IS NULL) or create new ‘standard’ session.
syncBalance(Session, User)Copy user.current_balance → session. Called after every trade.
completeSession(User)Set ended_at = now() on active session.

PasswordHasher

hash(password):
  salt  = bin2hex(random_bytes(16))  // 32-char hex
  value = salt + "|" + password
  loop 1000x: value = sha256(value)
  return { hash: value, salt: salt }

verify(password, storedHash, salt):
  computed = runHashLoop(password, salt)
  status = 0
  for each char i: status |= ord(computed[i]) XOR ord(storedHash[i])
  return (status === 0)  // constant-time comparison

10. Custom Data Structures

SimpleList

Manual dynamic ordered list.

  • add(v) — O(1) append
  • get(i) — O(1) index access
  • set(i,v) — O(1) replace
  • removeAt(i) — O(n) left-shift
  • slice(s,l) — O(n) subset
  • size() — O(1)
  • last() — O(1)
Used: MarketState tick buffers & candle history

SimpleQueue (FIFO)

Manual front-removal without array_shift.

  • enqueue(v) — O(1) add to back
  • dequeue() — O(n) remove front
  • peek() — O(1)
  • isEmpty() — O(1)
Used: PriceEngine trade queue

SimpleStack (LIFO)

Push and pop from top.

  • push(v) — O(1)
  • pop() — O(1)
  • peek() — O(1)
  • isEmpty() — O(1)
Designed for: tutorial undo/back-navigation

11. Custom Algorithms

MergeSort O(n log n) — app/Support/Algorithms/MergeSort.php

Divide-and-conquer sort for arrays of associative arrays. No PHP built-in sort used.

sortByKey(items, key, ascending):
  if count <= 1: return unchanged
  left  = items[0 .. mid-1]    right = items[mid .. end]
  left  = sortByKey(left)      right = sortByKey(right)
  return merge(left, right)

merge: two-pointer comparison, take smaller (asc) or larger (desc), append remainder

Used: MarketHistoryController@show() and @json() — sort candles by time_index.

BinarySearch O(log n) — app/Support/Algorithms/BinarySearch.php

Fast lookup in a sorted array. Input must be pre-sorted.

findIndexByKey(items, key, target):
  low=0, high=count-1
  mid = floor((low+high)/2)
  items[mid][key] == target  →  return mid
  items[mid][key] <  target  →  low  = mid+1
  items[mid][key] >  target  →  high = mid-1
  return -1 if not found

Used: MarketHistoryController@show() — find candle by ?time_index=N.

TutorialGraph + DFS O(V+E) — app/Support/Graphs/TutorialGraph.php

Directed adjacency-list graph with recursive depth-first traversal.

Graph: intro → candles → risk → practice

dfs(node, visited, order):
  if visited: return
  mark visited, append to order
  for each neighbor: dfs(neighbor)

depthFirstOrder('intro') = ['intro', 'candles', 'risk', 'practice']

Used: TutorialController@index() for order, @complete() for next-step navigation.

Iterative SHA-256 Password Hash — app/Services/PasswordHasher.php
value = salt + "|" + password
for i = 0 to 999: value = sha256(value)
1000 iterations makes brute-force 1000x slower per attempt.

12. Middleware & Security

ManualAuthenticate (alias: auth.manual)

Checks session()->has('user_id'). If missing: redirect to login. Applied to all authenticated routes.

EnsureUserHasRole (alias: role)

Usage: middleware(['auth.manual', 'role:admin']). Checks session('user_role') === $required. If mismatch: abort(403). Applied to all /admin/* routes.

Security Measures

  • 1000-iteration SHA-256 + unique random salt per user (brute-force resistant).
  • Constant-time XOR comparison in password verify (prevents timing attacks).
  • CSRF tokens on all forms (Laravel default VerifyCsrfToken middleware).
  • Eloquent parameterized queries throughout (no SQL injection).
  • DB::transaction() in TradeController (atomic writes, no partial state).
  • Full server-side input validation before any database writes.
  • password and password_salt in User $hidden (excluded from JSON output).

13. Views / Frontend

View FileDescription
layouts/app.blade.phpMaster template. Navbar, sidebar, @yield(content).
partials/sidebar.blade.phpRole-aware navigation. Admin sees extra links.
auth/login.blade.phpEmail + password login form with error display.
auth/register.blade.phpRegistration form. Inline validation errors. Preserves old input.
dashboard/user.blade.phpCash balance, total portfolio value, P&L, holdings table, session info.
dashboard/admin.blade.phpAdmin welcome + quick links.
dashboard/portfolio.blade.phpFull holdings detail, realized/unrealized P&L, last 20 trades.
dashboard/session-summary.blade.phpSession report: balance P&L, trade counts, cash flow, full trade log.
market/live.blade.phpLive price table. JS polls /market/tick, updates DOM. Inline trade form.
market/charts.blade.phpGrid of asset cards linking to chart pages.
market/chart.blade.phpCandlestick chart. Fetches JSON from /market/history/{asset}/json.
market/history.blade.phpCandle history table (MergeSort sorted). Search uses BinarySearch.
tutorial/index.blade.phpAll steps in DFS order with completion status.
tutorial/step.blade.phpSingle step view with “Mark as complete” button.
admin/users.blade.phpAll users table + platform totals.
admin/assets/index.blade.phpAsset list with inline edit forms. Add Asset form.

14. Seeded Data

Run: php artisan db:seed — uses AssetSeeder with updateOrCreate (safe to re-run).

SymbolNameBase PriceVolatilityLiquidityTypeProfile
BLUEBlue Chip Industries$100.001.0%500,000Stockstable
TECHTech Growth Corp$50.003.5%250,000Stockgrowth
REITCity Real Estate Trust$80.001.5%300,000Stockdefensive
BTCSimulated Bitcoin$30,000.007.0%150,000Cryptocrypto

BLUE = lowest risk (1%). REIT = low-moderate (1.5%). TECH = medium-high (3.5%). BTC = highest risk (7%) — demonstrates crypto volatility to beginners.

15. Glossary

Asset
A tradeable instrument. Stock (BLUE, TECH, REIT) or cryptocurrency (BTC). Defined by price, volatility, liquidity, and behaviour profile.
Average Cost (Weighted)
Mean price paid per unit across multiple buys. ((old_qty × old_avg) + (new_qty × price)) ÷ total_qty.
Balance
User’s virtual cash. Starts at $1,000. Reduced by buys, increased by sells. Cannot go below $0.
Binary Search
O(log n) algorithm. Finds an element in a sorted array by halving the search range each step. Returns -1 if not found.
Candle / Candlestick
Price summary over a period: Open, High, Low, Close + Volume. One candle every 5 ticks. Green = closed above open. Red = closed below.
Close Price
The final price at the end of a candle period.
DFS (Depth-First Search)
Graph traversal exploring paths to their deepest point before backtracking. Used for tutorial step ordering.
Holding
Live record of how many units of an asset a user owns and at what average purchase cost.
High / Low
Maximum (High) and minimum (Low) prices reached during a candle period.
Initial Balance
Starting virtual budget: $1,000.00. Fixed baseline for overall P&L calculation.
Liquidity
Volume needed to move price. High liquidity = small impact per trade. Low = large impact.
Merge Sort
O(n log n) divide-and-conquer sort. Splits array, sorts halves recursively, merges back in sorted order.
OHLCV
Open, High, Low, Close, Volume — the five standard candle data fields.
Open Price
First price recorded at the start of a candle period.
P&L (Profit & Loss)
Current value minus original cost. Positive = profit. Negative = loss.
Portfolio
All holdings plus cash balance. Total value of everything a user owns.
Price Tick
Single simulated price update from PriceEngine (noise + trade impact).
Random Walk
Price simulation: each tick applies a random change bounded by volatility, making prices wander unpredictably.
Realized P&L
Profit/loss locked in by completing sell trades.
Role
‘user’ = standard trader. ‘admin’ = full management access. Enforced via session middleware.
Simulation Session
Time-bounded trading period. ended_at=NULL means active. Records start/end balance for performance review.
Time Index
Sequential integer (0,1,2…) identifying a candle’s position in price history. Used for MergeSort ordering and BinarySearch lookup.
Tutorial
4-step guided path: intro → candles → risk → practice. Ordered by DFS graph traversal. Progress tracked per user.
Unrealized P&L
Hypothetical gain/loss on open positions at current prices. Becomes realized only when sold.
Volatility
Decimal fraction controlling max price swing per tick. 0.07 = ±7% per tick. Higher = more risk & reward.
Volume
Quantity of units traded in a candle period. Currently 0 (placeholder for future implementation).