Copy-Trading Signals in Mobile App
Copy-trading is when a master trader's deals are automatically copied to subscribers' accounts. The mobile app solves two fundamentally different tasks here: for the master—publishing deals and managing subscribers, for the copier—receiving signals, configuring copy parameters, and monitoring results.
Signal Architecture
A signal is not just a notification "buy BTC". It's a structured object with sufficient context for automatic execution:
struct TradingSignal: Codable {
let id: UUID
let masterId: String
let pair: String // "BTC/USDT"
let side: TradeSide // .long, .short
let entryPrice: Decimal? // nil for market order
let takeProfit: [Decimal] // multiple TP levels
let stopLoss: Decimal
let leverage: Int? // for futures
let riskPercent: Decimal // % of subscriber's deposit
let comment: String?
let publishedAt: Date
let validUntil: Date? // signal expires
}
riskPercent instead of fixed amount—because subscribers have different deposits. 2% of $1,000 and 2% of $50,000—different amounts, but equal risk.
Signal Delivery: Latency Matters
Entry signal is a time-critical object. Price moves while signal travels. Latency requirements depend on master's strategy: for swing trading (positions held for days), push with 5–10 second delay is enough. For scalping—WebSocket is mandatory, push is too slow.
WebSocket for active users in the app. Backend broadcasts signal to all connected subscribers of the master simultaneously.
FCM/APNs push for background notifications. Important nuance: push is not guaranteed delivery, FCM can buffer. For trading signals this is unacceptable. So on app open—always sync missed signals via GET /signals?since={lastReceivedAt}.
// Android — receive and process signal
class SignalReceiver @Inject constructor(
private val signalRepository: SignalRepository,
private val orderExecutor: OrderExecutor,
private val notificationManager: AppNotificationManager,
) {
suspend fun handle(signal: TradingSignal) {
signalRepository.save(signal)
val subscription = signalRepository.getSubscription(signal.masterId) ?: return
if (!subscription.autoExecute) {
// Notification only, user decides manually
notificationManager.showSignal(signal)
return
}
// Auto-execution with checks
val account = accountRepository.getCurrent()
val orderSize = account.balance * subscription.riskPercent / 100
if (orderSize < exchange.minimumOrderSize(signal.pair)) {
notificationManager.showError("Insufficient balance to execute signal")
return
}
val result = orderExecutor.execute(signal, orderSize)
notificationManager.showExecutionResult(signal, result)
}
}
Copy Configuration
Each subscriber configures parameters separately for each master:
Position sizing:
- Fixed % of deposit (follow master's risk)
- Fixed amount in USDT
- Multiplier of master's size (for example, 0.5x if master's volume is too large)
Signal filters:
- Long only / short only / both directions
- Pairs (allow only BTC, ETH; ignore altcoins)
- Maximum leverage (don't copy if master uses > 10x)
- Maximum concurrent positions
Execution mode:
- Auto (immediately on signal receipt)
- With confirmation (push → user taps "Execute")
- Notifications only (no execution, monitor master's strategy)
Settings form is a key UX element. User should understand consequences of each parameter. For deposit %—show calculation: "At $2,000 deposit and 2% risk—$40 per trade".
Master Profile: What Subscriber Sees
Master profile shows his trading metrics, not marketing text:
| Metric | Value |
|---|---|
| Win Rate | 64% |
| Profit Factor | 1.87 |
| Max Drawdown | −18.4% |
| Sharpe Ratio | 1.31 |
| Trades (30d) | 142 |
| Subscribers | 2,840 |
| Monthly PnL | +12.3% |
Master's equity curve graph is mandatory. Subscriber should see not just "+30% per year", but how exactly: was there a long drawdown plateau? Sharp growth at start then stagnation?
My Results as Copier
Separate screen—PnL from copying specific master. Data differs from master: different execution prices (slippage), different entry time (signal delivery delay), different position sizes.
Comparison: master's equity curve vs. my results on same chart. If divergence is large—analyze reasons (slippage, missed signals, filter limits).
// iOS, SwiftUI — compare curves
Chart {
ForEach(masterEquity) { point in
LineMark(
x: .value("Date", point.date),
y: .value("PnL", point.pnl),
series: .value("Type", "Master")
)
.foregroundStyle(.blue)
}
ForEach(myEquity) { point in
LineMark(
x: .value("Date", point.date),
y: .value("PnL", point.pnl),
series: .value("Type", "My results")
)
.foregroundStyle(.green)
}
}
.chartLegend(.visible)
Master Search and Ratings
Master catalog with filters: by win rate, by drawdown, by trade count, by trading period (minimum 3 months active). Sort by Sharpe Ratio by default—only metric accounting for both return and risk simultaneously.
Master card in list—compact: avatar, nick, 3 key metrics, mini-sparkline of equity curve, "Subscribe" button.
What's Included
- Signal delivery via WebSocket + FCM/APNs with missed signal sync
- Copy settings form with filters and position size calculation
- Master profile with metrics and equity curve
- My results screen with master comparison
- Master catalog with filtering and ratings
- Signal history: received, executed, missed (with reason)
Timeline
| Role | Features | Duration |
|---|---|---|
| Copier (subscriber) | Signals, settings, results | 10–14 days |
| Master | Publishing, subscribers, analytics | 7–10 days |
| Full system | Catalog, ratings, both roles | 20–28 days |
Cost is calculated individually after requirements analysis.







